User’s guide¶
ActivityPub¶
Using microblog.pub efficiently requires knowing a bit about how ActivityPub works.
Skimming over the Overview section of the ActivityPub specification should be enough.
Also, you should know that the Fediverse is a common name used to describe all the interconnected/federated instances of servers supporting ActivityPub (like Mastodon, Pleroma, PeerTube, PixelFed…).
Mastodon client apps¶
Besides the built-in web UI, you can also use your instance from any Mastodon client app (Tusky, Fedilab, Ivory, the official Mastodon app…): enter your domain where the app asks for an instance, then log in with your admin password. See the dedicated Mastodon client apps page for what’s supported, what isn’t, and troubleshooting tips.
Configuration¶
Profile¶
You initial profile configuration is generated via the setup wizard.
You can manually edit the configuration file stored in data/profile.toml (TOML), note that the following config items cannot be updated (without breaking federation):
domainusername
As these two config items define your ActivityPub handle @handle@domain.
You can tweak your profile by tweaking these items:
name: The name shown with your profile.summary: The summary or ‘bio’ part of your profile, written in Markdown.icon_url: Your profile image or avatar.image_url: This provides a ‘header’ or ‘banner’ image. Note that it is not shown by the default Microblog.pub templates. It will be used by Mastodon (which uses a 3:1 ratio image) and Pleroma. Pixelfed and Peertube, for example, don’t show these images by default.
Whenever one of these config items is updated, an Update activity will be sent to all known servers to update your remote profile.
The server will need to be restarted for taking changes into account.
Before restarting the server, you can ensure you haven’t made any mistakes by running the configuration checking task.
Note that currently image_url is not used anywhere in microblog.pub itself, but other clients/servers do occasionally use it when showing remote profiles as a background image.
Also, this image can be used in microblog.pub - just add this:
<img src="{{ local_actor.image_url | media_proxy_url }}">
to an appropriate place of your template (most likely, header.html).
For more information, see a section about custom templates further in this document.
Local avatar/header files¶
If you’d rather not host your avatar/header elsewhere and reference it with an absolute URL, you can drop image files directly into the instance and let it serve them. The avatar and header are resolved in this priority order:
icon_url/image_urlindata/profile.toml— an explicit URL here always wins. If set, the files below are ignored.data/avatar.jpg/data/profile.image.jpg— your own files, kept with the rest of your mutable data (and therefore preserved across upgrades and Docker image rebuilds).app/static/avatar.jpg/app/static/profile.image.jpg— packaged defaults shipped with the instance; adata/file of the same name overrides them.
When one of these files is used, it is served from /img/avatar.jpg (or
/img/profile.image.jpg) on your own domain, and that URL is what gets
advertised as your icon/image over ActivityPub and as the og:image meta
tag. Only those two exact filenames are served this way. As with any profile
change, restart the server for it to take effect (an Update activity is then
federated to known servers).
Profile metadata¶
You can add metadata to your profile with the metadata config item.
Markdown is supported in the value field.
Be aware that most other software like Mastodon will limit the number of key/value to 4.
metadata = [
{key = "Documentation", value = "[https://docs.microblog.pub](https://docs.microblog.pub)"},
{key = "Source code", value = "[https://github.com/toniher/microblog.pub](https://github.com/toniher/microblog.pub)"},
]
Discoverability¶
These two profile.toml items are published on your actor, so remote servers know
what you want done with your profile and posts. Both default to true, matching what
the public web profile and the shipped robots.txt already allow:
discoverable— may other servers list your profile in directories, suggestions and “who to follow” features?indexable— may they include your posts in full-text search?
discoverable = false
indexable = false
They are requests, not enforcement: a remote server is free to ignore them, and
nothing here stops a public page from being read. Set them to say what you want; keep
posts non-public if you need it guaranteed. Both are also reported to Mastodon clients
(as discoverable and noindex on the account), and microblog.pub honours the same
properties on remote actors when they publish them.
Followers and following¶
These profile.toml items control how followers are handled and what’s shown
publicly (all default to false):
manually_approves_followers— require manual approval of new followers.hides_followers— hide your followers list.hides_following— hide who you are following.
manually_approves_followers = true
hides_followers = true
hides_following = true
Quote posts¶
When a remote server asks to quote one of your posts (FEP-044f), the quote_policy
config item controls how the request is handled:
quote_policy = "public"
"public"(the default) — auto-accept a request targeting a public or unlisted post, reject anything else."followers"— like"public", but the requester must also be one of your followers."manual"— every request surfaces as a notification with accept/reject buttons, like an incoming follow request. This one can’t be turned off viadisabled_notifications, for the same reason a pending follow request can’t be: it’s the only place to act on it."nobody"— always reject.
Quoting someone else’s post from here always sends that server a request first
(except quoting your own post, which needs no request); the status stays pending
until they respond. The handshake is FEP-044f — a QuoteRequest the
other server answers with an Accept (carrying an authorization stamp) or a
Reject. That stamp matters beyond the pending state: Mastodon 4.5 and later
refuse to display a quote that doesn’t carry one.
A stamp you’ve granted for one of your own posts can be revoked from /admin — a
revoke quote button next to the quoting post and next to its notification. That
sends the quoting server a Delete of the stamp and stops the quote rendering here
immediately, but reaching everyone who already saw the quote depends on that server
forwarding the revocation to its own audience, as the FEP describes.
URL aliases¶
Every post is reachable at an opaque permalink like /o/ef0166f89a124e7d9d3d6305cfa5597a.
From a post’s admin edit page, you can give it a human-readable alias instead —
set it and the post becomes canonically reachable at /post/my-first-note; the old
/o/… link keeps working, redirecting (301) to the alias. Clearing the alias field
restores the original URL. This is local-only: changing only the alias does not
federate an Update to followers.
Whatever you type is normalized: lowercased, spaces collapsed into hyphens, and
anything that isn’t a letter, digit, hyphen or underscore is stripped — so
My First Note! becomes my-first-note. Prefer hyphens over underscores for
word separation (my-first-note, not my_first_note); hyphens are what the
normalization produces automatically and are the convention used everywhere
else in the app’s own URLs. Aliases are capped at 200 characters and must be
unique across all posts, including deleted ones.
The path prefix (post by default) is configurable via the alias_url_prefix
config item in profile.toml:
alias_url_prefix = "blog"
It must not collide with an existing top-level route (o, articles, admin,
static, .well-known, and similar are rejected at startup). Treat it as a
set-once setting: changing it after posts have already been aliased and federated
invalidates the url already sent to remote servers for those posts (local
rendering picks up the new prefix immediately, and /o/{public_id} — the
ActivityPub id, which never changes — keeps resolving either way).
Default language¶
You can set a default language for your instance with the language_code config item in
profile.toml, using a BCP 47 tag
(e.g. en, fr, pt-BR):
language_code = "en"
The default value is en. It is used for the lang attribute of the generated HTML pages
(which helps screen readers and translation tools) and as the suggested default when
setting a per-note language.
It also acts as the fallback language the interface (buttons, labels, messages) is
shown in: both the admin UI (/admin) and public pages use a visitor’s browser
language when a translation is available for it, falling back to language_code
otherwise (e.g. no Accept-Language header sent, or none of the requested languages
are available). See the developer guide for
how translations are added.
Schema.org microdata¶
By default, the generated HTML only carries microformats2
markup. You can additionally enable schema.org structured data
(itemscope/itemtype/itemprop microdata attributes) with:
enable_microdata = true
The default value is false. When enabled, feeds are marked up as a Blog, and each
note/article/video/event as the corresponding schema.org type (BlogPosting,
SocialMediaPosting, VideoObject, Event…), with author, datePublished, url
and media properties filled in alongside the existing microformats2 classes. This can
help search engines and other tools that read schema.org structured data (e.g. rich
results) understand your content.
Privacy replace¶
You can define domains to be rewritten to more “privacy friendly” alternatives, like Invidious or Nitter.
To do so, add these extra config items. This is a sample config that rewrite URLs for Twitter, Youtube, Reddit and Medium:
privacy_replace = [
{domain = "youtube.com", replace_by = "yewtu.be"},
{domain = "youtu.be", replace_by = "yewtu.be"},
{domain = "twitter.com", replace_by = "nitter.fdn.fr"},
{domain = "medium.com", replace_by = "scribe.rip"},
{domain = "reddit.com", replace_by = "teddit.net"},
]
Disabling certain notification types¶
All notifications are enabled by default.
You can disabled specific notifications by adding them to the disabled_notifications list.
This example disables likes and shares notifications:
disabled_notifications = ["like", "announce"]
Available notification types¶
new_followerrejected_followerunfollowfollow_request_acceptedfollow_request_rejectedmovelikeundo_likeannounceundo_announcementionnew_webmentionupdated_webmentiondeleted_webmentionblockedunblockedblockunblockreportedquotestatusupdatepoll
Outgoing HTTP connections¶
Requests the server makes to other instances — fetching ActivityPub objects, delivering activities, webfinger lookups, and proxying remote media — go through shared HTTP clients that keep connections alive and reuse them, negotiating HTTP/2 where the remote end supports it.
This is enabled by default and needs no configuration. All four settings below are optional; omit them to keep the default behaviour.
http_client_pooling = true
http_client_http2 = true
http_client_max_connections = 100
http_client_max_keepalive_connections = 20
http_client_pooling— whenfalse, no connection is kept open past the response that used it, so every request opens a fresh one. This also disables HTTP/2, since running several requests over a single multiplexed connection is a form of connection sharing as well.http_client_http2— whenfalse, connections are still pooled and reused, but only HTTP/1.1 is negotiated. Has no effect ifhttp_client_poolingisfalse.http_client_max_connections— upper bound on connections open at once, across all remote hosts.http_client_max_keepalive_connections— how many idle connections are kept around for reuse.
One visible side effect of pooling: because a page’s remote images usually come from a
single host, they are now fetched over one shared connection and tend to finish
together, rather than trickling in one by one as they did when every request opened its
own connection. The page finishes loading sooner, but it renders in one go instead of
progressively. Setting http_client_pooling = false restores the old behaviour at the
cost of re-establishing a connection for every request.
Outgoing federation delivery¶
Activities queued for delivery (to followers, mentioned actors, webmention targets…) are sent out in batches, with several deliveries in flight at once instead of one at a time.
This is enabled by default and needs no configuration. All three settings below are optional; omit them to keep the default behaviour.
outgoing_delivery_batch_size = 20
outgoing_delivery_concurrency = 10
outgoing_delivery_per_host_concurrency = 2
outgoing_delivery_batch_size— how many due activities are fetched per poll.outgoing_delivery_concurrency— how many deliveries run at once, across all recipients. Must not exceedhttp_client_max_connections.outgoing_delivery_per_host_concurrency— how many deliveries run at once against a single remote host. Must not exceedoutgoing_delivery_concurrency.
Delivery order to a given recipient inbox is always preserved regardless of these
values — activities queued for the same inbox are still delivered one at a time, in
order, so a boost can never overtake the post it boosts, for example. Only deliveries
to different recipients run concurrently. Setting outgoing_delivery_concurrency = 1
restores fully serial delivery, one activity at a time.
Upload size limits¶
All settings below are optional; omit them to keep the defaults, which match what’s advertised to Mastodon clients out of the box.
max_image_upload_size = 10485760 # 10 MiB
max_video_upload_size = 41943040 # 40 MiB
max_image_pixels = 16777216 # 16 MP
max_image_upload_size— the largest an image upload may be, in bytes.max_video_upload_size— the largest a video or audio upload may be, in bytes (audio shares this limit rather than getting a third setting, matching Mastodon).max_image_pixels— the largest an image upload may be in pixels (width × height).
The pixel cap is not redundant with the byte cap: a 7 MB JPEG straight off a phone decodes to a 12 megapixel bitmap, and EXIF-stripping and thumbnailing both cost memory and CPU in proportion to pixel count, not file size. The limit is checked from the image header, before any pixel is decoded, so an oversized image costs nothing to reject.
A file over its limit is rejected before any of it is written to disk. If you’re behind a
reverse proxy, make sure its own body-size cap (e.g. nginx’s client_max_body_size, see
Reverse proxy) is at least as large, or it will reject a
large-but-valid upload before the app ever sees it.
Web Push delivery¶
Mastodon client apps that support push notifications (see
Mastodon client apps) are served by a separate push_worker
process. Both settings below are optional; omit them to keep the defaults.
vapid_key_path = "data/vapid_key.pem"
push_delivery_batch_size = 10
vapid_key_path— where the instance’s VAPID (RFC 8292) keypair lives. Generated automatically on first use; back it up along with the rest ofdata/— regenerating it invalidates every existing subscription, and clients must re-subscribe.push_delivery_batch_size— how many subscriptions with pending notifications are fetched per poll by thepush_workerprocess.
Streaming API¶
Mastodon client apps that support live timeline/notification updates (see
Mastodon client apps) connect over wss://…/api/v1/streaming.
Unlike Web Push, this needs no separate process — see the linked section for why.
All settings below are optional; omit them to keep the defaults.
streaming_enabled = true
streaming_poll_interval = 1.0
streaming_max_connections = 32
streaming_enabled— set tofalseto disable the endpoint entirely; this also removes thestreaming_api/configuration.urls.streamingadvertisement, so clients never attempt a connection that would just fail.streaming_poll_interval— how often (seconds) the in-process pump checks for new activity while at least one client is connected. Lower is more responsive and costs a few more cheap queries per second; has no cost at all when no client is connected.streaming_max_connections— a cap on concurrent streaming sockets, sincepublic/hashtagstreams can be reached without authentication.
Customization¶
Default emoji¶
If you don’t like cats, or need more emoji, you can add your favorite emoji in profile.toml and it will replace the default ones:
emoji = "🙂🐹📌"
You can copy/paste them from getemoji.com.
Custom emoji¶
You can add custom emoji in the data/custom_emoji directory and they will be picked automatically.
Do not use exotic characters in filename - only letters, numbers, and underscore symbol _ are allowed.
Custom CSS¶
The CSS is written with SCSS.
You can override colors and the font by editing data/_theme.scss:
$primary-color: #e14eea;
$secondary-color: #32cd32;
The following variables are available to override (see app/scss/main.scss for the
authoritative list and their default values):
Variable |
Purpose |
|---|---|
|
Font family used across the site |
|
Page background |
|
Alternate/secondary background (e.g. cards) |
|
Main body text |
|
Primary accent (links, default favicon) |
|
Secondary accent |
|
Muted/secondary text (e.g. timestamps) |
|
Form field background |
|
Form field text |
|
Navigation button background |
|
Navigation button text |
|
Text on primary buttons |
|
Background behind highlighted code blocks |
Only set the variables you want to change — anything you leave out keeps its default.
If you need to go further than variables, you can add arbitrary SCSS after the variable
definitions in data/_theme.scss.
The palette is exposed as CSS custom properties, so the site automatically follows the
visitor’s system light/dark preference (prefers-color-scheme). Every variable above has a
dark-mode counterpart prefixed with $dark- (e.g. $dark-background, $dark-text-color,
$dark-primary-color) that you can override the same way. Leave them unset to keep the
built-in dark theme:
$dark-background: #101418;
$dark-primary-color: #6fd06f;
You will need to recompile CSS after doing any CSS changes (for actual css files to be updates) and restart microblog.pub (for css link in HTML documents to be updated with a new checksum - otherwise, browsers that downloaded old CSS will keep using it).
Custom favicon¶
By default, microblog.pub favicon is a square of $primary-color CSS color (see above section on how to redefine CSS colors).
You can change it to any icon you like - just save a desired file as data/favicon.ico.
It is served the same way as the avatar/header images: a data/favicon.ico
overrides the packaged app/static/favicon.ico default (the themed square) at
serve time, so you only need to restart microblog.pub for it to take effect — no
recompile step required. As with your data, it is preserved across upgrades and
Docker image rebuilds.
Web analytics¶
You can add a self-hosted analytics tool such as Matomo,
Plausible, GoatCounter
or umami by pasting the provider’s tracking snippet into
the analytics_html config item in profile.toml:
analytics_html = '''
<script>
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u = "https://analytics.example.com/";
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '1']);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.async = true; g.src = u + 'matomo.js'; s.parentNode.insertBefore(g, s);
})();
</script>
'''
The snippet is inserted verbatim (as raw HTML) in the <head> of public
pages only — it is never shown on /admin pages, and never shown to you when
you’re browsing your own instance logged in, so your own visits aren’t tracked.
By default the Content Security Policy
only allows resources from your own domain (default-src 'self'), which will
block a script loaded from another host. If your analytics provider is hosted
on a different domain than your instance, you also need to set
custom_content_security_policy to allow it, e.g.:
custom_content_security_policy = "default-src 'self'; style-src 'self' 'sha256-{HIGHLIGHT_CSS_HASH}'; script-src 'self' https://analytics.example.com; connect-src 'self' https://analytics.example.com; img-src 'self' https://analytics.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"
Custom templates¶
If you’d like to customize your instance’s theme beyond CSS, you can modify the app’s HTML by placing templates in data/templates which overwrite the defaults in app/templates.
A template placed in data/templates/<name>.html fully replaces app/templates/<name>.html.
The ones you are most likely to want to override are:
header.html— the site header shown at the top of every public page (name, avatar, navigation)layout.html— the overall page skeleton (<head>, footer, common markup)index.html— the public homepageutils.html— shared macros (see the per-macro override trick below)
Templates are written using Jinja templating language.
Moreover, utils.html has scoped blocks around the body of every macro.
This allows macros to be overridden individually in data/templates/utils.html, without copying the whole file.
For example, to only override the display of a specific actor’s name/icon, you can create data/templates/utils.html file with following content:
{% raw %}
{% extends "app/utils.html" %}
{% block display_actor %}
{% if actor.ap_id == "https://me.example.com" %}
<!-- custom actor display -->
{% else %}
{{ super() }}
{% endif %}
{% endblock %}
{% endraw %}
Custom Content Security Policy (CSP)¶
You can override the default Content Security Policy by adding a line in data/profile.toml:
custom_content_security_policy = "default-src 'self'; style-src 'self' 'sha256-{HIGHLIGHT_CSS_HASH}'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"
This example will output the default CSP, note that {HIGHLIGHT_CSS_HASH} will be dynamically replaced by the correct value (the hash of the CSS needed for syntax highlighting).
Code highlighting theme¶
You can switch to one of the styles supported by Pygments by adding a line in data/profile.toml:
code_highlighting_theme = "solarized-dark"
Custom routes¶
Custom routes can be added in data/custom_routes.py (the file is imported
automatically at startup if it exists).
The simplest option is to serve a static HTML page. The html_file is read from the
data/ directory and its content is inserted verbatim (as raw HTML) into the page
body, wrapped in the site layout:
from app.customization import register_html_page
register_html_page(
"/testcustom",
title="test html page",
html_file="test.html", # relative to data/
show_in_navbar=True,
)
If you register a page at /, it replaces the homepage, and the default stream of
notes is moved to /notes automatically.
For anything more dynamic, you can register a raw FastAPI handler instead:
from starlette.responses import PlainTextResponse
from app.customization import register_raw_handler
async def my_handler(request):
return PlainTextResponse("hello from a custom route")
register_raw_handler(
"/hello",
title="Hello",
handler=my_handler,
show_in_navbar=False,
)
Routes registered with show_in_navbar=True (the default) get a link added to the
navigation bar automatically.
Custom stream visibility¶
The main public stream (the homepage) shows, by default, non-reply notes from people you follow, plus anything mentioning you and replies within your own conversations.
You can override exactly what appears in the stream by defining a
custom_stream_visibility_callback in data/stream.py. It receives an ObjectInfo
for each incoming object and returns True to show it in the stream or False to
hide it:
from app.customization import ObjectInfo
def custom_stream_visibility_callback(object_info: ObjectInfo) -> bool:
# e.g. only show posts tagged #mycommunity from people you follow
return (
object_info.is_from_following
and "mycommunity" in object_info.hashtags
)
The ObjectInfo passed to the callback exposes:
Field |
Description |
|---|---|
|
Whether the object is a reply |
|
Whether it’s a reply to one of your own objects |
|
Whether it mentions you (the local actor) |
|
Whether it’s from someone you follow |
|
List of hashtags on the object, e.g. |
|
The author’s handle, e.g. |
|
The full remote object |
Restart microblog.pub after adding or changing data/stream.py.
Blocking accounts¶
You can block a single actor from anywhere their profile is displayed in the admin
interface (the block button next to their handle).
The Blocks page in the admin menu lists every account you have blocked, with an
unblock button for each — it’s also where you can check whether someone is still
blocked. Mastodon clients see the same list through GET /api/v1/blocks.
Note that the list is ordered by when each account was first seen by your instance, not by when you blocked it: blocks are stored as a flag on the account, without a timestamp of their own.
Muting accounts¶
Muting is the quieter alternative to a block: the account keeps following you and can still see your posts, nothing is federated (they can’t tell), but their posts stop appearing anywhere you read. That covers the stream, their boosts, and other people’s boosts of them, plus their notifications.
The mute button sits next to block wherever a profile is displayed in the
admin interface, and the Mutes page in the admin menu lists everyone you’ve
muted with an unmute button for each. Muting someone hides posts you already
received rather than deleting them — unmuting brings them back.
Mastodon clients see the same list through GET /api/v1/mutes and can mute with
the two options the API defines: whether notifications are hidden too (the admin
button always hides them), and a duration after which the mute lapses on its own
(the admin button mutes until you undo it). Like the blocks list, the page is
ordered by when each account was first seen by your instance, not by when you
muted it.
Blocking servers¶
In addition to blocking “single actors” via the admin interface, you can also prevent any communication with entire servers.
Add a blocked_servers config item into profile.toml.
The reason field is just there to help you document/remember why a server was blocked.
You should unfollow any account from a server before blocking it.
blocked_servers = [
{hostname = "bad.tld", reason = "Bot spam"},
]
Public website¶
Public notes will be visible on the homepage.
Only the last 20 followers/follows you have will be shown on the public website.
And only the last 20 interactions (likes/shares/webmentions) will be displayed, to keep things simple/clean.
Admin section¶
You can login to the admin section by clicking on the Admin link in the footer or by visiting https://yourdomain.tld/admin/login.
The password is the one set during the initial configuration.
By default, an admin session stays valid for 3 days. You can change this by setting session_timeout (in seconds) in profile.toml:
session_timeout = 86400 # stay logged in for 1 day
Lookup¶
The Lookup section allows you to interact with any remote remote objects/content on the Fediverse.
The lookup supports:
profile page, like
https://testing.microblog.pubcontent page, like
https://testing.microblog.pub/o/4bccd2e31fad43a7896b5a33f0b8ded9username handle like
@testing@testing.microblog.pubActivityPub ID, like
https://testing.microblog.pub/o/4bccd2e31fad43a7896b5a33f0b8ded9
Post actions¶
Each post shows a row of admin-only action buttons. Two of them apply to specific kinds of posts:
history— shown on your own posts once they have at least one edit. Opens a page listing every past revision, with the timestamp it was saved at, so you can see what a post looked like before each edit.fetch replies— shown on posts from other instances that appear in your inbox (boosts and replies included). Triggers an on-demand fetch of that post’srepliescollection from the remote server, for replies that were never delivered to you directly. It’s capped to a few seconds and a couple of new replies per click, so it’s meant to be pressed again if a thread has more replies than it pulled in on the first try, rather than left to fetch everything at once.
Webmentions¶
Public notes that link to “Webmention-compatible” website will trigger an outgoing webmention. Most websites that support Webmention will display your profile on the mentioned page.
Fenced code blocks¶
You can include code blocks in notes, using the triple backtick syntax.
The code will be highlighted using Pygments.
Example:
Hello
```python
print("I will be highlighted")
```
Interactions¶
microblog.pub supports the most common interactions supported by the Fediverse.
Likes¶
Liking an object will notify the author.
Unlike sharing, liked objects are not displayed on the homepage.
Most receiving servers will increment the number of likes.
Receiving a like will trigger a notification, increment the likes counter on the object and the actor avatar will be displayed on the object permalink.
Bookmarks¶
Bookmarks allow you to like objects without notifying the author.
It is basically a “private like”, and allows you to easily access them later.
It will also prevent objects to be pruned.
Webmentions¶
Sending webmentions to ping mentioned websites is done automatically once a public note is authored.
Receiving a webmention will trigger a notification, increment the webmentions counter on the object and the source page will be displayed on the object permalink.
Backup and restore¶
All the data generated by the server is located in the data/ directory:
Configuration files
Server secrets
SQLite3 database
Theme modifications
Custom emoji
Uploaded media
Restoring is as easy as adding your backed up data/ directory into a fresh deployment.
Moving from another instance¶
If you want to move followers from your existing account, ensure it is supported in your software documentation.
For Mastodon you can look at Moving or leaving accounts.
If you wish to move to another instance, see Moving to another instance.
First you need to grab the “ActivityPub actor URL” for your existing account:
Python edition¶
# For a Python install
poetry run inv webfinger username@instance-you-want-to-move-from.tld
Edit the config.
Docker edition¶
# For a Docker install
make account=username@instance-you-want-to-move-from.tld webfinger
Edit the config.
Edit the config¶
And add a reference to your old/existing account in profile.toml:
also_known_as = "https://instance-you-want-to-move-form.tld/users/username"
Restart the server, and you should be able to complete the move from your existing account.
Note that if you already have a redirect in place on Mastodon, you may have to remove it before initiating the migration.
Import follows from Mastodon¶
You can import the list of follows/following accounts from Mastodon.
It requires downloading the “Follows” CSV file from your Mastodon instance via “Settings” / “Import and export” / “Data export”.
Then you need to run the import task:
Python edition¶
# For a Python install
poetry run inv import-mastodon-following-accounts following_accounts.csv
Docker edition¶
# For a Docker install
make path=following_accounts.csv import-mastodon-following-accounts
Tasks¶
Configuration checking¶
You can confirm that your configuration file (data/profile.toml) is valid using the check-config
Python edition¶
poetry run inv check-config
Docker edition¶
make check-config
Recompiling CSS files¶
You can ensure your custom theme is valid by recompiling the CSS manually using the compile-scss task.
Python edition¶
poetry run inv compile-scss
Docker edition¶
make compile-scss
Password reset¶
If have lost your password, you can generate a new one using the reset-password task.
Python edition¶
# shutdown supervisord
poetry run inv reset-password
# edit data/profile.toml
# restart supervisord
Docker edition¶
docker compose stop
make reset-password
# edit data/profile.toml
docker compose up -d
Pruning old data¶
You should prune old data from time to time to free disk space.
The default retention for the inbox data is 15 days.
It’s configurable via the inbox_retention_days config item in profile.toml:
inbox_retention_days = 30
Data owned by the server will never be deleted (at least for now), along with:
bookmarked objects
liked objects
shared objects
inbox objects mentioning the local actor
objects related to local conversations (i.e. direct messages, replies)
For now, it’s recommended to make a backup before running the task in case it deletes unwanted data.
You should shutdown the server before running the task.
Python edition¶
# shutdown supervisord
cp -r data/microblogpub.db data/microblogpub.db.bak
poetry run inv prune-old-data
# relaunch supervisord and ensure it works as expected
rm data/microblogpub.db.bak
Docker edition¶
docker compose stop
cp -r data/microblogpub.db data/microblogpub.db.bak
make prune-old-data
docker compose up -d
rm data/microblogpub.db.bak
Reporting unattached uploads¶
Uploaded media (images, video, audio) is stored independently of the post it’s
attached to, and in a few cases a row can end up unattached to any post — e.g.
a rejected multi-file post, or media uploaded via the Mastodon API and never
used. This is a read-only report: it lists unattached Upload rows and files
in data/uploads with no matching row, but deletes nothing. Unattached
does not mean deletable: it can be in-flight media for a post not submitted
yet, or media queued in a scheduled post. The server doesn’t need to be
stopped to run it.
Python edition¶
poetry run inv report-unattached-uploads
Docker edition¶
make report-unattached-uploads
Removing a confirmed orphan by hand¶
The report deliberately doesn’t delete anything – an unattached upload isn’t
necessarily garbage (see above). If, after checking, you’re sure a row is
safe to remove (e.g. an old, undated one with no (referenced by a scheduled status) note, and no ongoing draft it could belong to), remove it by hand.
data/ is the same directory in both the Python and Docker editions, so this
runs against it directly on the host either way:
# Back up first
cp -r data/microblogpub.db data/microblogpub.db.bak
# id and hash are the Upload#<id> and hash=... values the report printed
sqlite3 data/microblogpub.db "DELETE FROM upload WHERE id = <id>;"
rm -f "data/uploads/<hash>" "data/uploads/<hash>_resized"
# once you've confirmed the server still starts and behaves as expected
rm data/microblogpub.db.bak
For a file the report lists with no matching Upload row (the second half
of the report), there’s nothing in the database to touch – just remove the
file(s) directly.
Moving to another instance¶
If you want to migrate to another instance, you have the ability to move your existing followers to your new account.
Your new account should reference the existing one, refer to your software configuration (for example Moving or leaving accounts from the Mastodon doc).
If you wish to move from another instance, see Moving from another instance.
Execute the Move task:
Python edition¶
# For a Python install
poetry run inv move-to username@domain.tld
Docker edition¶
# For a Docker install
make account=username@domain.tld move-to
Deleting the instance¶
If you want to delete your instance, you can request other instances to delete your remote profile.
Note that this is a best-effort delete as some instances may not delete your data.
The command won’t remove any local data, it just broadcasts account deletion messages to all known servers.
After executing the command, you should let the server run until all the outgoing delete tasks are sent.
Once deleted, you won’t be able to use your instance anymore, but you will be able to perform a fresh re-install of any ActivityPub software.
Python edition¶
# For a Python install
poetry run inv self-destruct
Docker edition¶
# For a Docker install
make self-destruct
Troubleshooting¶
If the server is not (re)starting, you can:
Verify if you haven’t any syntax error in the custom theme by recompiling the CSS.
Look at the log files (in
data/uvicorn.log,data/incoming.loganddata/outgoing.log; see Viewing logs for how they rotate and are compressed).If the CSS is not working, ensure your reverse proxy is serving the static file correctly.