Skip to content

Federation

Connect independent Trackarr instances so they can share catalogue, social activity, account proofs and (optionally) swarms — a private, owner-curated network rather than one monolithic tracker. Every instance stays sovereign: it keeps its own users, ratio economy, moderation and data, and decides — per partner, per direction — exactly what it shares.

Federation is off by default and only the owner (an admin) can turn it on, from /admin/federation.

Admin · Federation — the instance's own identity, the default sharing scopes proposed to new partners, and sync health

Trust model

There is no central authority and no PKI. Trust is established once, by hand, between two owners.

  • Each instance owns a single Ed25519 keypair, generated lazily the first time federation is enabled. The private key is encrypted at rest (same cipher as notification-channel secrets — CHANNEL_ENCRYPTION_KEY, falling back to NUXT_SESSION_SECRET).
  • An instance's instanceId is the fingerprint of its public key (tk_ + truncated SHA-256 over the SPKI). It can never drift from the key it names.
  • Every server-to-server (S2S) request is signed (see Security).
  • A peer only exists once it's in your allow-list (federation_peers), added through a double opt-in handshake that both owners confirm.

The handshake (double opt-in)

Owner A                                  Owner B
  │  add B's URL in /admin/federation
  │  POST /api/federation/handshake  ───────────►  pending_in row + owner notice
  │            (status: pending_out)               (B verifies A's fingerprint)
  │                                                 owner B approves → scopes chosen
  │  ◄───────  POST /api/federation/callback ─────  (status: active on B)
  │  status: active                                 callback signed with B's key
  1. A initiates. Owner A enters B's public URL. A sends a signed handshake carrying A's public key + offered scopes; A's side is now pending_out.
  2. B reviews. B stores a pending_in row and notifies its owner. Owner B verifies the short fingerprint (e.g. 8E·44·A2) with owner A out of band (the only defense against an impostor URL) and approves, choosing what B shares and accepts.
  3. B calls back. B flips to active and sends a signed callback; A flips to active too. The link is live.

Re-handshakes from a known peer never silently re-open a blocked/revoked link, and a callback only ever completes an outbound handshake — it can't rewrite an established link's scopes.

Scopes

Sharing is granular and asymmetric. For each peer you set two independent scope sets:

FieldMeaning
sharesWithThemwhat you expose to that peer
acceptsFromThemwhat you consume from that peer

Each set is four booleans:

ScopeUnlocks
catalogtorrent metadata discovery (browse + search)
socialfederated comments & forum, follow notifications
accountsidentity-link verification + read-only reputation
swarmpeer cross-announce (highest risk — see Swarm)

You can cut one scope without touching the others (e.g. drop social, keep catalog) at any time from the peer's Manage dialog — it takes effect on the next request.

The four axes

Axis 1 — Catalogue

Discover and search partner content without touching swarms.

  • Cache mode (default). A background cron (FEDERATION_SYNC_INTERVAL, 15 min) reconciles with each catalog-sharing peer and pulls whatever is missing into a local, read-only mirror (remote_torrents). Browse it at /federated.
  • Reconciliation, not a cursor. The two sides compare fingerprints of ranges of their catalogues and converge on exactly what differs, in a handful of round trips whatever the size. When nothing has changed that is one request and one "we agree" — and unlike a watermark it is proven rather than assumed, so a release that went missing for any reason is found and repaired on the next pass instead of never.
  • The partner drives our work, so the work is budgeted. A range that is open at an end costs an aggregate over most of the record set — measured at 115–145 ms, and 300–465 ms more when the two sides disagree, over 200 000 records. Four such ranges per message and 120 per minute per peer, which is five times what a real sync needs (two per message, twelve rounds a tick). Over budget, the expensive ranges come back as pending and the initiator re-sends them: a throttled round is deferred, never failed, so nothing is lost and no healthy peer ever shows an error for it.
  • Live mode. Toggle Live on /federated to fan out a signed GET /api/federation/search?q= to every partner in real time (time-bounded, best-effort). It answers with the same signed records the feed streams, so a live result is verified and cached exactly like a synced one.
  • Signed records. Everything a partner asserts about a release travels as an immutable record it signed with its own key, verified on arrival. A partner can decline to answer or serve you nothing — it cannot forge a release it did not publish, nor alter one in flight. New uploads, edits and withdrawals all arrive in that one stream: an edit is a record that supersedes another, a withdrawal is a tombstone.
  • Swarm counts are the exception. They change constantly, so they cannot live inside an immutable record and travel unsigned on their own feed. Treat a partner's seeder count as a hint, not a promise.
  • Dedupe. A remote item already present locally is flagged "also here" (same info-hash) or "same content here" (same content-signature, different info-hash). Hints only — the mirror is never merged into your local torrents table.
  • Download. A federated torrent's link points back to the origin instance — you download there, with your account there. Trackarr never serves a partner's .torrent with your local passkey.

What crosses the wire is metadata only: name, size, category, tags, media IDs, stats, and the uploader's display name — never the .torrent bytes nor a real user id.

The federated catalogue at  — each release carries the partner it
came from, the remote uploader's display name, and that instance's swarm
counts; Open hands you off to the origin tracker

Axis 2 — Social

A light fediverse layer, pull-based and read-only.

  • Follows. Follow a remote uploader from /federated; when the catalogue sync first sees a new upload by someone you follow, you get a federated_followed_upload notice (capped per run; the initial backlog is never blasted out).
  • Comments & forum. A remote torrent's comments and a partner's recent forum topics are fetched live, rendered read-only with attribution, and sanitized exactly like local content (DOMPurify). Aggregated forum view at /forum/federated.
  • Sovereignty. Everything remote is gated on the peer's social scope; cut it and the exchange stops immediately. No remote moderation is ever imposed on you.

Axis 3 — Accounts & reputation

Portable identity, never portable economy.

  • Linked identity (recommended). A local user proves they own an account on a partner: Trackarr issues a one-time trackarr-verify-<hex> code, the user pastes it into their bio on the partner, and a signed GET /api/federation/verify-identity confirms it server-side. A "verified on partner.example" badge appears. Manage at /federated-identity.
  • Reputation (read-only). For a verified link, ratio / uploads / member-since can be pulled as a display signal, always labeled with its source. It is never folded into your local ratio, bonus or invites.
  • No SSO. Single sign-on is intentionally out of scope — the zero-knowledge auth model makes it unsafe, and account economies stay isolated.

Axis 4 — Swarm

Highest risk — off by default

A private tracker deliberately isolates swarms. Cross-announce pierces that isolation, so it is gated three ways and ships disabled.

Mutualise seeders/leechers between instances, but only when all of:

  1. the tracker has TRACKER_FEDERATION_SWARM=true (off by default), and
  2. the peer link has the swarm scope (both directions), and
  3. the individual torrent is opted in (federate_swarm, toggled by the uploader or staff on the torrent page).

When enabled, the API pulls partner peers (signed) into a short-lived Redis cache (remote_peers:{infoHash}) and the Go tracker mixes them into the announce response. Exposure is minimal and hardened:

  • only ip/port/isSeeder leave — never peer_id, ipHash or a user;
  • private / loopback / link-local IPs are filtered out both when exposing and when ingesting;
  • a 60-min freshness window and a per-torrent cap apply.

Ratio integrity is preserved: each instance only ever counts its own announcing users. Remote peers are injected read-only into the peer list — they never enter ratio, Hit-and-Run or stats accounting.

Governance

From /admin/federation, per peer, the owner can:

  • Approve an inbound request (choosing scopes).
  • Suspend / Reactivate — pause exchange without deleting the link.
  • Block — refuse a peer.
  • Edit scopes — change what you share/accept after the fact.
  • Remove — delete the link entirely.

Status transitions are enforced by a central validator (a revoked link is terminal; a pending handshake must be approved or deleted, not patched). Any non-active status immediately makes every inbound S2S endpoint reject the peer.

Standards, and where this diverges

Federation reuses established formats where they fit, and says so where it does not:

  • Records are FEP-d8c8Torrent objects — the same type, @context and bt:infohash_v1 a generic fediverse consumer understands — extended with a trackarr: vocabulary served at /api/federation/context.
  • Proofs are W3C Data Integrity with the eddsa-jcs-2022 cryptosuite (a Recommendation as of May 2025): the signing input is SHA-256(proofConfig) ‖ SHA-256(document), each canonicalised with RFC 8785 (JCS). Content addresses are sha256:<hex> over the canonical bytes, excluding proof and id.
  • Identity keys are did:key (Ed25519, multicodec 0xed01). did:key has no rotation by construction — the identifier is the key — so succession and revocation are layered on top rather than assumed from the method.

Divergence from FEP-c390 (Identity Proofs), deliberate. A portable-identity assertion here is a signed Person record carrying alsoKnownAs + the evidence that proved each alias, reconciled and relayed like any other record. FEP-c390 instead attaches a VerifiableIdentityStatement to an actor's attachment, with alsoKnownAs naming the actor's own id. The two express the same fact — "these identifiers are one person" — but this design makes the assertion a first-class, relayable record (so it survives the origin instance disappearing, which is the case that matters) rather than an attachment resolved by fetching a live actor. Converging on the FEP-c390 shape for cross-fediverse interoperability is possible later; it is not done today, and this note is the honest statement of that.

Security

ConcernMeasure
AuthenticityEd25519 HTTP message signatures on every S2S call: x-trackarr-instance/date/digest/signature.
TamperingThe signature covers METHOD \n PATH \n DATE \n DIGEST; digest binds the exact body bytes.
Replay±5-min clock window plus a per-signature nonce cache (Redis SET NX).
IdentityinstanceId is the public-key fingerprint; the handshake rejects an id that doesn't match.
AuthorizationEvery inbound endpoint checks allow-list + status==='active' + the required scope, in one shared guard.
AbuseIP rate-limit and a per-instanceId rate-limit (can't be bypassed by rotating egress IPs).
SSRFAll outbound calls go through safeFetch (blocks loopback / private / link-local / metadata ranges, re-validates every redirect hop).
XSSRemote text is DOMPurify-sanitized; partner-supplied URLs are http(s)-only before any :href.
EconomyRemote catalogue is a read-only mirror; reputation is display-only; swarm peers never touch ratio.
SecretsThe instance private key is AES-GCM encrypted at rest.

Configuration

Federation introduces no required variables — it provisions its key on first enable.

VariableRead byDefaultPurpose
FEDERATION_SYNC_INTERVALapi900000 (15 min)Catalogue-sync cron period (ms). No-op while federation is disabled. The health page's "behind" threshold is 3 × this, floored at 15 min — lowering it to sync faster will not paint healthy peers amber.
FEDERATION_REQUIRE_AUDIENCEapifalseReject inbound S2S signatures — and relay countersignatures — that carry no audience binding. Turn on only once every partner runs a build that sends it.
TRACKER_FEDERATION_SWARMtrackerfalseMaster switch for swarm cross-announce on the Go tracker.
CHANNEL_ENCRYPTION_KEYapiEncrypts the instance private key at rest (falls back to NUXT_SESSION_SECRET).

Clocks must be in sync

Every S2S request carries a signed timestamp and is rejected outside a ±5-minute window. A partner whose clock drifts past that becomes unreachable in both directions, surfaced only as http 401 in its last error. Run NTP on every instance in a mesh.

Relay and public catalogue are one-way doors, and relay costs disk

Turning relay on retroactively pulls every partner's whole catalogue (their bytes, not just yours) and re-advertises it to your other partners — plan for the disk. Neither relay nor the public catalogue can un-publish what has already left: a partner that took a record in holds a signed copy, and a withdrawal is a request it may ignore.

Local two-instance testing

safeFetch blocks loopback and private ranges by design, so two instances on the same host/LAN can't federate over localhost/RFC-1918 addresses. Use public hostnames or set SAFE_FETCH_ALLOW_HOSTS to the peer hostnames.

Data model

All federation state lives in dedicated tables — the local economy schema is untouched.

federation_config        singleton: master switch, this instance's identity, default scopes
federation_peers         the allow-list: base_url, instance_id, public_key, status,
                         shares_with_them / accepts_from_them (jsonb scopes)
federation_sync_state    per-peer × resource sync cursor (catalogue watermark)
remote_torrents          read-only mirror of partner catalogues (never merged into `torrents`)
federated_follows        local user → remote username subscriptions
federated_identities     local user ↔ remote account links (pending / verified)
torrents.federate_swarm  per-torrent swarm opt-in flag

Swarm cross-announce uses no table — it's a Redis cache (remote_peers:{infoHash}) read by the Go tracker behind its flag.

Sharing data, not just metadata

Federation mirrors a partner's catalogue; on its own that only lets you see what exists elsewhere. Turning "I see it" into "I can act on it" — content addressing (BEP 52), Request here, cross-seed matching, mesh availability, and the credit settlement a future content relay needs — is its own guide: federation-data-sharing.md.

Pages at a glance

PageWhat
/admin/federationOwner console: master switch, identity, peers, governance.
/federatedBrowse/search the federated catalogue (Cache / Live).
/federated/:idA remote torrent's detail + live comments.
/forum/federatedAggregated partner forum topics.
/federated-identityManage your linked remote identities + imported reputation.

Released under the MIT License.