Self-Hosting the Community Server
Swarmfile Server (Community) is the open-source server you run yourself (dual MIT/Apache-2.0; source and releases at swarmfile-server): a single-tenant service with SQLite for metadata and a local block directory (or an S3-compatible bucket) for file bytes. It speaks the same API as the hosted service, so the client tools that already talk to Swarmfile (the desktop app, the git-LFS agent, stock git-lfs, the migration tool, and the swarmfile:// git helper) can all point at yours.
Running it yourself means running it on a computer you trust: the server holds your data, and on a managed project it also holds the encryption key. What stays on the hosted service is everything around the core: access rules and sharing, anonymous drops, end-to-end encryption, previews, hosted CI, office caches, and billing.
Install#
Binary (macOS, Linux, and Windows, each on x64 and ARM). The install script downloads the release for your machine and verifies its checksum; on Windows it points you at the release .zip; unzip it and run swarmfile-server.exe:
curl -fsSL https://get.swarmfile.com/server | sh
The server release page also carries an Install this release command inside its Install & clone shelf; that one is pinned to the tag you are viewing, while the one-liner above follows the latest release.
Container. The release publishes a multi-arch image (ghcr.io/mirrexa/swarmfile-server, tagged latest and the release version), and the server project carries a compose.yaml that uses it:
docker compose up -d
docker compose exec server swarmfile-server admin --data-dir /data create-org --id my-org --name "My Org"
docker compose exec server swarmfile-server admin --data-dir /data create-project --org my-org --name "My Project"
docker compose exec server swarmfile-server admin --data-dir /data mint-key --org my-org --project <project-id> --name cli
docker compose exec server swarmfile-server admin --data-dir /data enable-git --org my-org --project <project-id> --threshold-bytes 50000000
export SWARMFILE_HUB_URL=http://127.0.0.1:8080
export SWARMFILE_API_KEY=sf_key_… # the key minted above
git clone swarmfile://my-org/<project-id>
The clone needs the standalone git-remote-swarmfile helper (install); the server's own GET /api/profile is served on http://127.0.0.1:8080/api/profile. Set SWARMFILE_SERVER_VERSION to pin the image, or build the source Dockerfile when you need a local build. /data is a named volume; back it up. The GHCR package must be public for an anonymous pull.
From source: cargo build --release -p swarmfile-server --features s3 in the published project (the s3 feature is optional but the release binaries and images include it).
First run#
swarmfile-server admin --data-dir ./data init
swarmfile-server admin --data-dir ./data create-org --id my-org --name "My Org"
swarmfile-server admin --data-dir ./data create-project --org my-org --name "My Project"
swarmfile-server admin --data-dir ./data mint-key --org my-org --project <project-id> --name desktop
swarmfile-server serve --data-dir ./data --listen 127.0.0.1:8080
create-project prints the project id, which every command that follows names it (--org and --project also accept a live or historical slug). mint-key prints a key (sf_key_…) exactly once; treat it like a password. A project's encryption tier is chosen when it is created: --tier plaintext (the default) stores bytes as written, --tier managed keeps a key on the server so authorized clients encrypt and decrypt through it.
Renaming keeps published addresses working:
swarmfile-server admin --data-dir ./data rename-project --org my-org --project <project-id-or-slug> --slug new-name
swarmfile-server admin --data-dir ./data rename-org --org my-org --slug new-org
admin list-aliases --org my-org prints the historical org and project slugs, so an operator can audit what old URLs still resolve. rename-project and rename-org also take --name. The old slug is recorded as a reserved alias, so an existing swarmfile://<host>/<org>/<project> URL or a git remote keeps resolving to the same project (the community server has no anonymous public pages or share links; those are hosted-only); a live slug always wins, and another project cannot create or rename onto a historical name. Renaming back to an old slug frees it again. (The community server has no project or org delete surface, so a historical name is never released by a delete.)
A serve process holds an exclusive lock on its data dir, so there is one server instance per data dir: a second serve refuses at startup (the admin CLI may run alongside it).
Serve plain HTTP and put a TLS reverse proxy in front (Caddy or nginx); the client tools expect https:// outside a purely local setup. Set SWARMFILE_SERVER_PUBLIC_URL to the public origin (for example https://files.example.com): the server's signed host identity (.well-known/swarmfile.json) signs that configured origin, never the request Host header. The document is origin-root by design (RFC 8615): a deployment served under a path prefix must map /.well-known/swarmfile.json at the root for verifiers to find it. Without it the identity document answers 503; swarmfile-verify-history --check-host-identity then fails closed.
Connect the tools#
-
Desktop app - install the self-hosted edition from your server's bundle (the
swarmfile-self-hosted-<version>-<platform>download on the swarmfile-server release page), then choose Connect to your server and enter the server's address and the key. If the key opens more than one project, the app asks which one. The drive, listing and saves work the same as against the hosted service. The first connect asks you to accept the edition's license (EULA.mdin the bundle). This edition does not update itself, and Reinstall andswarmfile-doctor --repairrefuse on it rather than replace it with the hosted app: upgrade by installing the next bundle, server first. Disconnect from server forgets the key on that computer. A key set by theSWARMFILE_API_KEYenvironment variable can't be changed from the app: Connect and Disconnect say so and leave it to whoever set the variable. An address set bySWARMFILE_HUB_URLis fixed the same way: Connect says so. The desktop app from swarmfile.com always connects to the hosted service. On Windows and Linux the two editions can't be installed together: each installer refuses while the other is installed, so uninstall one first. On macOS they install side by side as separate apps.Each bundle holds, for one platform, the server (
server/), the self-hosted desktop installer (desktop/), the standalone tools (tools/), the license, the third-party notices and aversions.jsonnaming every component's release. A bundle is published only after the community end-to-end tests have passed for that exact version set. -
git-LFS: a project serves git-LFS under its own path; set it in the repo's
.lfsconfig:git config -f .lfsconfig lfs.url https://your-server/orgs/my-org/lfs/<project-id>Stock
git-lfsworks for ordinary objects; the bundled agent handles very large ones. Native ingest is plaintext-only on the community server: on a managed project both are refused422, because the server stores only sealed blocks and will not write unsealed content at rest, so move large files through the mounted drive there. See Git-LFS for how the two fit together. -
git clone - turn on a project's git access (one-way, as on the hosted service), then clone over the helper:
swarmfile-server admin --data-dir ./data enable-git --org my-org --project <project-id-or-slug> --threshold-bytes 50000000 git clone swarmfile://your-server/my-org/<project-id>Set
--threshold-bytesabove the largest blob the repository already contains before the first enable: the value is frozen, and a push refuses a literal blob at or above it. The default is 1 MiB. Name the server (the host-qualified form) in anything you hand to a teammate: a relativeswarmfile://my-org/<project-id>resolves against whichever hub that machine is configured for. If the server is plain HTTP rather than behind the TLS proxy above, the client must list its authority inSWARMFILE_GIT_INSECURE_HOSTS(loopback needs no entry). A teammate whose Desktop App is connected to a different hub presents no session here: connect the app to this server, store the server key for this host withgit credential approve, or setSWARMFILE_API_KEYtogether withSWARMFILE_ALLOW_CROSS_HUB_KEY=1.An owner key can do the same over the API:
POST https://your-server/orgs/my-org/projects/<project-id>/metadata/git-view/enablewith the key as a bearer token.Cloning here always presents a key: the anonymous public clone is a hosted-service feature, and the community server's git surface is member-scoped (no public gates, no clone ticket).
See Cloning a Project with Git for what a clone contains and how push works. On the community server there is no background derive worker: a push confirms its commits itself as it lands, and if a push is interrupted, run
swarmfile git indexfrom a mounted drive to confirm the commits it left undecided. -
Existing data: bring a local tree or object store across with the standalone migration tool (swarmfile-migrate):
swarmfile-migrate --hub-url https://your-server --org-id my-org --project-id <project-id> --token <key> --source ./tree. See Migrating Existing Data. -
Place a single file: the standalone
swarmfile-seed(swarmfile-seed) puts one local file at a project path, no mount:swarmfile-seed --hub-url https://your-server --org-id my-org --project-id <project-id> --token <key> --file ./build/app.zip --path /Releases/app.zip. See Seeding a File Into a Project. -
Mirror a project:
swarmfile-mirror(swarmfile-mirror) copies a project (or a subtree) to another, e.g. off the hosted hub onto yours: give it a--source-*and a--dest-*hub/org/project and token. See Mirroring a Project Between Hubs. -
Check connectivity: the open
swarmfile-doctor(swarmfile-doctor) confirms DNS, TLS, your key, the block path and your server's descriptor before you rely on it. See Diagnosing Connectivity.
These open tools each install on their own (curl -fsSL https://get.swarmfile.com/{seed,mirror,doctor} | sh) and share the migration tool's pipeline: an API key, no engine, no mount.
Keys and access#
swarmfile-server admin --data-dir ./data list-keys
swarmfile-server admin --data-dir ./data revoke-key --key <id|name|prefix>
Each key is scoped to one project, or to the whole organization when it is minted without --project. A revoked key is refused on its next request; revoke and mint a replacement to rotate.
On a community server a key stands for a person, so give each teammate (and each CI job) their own. Every key has a role:
- owner (the default for
admin mint-key, and what keys minted before roles existed became): manages every key in the organization, and its commits are recorded as confirmed history straight away. - member (
admin mint-key --role member): manages only its own keys and the keys it minted, and its commits wait for another person's key to confirm them, the hosted service's two-person rule. Keys a member mints count as that member, so a CI key is a good member key: it cannot write confirmed history on its own.
Keys can also be managed without the admin CLI: swarmfile api-key list, swarmfile api-key create <name> and swarmfile api-key revoke <id> work against the server. A key created that way is a member key for one project; an owner key can create another owner key. A key scoped to one project, an owner's included, manages only that project's keys. Revoking a key that has been used also rotates the organization's swarm secret, like admin revoke-key.
Locks belong to the key that took them. A git push that changes a file locked by another key is refused (423 locked) until that lock is released; your own lock never blocks your own push, and only the key that took a lock (or an owner key) can release it. The same goes for a folder reservation: changelists and pushes that touch a folder another key has reserved are refused until the reservation ends.
Exposing it to your team#
The server has no browser UI: the tools are native apps and CLI, so an ordinary LAN/TLS setup needs nothing extra. A few knobs exist for the rest:
- Git surface. Git serves clone and push by default. A mirror or read-only replica can narrow it with
SWARMFILE_SERVER_GIT_MODE(orswarmfile-server serve --git-mode):readserves clones only (pushes, branch/tag writes and git-view enablement refuse with a typed403 git_read_only, while cloning an unsettled project still derives and mints its tip), andnoneturns git off entirely (403 git_disabledon every git route). It gates only the git surface; metadata, locks, LFS and blocks stay served, andGET /api/profilereports the mode asfeatures.git. - Browser clients. CORS is off by default. Set
SWARMFILE_SERVER_CORS_ORIGINSto a comma-separated list of the origins allowed to call the API from a browser (or*to allow any); without it a browser tool cannot read any response. - Addressing. Every route accepts the org and project slugs wherever it accepts ids (
https://your-server/orgs/my-org/projects/my-project/…), so links written with names keep working. Slugs are derived from the name at creation and can be set with--slug.
Peer-to-peer: the swarm#
The server also runs the swarm's rendezvous: desktop apps pointed at it join your organization's private peer-to-peer network, so teammates' machines stream file bytes to each other LAN-first: one machine fetches a block from the server and the rest of the team fetches it from that machine, and live edits gossip between them instead of waiting for a poll. This is on by default; nothing needs configuring on the clients. A client that can reach a teammate only through the public relay falls back to your server by default; SWARMFILE_ALLOW_RELAY_FETCH=1 on both serving and fetching engines opts into relayed block transfer (throttled).
SWARMFILE_SERVER_P2P (or serve --p2p) picks the posture:
| value | what it means |
|---|---|
all (default) | Full peer-to-peer, and the swarm may carry cleartext: plaintext-tier project blocks, and cleartext stored on managed projects (LFS objects), included. The swarm is exactly the machines holding authenticated keys to your server. Note the swarm is per-organization: any org key holder's machine can fetch any project's blocks by content id, without the per-project scoping the HTTP routes apply; choose sealed if that matters to you. |
sealed | Full peer-to-peer, but only sealed (managed-tier, encrypted) blocks travel between machines; cleartext is never served to peers, including the LFS objects managed projects store in the clear. (A machine may still cache cleartext blocks locally; it just won't hand them out.) Choose this if you want cleartext never to leave a machine over the swarm. |
off | No peer-to-peer: no discovery, no peers; every read comes from the server. |
The posture is read when an engine starts, on project switches, and by every running engine's periodic server check, about every 5 minutes for desktop apps and headless seeds/runners alike (set SWARMFILE_DOCTOR_PERIOD_SECS=0 on an engine to disable that check; then only a restart applies a change). A server that narrows its swarm therefore stops its running engines serving, without waiting for a restart. To cut a whole fleet's swarm instantly, rotate the secret and restart the clients.
How membership works: on the first request the server mints a per-organization swarm secret and hands it out only over the authenticated discovery route, so the swarm is exactly your team's engines. To remove a machine from the swarm, revoke its key; the server rotates the swarm secret in the same command:
swarmfile-server admin --data-dir ./data revoke-key --key <id|name|prefix>
Engines pick up the rotated secret within about 90 seconds; the replaced secret stays accepted during a short overlap so nobody drops mid-read. Until a rotation happens, a machine that still holds the current secret keeps swarm access: the rotation is the cutoff. Use --no-rotate for housekeeping revokes (a build-robot key that never joined the swarm); run admin rotate-swarm-key --org <id> by hand when you want a rotation without a revoke. admin swarm-status --org <id> prints the current generation, how long the old secret is still accepted, how many engines are live, and the cumulative peer-to-peer bytes the engines have reported.
What the swarm needs on the network: plain QUIC/UDP between your machines (engines bind a random local UDP port and advertise it through the server, so allow UDP egress), multicast for same-LAN discovery (mDNS), and, for teammates on different networks, the public iroh relay that lets two machines behind NAT meet. The relay is for meeting/address discovery: block bytes are not sent over it by default, so a relay-only peer also falls back to the server (SWARMFILE_ALLOW_RELAY_FETCH=1 opts in, throttled). Block UDP entirely and the swarm quietly degrades: every read falls back to the server. Pin the bind address with SWARMFILE_IROH_BIND_ADDR, advertise a stable one with SWARMFILE_IROH_PUBLIC_ADDR, or turn the swarm off on a single machine with SWARMFILE_HUB_ONLY=1. To check a machine, swarmfile doctor prints the server profile (look for the p2p= / p2pPlaintext= fields) and probes iroh and mDNS.
What the community server does not do here: it does not sign change attestations (that trust layer is hosted-only), and it has no per-node approval list: membership is the swarm secret itself.
History#
Every change becomes a commit, the same as on the hosted service. The desktop app commits each burst of saves on its own once the drive has been quiet for a few minutes. Changes that arrive any other way are committed by the server itself once the branch has been quiet for five minutes: an import with swarmfile-migrate or a file placed with swarmfile-seed. While a desktop app is in the middle of its own commit the server waits twice as long, so a desktop app that went offline before committing is covered after ten minutes. Those commits are authored server:autosave and read Saved N files; git push records your own commits as they are. Set SWARMFILE_SERVER_AUTOCOMMIT_IDLE_SECS to change the quiet window (in whole seconds, 1 or more), or to off to leave such changes uncommitted until the next push or save. Staged changelists work too: swarmfile changelist open, then save, then swarmfile commit -m (or changelist submit) lands the staged files as one commit, and nothing reaches the branch before that. swarmfile describe renames a commit afterwards. Open sessions and changelists with no activity for seven days are closed automatically, as on the hosted service. When two people save the same file at the same time, the version that reaches the server first is kept and the other save is recorded as a conflict, as on the hosted service. That person's drive keeps their version, readable but read-only, and swarmfile conflicts names the file. swarmfile conflicts resolve <path> settles it: --winner local keeps their version, --winner remote keeps the one on the server, --winner keep-both keeps both (theirs is saved alongside as name (conflict <user> <date>).ext), and --auto merges text files; the desktop app offers the same choices. Until it is settled, saves to that file are refused from every computer, and deleting the file settles it. A git push that changes the file is refused until it is settled too. Locking the file first (swarmfile lock, or Lock in the desktop app) avoids this: while the lock is held, saves from other computers are refused, and so is a git push from any other key, until it's unlocked.
Storage and backups#
By default everything lives under the data directory: system.db, one SQLite file per project, the block store, and history-signing.key - the server's own Ed25519 checkpoint key, created on first use with owner-only permissions (0600 on macOS/Linux; on Windows the file takes the data directory's inherited ACLs). The server signs a checkpoint of each branch's confirmed head with it, and swarmfile-verify-history checks those signatures from outside. The key is part of the server's identity: back it up with the directory (a restored server keeps verifying), and replacing it starts a new identity - checkpoints signed by the old key stop verifying, and the replacement is loaded on the next restart. To keep block bytes in an S3-compatible bucket instead (metadata stays local), set SWARMFILE_SERVER_S3_ENDPOINT and SWARMFILE_SERVER_S3_BUCKET, plus SWARMFILE_SERVER_S3_ACCESS_KEY_ID / SWARMFILE_SERVER_S3_SECRET_ACCESS_KEY (SWARMFILE_SERVER_S3_FORCE_PATH_STYLE defaults to path-style, so MinIO-style endpoints work as-is; set it to 0 only for virtual-host-style endpoints; SWARMFILE_SERVER_S3_REGION for buckets outside us-east-1; SWARMFILE_SERVER_S3_PREFIX namespaces keys in a shared bucket). This needs a server built with the s3 feature, which the release binaries and the container image are. The bucket must already exist. Back up the data directory (and the bucket, if you use one): that is the whole server.
Copying a project with exact history (optional)#
A same-server copy duplicates a project's database, hub-held key and block/LFS/git-object data into a new project. It is the one operation that preserves managed commit history exactly, because nothing is re-sealed. It is deliberately opt-in and the real copy runs with the server stopped (the command takes the serve lock and refuses while serve is running; VACUUM INTO would otherwise block writers). --dry-run does not create or copy project data or keys (normal startup schema maintenance may still run) and may run while serving. A detailed operator checklist ships with the server source:
# See exactly what would happen; writes nothing.
swarmfile-server admin --data-dir ./data copy-project \
--org my-org --project <project-id-or-slug> --name "Copy" --slug copy --dry-run
# The real copy; prints the new id, head, key and object counts.
swarmfile-server admin --data-dir ./data copy-project \
--org my-org --project <project-id-or-slug> --name "Copy" --slug copy
swarmfile-server admin --data-dir ./data mint-key --org my-org --project <new-id> --name copy
What it does and does not do:
- It copies the project DB (entries, branches, commits/claims, changesets), the hub-held project key (
managed), and thep/,lfs/ando/data prefixes. The commit DAG, head hash and content CIDs are byte-identical; the source is never modified. - It is a point-in-time copy; later writes to the source are not followed. It is not a mirror, does not cross servers or hubs, and is not a backup: it lives in the same data directory and block store, so a disk/store loss loses both copies. Transient locks, conflicts and checkouts are not carried; the copy opens clean.
- A crash during the copy can leave a marker-named destination (
__r219copy-…) and its data prefixes under a*.copy-<nonce>staging name. The source is unaffected; the nextservestart orcopy-projectremoves marker rows, staging prefixes and their DB/key automatically. Inspect the server log for the cleanup line before retrying. - It requires the filesystem block store; a configured S3 block store refuses before touching metadata (prefix copy is not implemented there yet).
- API keys are not copied (they are scoped and hashed); mint a destination key as above.
- The community server has no E2E tier (only
plaintext/managed); E2E key sharing is a hosted/cross-hub gap and is not copied by this command. - A pre-existing destination block prefix is refused (no silent merge), and a failed copy rolls back the destination metadata and copied prefixes.
Federating public projects#
Two community servers can serve public plaintext project data through an allowlist, without copying bytes: one server (the origin) exports a project to a named peer, and the other (the federator) pins that peer's signed host identity and allowlists the project. A federated clone then reads through the federator:
git clone swarmfile://server-a.example/fed/server-b/acme/widget
Set SWARMFILE_SERVER_PUBLIC_URL on both servers first (the signed origin-root /.well-known/swarmfile.json must be reachable, as described under Connect the tools).
On the origin (server-b), pin the federator and export the project - the project must be plaintext, its git access enabled, and the export named by the right slug:
swarmfile-server admin --data-dir ./data federation peer add --name server-a --origin https://server-a.example
swarmfile-server admin --data-dir ./data federation project allow --export --peer server-a --org acme --project widget
On the federator (server-a), pin the origin and allow the remote project. The command fetches the origin's signed identity and its public clone gate, so the origin must already be exported and serving:
swarmfile-server admin --data-dir ./data federation peer add --name server-b --origin https://server-b.example
swarmfile-server admin --data-dir ./data federation project allow --peer server-b --org acme --project widget
swarmfile-server admin --data-dir ./data federation project list
federation peer list shows the pinned origin and key id; federation project list shows each row's role (proxy on the federator, export on the origin), whether its actor signature verifies, whether the actor is local or imported from the peer's host-signed /.well-known/swarmfile-federation.json, and the export's effective auth mode. The import is explicit and two-step: export locally, let the federator allow the proxy, then re-run the export with --import-actor; the federator's advertisement must verify against its pinned host identity, and no private key is copied. peer disable stops all of a peer's proxying and exports immediately (the pin stays for audit); peer remove then drops a disabled peer and its allow rows (the recovery path after a permanent host-key rotation); project deny removes one allow row. The per-project actor key is generated at allow time (or imported from the peer's signed advertisement with --import-actor), advertises the opt-in record, and is separate from every content key (which stays null for plaintext) and from the server's history-signing key.
Signed peer reads (optional)#
An export is public-by-URL by default. To require a per-request signature from the federator's per-project actor key, run this on the origin after the actor is imported and the federator has allowed the proxy:
swarmfile-server admin --data-dir ./data federation project allow --export \
--require-actor --peer server-a --org acme --project widget
# later, back to anonymous public reads:
swarmfile-server admin --data-dir ./data federation project allow --export \
--allow-anonymous --peer server-a --org acme --project widget
- The requirement is effective only when every enabled export of the project requires an actor; any anonymous export keeps the project public (
federation project listshowseffectiveAuth). The origin cannot attribute an anonymous request to a peer, so this is deliberately weakest-export-wins. - The federator signs every proxied project read with the actor key (
swarmfile-fed-request-v1: method, origin path + query, body hash, timestamp, nonce, hop chain); it also signs_resolvewhen it has that project's proxy actor key, and a configured proxy row with a missing secret fails closed. The origin checks a ±60 s timestamp, a short nonce replay cache and a per-actor budget (SWARMFILE_SERVER_FEDERATION_ACTOR_RATE_PER_MIN, default 6,000/min). An unsigned, stale, replayed or wrongly signed request gets the same 404 as a project that is not exported. - A signed POST (the LFS batch and oid-coverage) hashes the exact forwarded body; an unsigned POST to a required project is refused before its body is read.
- The setup order is: export (anonymous) → federator
project allow→ origin--import-actor→ origin--require-actor. A federator re-allow after that signs its own gate check; a brand-new federator allow cannot, so switch the requirement on only after the first allow. - Direct public links stop working while every export requires an actor. That is an access boundary, not a secrecy one: the data is still public plaintext, and a peer that is allowed to read it can.
Public activity feed#
Every exported project (and, with signed reads, every allowlisted peer) can read a metadata-only activity feed:
GET /public/<org>/<project>/activity?limit=50&before=<seq>
It returns {org, project, generatedAt, branches, commits, nextBefore}; commit messages are capped at 4 KiB (messageTruncated says so) so one huge message cannot make the feed unbounded. branches are the live public refs with provisional hashes withheld; commits are recent committed changesets (seq, hash, status, treeCid, parents, message, committedAt, createdAt, branch) - no file contents and no author identities. limit is 1-200 (default 50); pass nextBefore as before to page older commits. It uses the same export/auth gate as the clone and is carried through a federator at /fed/<peer>/.../activity. It is not a public directory: the project URL is still unlisted.
What this does and does not do:
- Only public plaintext data. Managed/E2E projects, ACLs, member data and keys are never federated. git-LFS downloads are served through the federator; LFS uploads and locking stay member-only. Exporting makes the project's public reads answerable to anyone who knows the origin's
/public/…URL (unless every enabled export is actor-required) - the data is public and the URL is unlisted, not secret. Do not export private data. - The federator acts as a GET/HEAD proxy plus two read-only LFS POSTs (
git/oid-coverageand the download-onlygit/lfs/objects/batch; upload batches are refused) to the pinned origin's public reads. It refuses redirects, strips client credentials/cookies and hop-by-hop headers, caps response size and time (SWARMFILE_SERVER_FEDERATION_MAX_BYTES,SWARMFILE_SERVER_FEDERATION_TIMEOUT_SECS), rate-limits per peer/project (SWARMFILE_SERVER_FEDERATION_RATE_PER_MIN, default 600 per minute; raise it for large clones, which fetch many blocks) and writes one[federation]audit line per request. The origin has its own per-project budget (SWARMFILE_SERVER_FEDERATION_ORIGIN_RATE_PER_MIN, default 6,000/min, 429 +Retry-After: 60), which a direct anonymous reader also consumes; keep that in mind when sizing. It caches no bytes and never re-signs the origin's history. - The peer's signed identity is re-verified against the pin every
SWARMFILE_SERVER_FEDERATION_IDENTITY_TTL_SECS(default 60). Restoring the original key keeps serving; a replaced key fails closed until the operator pins it:federation peer disable,federation peer remove, thenfederation peer addwith the new statement. - A rename on the origin keeps old URLs working through its slug alias, but a federator allow row names a slug: using the new slug requires
federation project allowagain on the federator. The federated URL is explicit (/fed/<peer>/…); the federator never owns or aliases the origin's namespace.project denykeeps a proxy actor secret so a re-allow can sign a require-actor origin's gate; removing the peer clears it. - The federator's host-signed
/.well-known/swarmfile-federation.jsonlists every proxy row it federates (remote origin, org, project and the public actor key/signature). It is anonymous and signed, but it does publish the federation relationship; a peer-scoped advertisement is a Phase 4b item. Public project data and URLs are already public by design. - Mesh routes are experimental and disabled by default. A federator can point a peer at another federator's
/fedpath (A→B→C) only when every participating server setsSWARMFILE_SERVER_FEDERATION_MESH=1; without it, path-scoped peers, signed/fedhops and two-plus-hop reads are refused. The intermediate/fedis not a published endpoint. Do not share that URL, do not rely on anonymous reads through it, and expect a future per-reader tightening. The supported public contract is/public, plus your own federator's client-facing/fedURL. The intermediate's opt-in flags (allow_transitiveon the middle proxy,allow_reexporton the origin) are prerequisites for that future tightening, not a per-reader guarantee today. - This is not ForgeFed/ActivityPub, not private/E2E federation, and not a mirror. Hosted/cross-hub managed copy and E2E key sharing are owner-deferred and not built on either side. The open-source server's
test:federationlane proves the two-server slice end to end.
Upgrades#
Stop the server, replace the binary or image, start it again: the schema migrates on first use. If your team uses branches and is upgrading from a release before October 2026, run swarmfile cache resync once on each desktop app afterward: older servers sent other branches' changes to the default branch's sync, and the resync clears them from the app's cache. A data directory (or project) that a newer server has opened refuses to open on an older one, so downgrades need a backup rather than a rollback.
What the community server leaves to the hosted service#
Access rules and external collaborators, branch merges, the commit-level revert and restore operations, project management (archiving, retention and the other project settings beyond the administrator's admin create-project / admin rename-project; as in First run), end-to-end encryption (refused by design: the server can never hold those keys), previews, share links, anonymous drops (the homepage's public-link upload surface), RFIs, notification emails and webhooks, hosted CI, the Office caches feature set (the enrollment wizard, fleet dashboard and reporting; a bare seed node runs against this server too), multi-organization hosting, and billing. If your team needs those, the hosted service keeps them. The same clients work with both, so you can start yourself and move when it helps.
The clients say so rather than failing. Any request for one of these gets the same refusal from the server itself, so a command with no special handling still prints why it was refused. Where the desktop app reaches one of these (the Project settings panel, New project, the CI and notification settings, a share-link or RFI link, the plan usage card), it shows one line saying the feature isn't available on this server, with a link back to this section. The CLI refuses the same commands with the code not_supported_on_this_server and adds a hint: line pointing here (a hint field under --json); swarmfile project create, git import and clone --into name the admin create-project command. The server has no web dashboard either, and its profile says so, so the desktop app's links into one (Open in browser, Team, account settings, the full notification inbox, a file's web link, a release's public URL) explain that in one line instead of opening the hosted site, and the tray menu drops Open on the Web.