- Python 92.4%
- HTML 7.6%
Actor URI was a hardcoded bare /actor regardless of RELAY_ACTOR_NAME, so that config var only affected the WebFinger acct: form and preferredUsername. Changing it did nothing to actually change the actor's identity. Now actor_uri is https://{RELAY_DOMAIN}/actor/{RELAY_ACTOR_NAME}, so changing RELAY_ACTOR_NAME yields a genuinely new actor URI/keyId — the escape hatch for when a peer server caches a stale key for us that survives unfollow/refollow (hit this with GoToSocial after wiping our keypair during development; the old actor path is now abandoned in favor of a fresh one). Assisted-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| app | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| compose.yaml | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
relay
A small, self-hosted, manually-gated ActivityPub relay. It consumes
relay.fedi.buzz's public Mastodon-streaming
firehose (https://fedi.buzz/api/v1/streaming/public) and rebroadcasts every
post it sees as an Announce activity to whichever servers you've approved
as followers — nothing is filtered by tag, instance, or language.
Unlike old ActivityRelay-style (LitePub) relays, this one uses the same
one-way, bare-Announce-object pattern as buzzrelay/AodeRelay/rel.re,
which is what makes it work with GoToSocial.
How it works
- Peers (e.g. your GoToSocial instance) send a
Followto this relay's actor. It shows up as pending in the admin UI — nothing is auto-accepted. - You manually Accept or Reject each request at
/admin/requests. - Once accepted, every post from the firehose is
Announced to that follower's inbox (except posts originating from that follower's own domain). - Remove a follower any time at
/admin/followers— noUndoneeded on our side since we never Follow back.
Running
cp .env.example .env # set RELAY_DOMAIN to your real public hostname
docker compose up -d
No Dockerfile/image build step — compose.yaml runs uv's own image directly
against the source bind-mounted from this directory (uv sync happens on
container start), same pattern as the poster project.
The relay actor will be reachable at https://$RELAY_DOMAIN/actor/$RELAY_ACTOR_NAME
(default RELAY_ACTOR_NAME=relay, so https://$RELAY_DOMAIN/actor/relay). Point
GoToSocial's Admin → Federation → relay-subscribe flow at that URL (or the
WebFinger form acct:relay@$RELAY_DOMAIN), then approve the resulting
follow request in /admin/requests.
The actor path includes RELAY_ACTOR_NAME on purpose: if a peer server ever
caches a stale public key for us (e.g. after we regenerate our keypair) and
won't drop it even after unfollow/refollow, changing RELAY_ACTOR_NAME
gives the relay a brand new actor URI/keyId, forcing a completely fresh
discovery — at the cost of the peer needing to redo their subscription.
Important — proxy exposure: /actor/*, /.well-known/webfinger, and
/inbox must stay publicly reachable with no auth (remote servers hit them
directly). Everything under /admin/* and / should sit behind your
reverse proxy's auth (e.g. Pangolin) — there is no app-level login.
Local dev
uv run uvicorn app.main:app --reload
Data
Everything (follower state + the relay's own signing keypair) lives in a
single SQLite file at $DATA_DIR/relay.db (WAL mode), under ./data by
default — since the whole project directory is bind-mounted into the
container, this is just a regular directory on the host. That's the only
thing you need to back up.