# Deploying FRX Three roles: **member node** (`frxd serve`), **relay** (`frxd relay`), **registry** (`frxd registry`, run by the MA). TLS is only a concern for servers. Members connect outbound and need no domain, port, or certificate. ## Member node (zero TLS work) ``` frxd init --name alice --id alice.frx.federatedsearch.org \ --registry https://ma.federatedsearch.org/registry.json --ma-key \ --relay https://relay.federatedsearch.org --data-dir ./alice-data frxd add ~/documents --name docs --shared --exposure full frxd serve ``` The node connects outbound over HTTPS, verifies the registry with the pinned MA key, and holds an SSE stream per relay. Nothing inbound, no DNS, no certificates. Members at departmental level can start here. ## Joining the network (membership site + wizard) The apex (`federatedsearch.org`) proxies the same membership page; `ma.` is the service host. Identity registration stays with the MA; the wizard only consumes the credentials it issues. MA operator — run the signup site: ``` frxd registry --dir ./ma init --zone frx.federatedsearch.org frxd registry --dir ./ma serve --listen 127.0.0.1:7800 ``` (put Caddy in front for a real domain). The page at `/` collects the registration form (short name, organization details) and queues it for MA review — `frxd registry --dir applications` lists applications and `frxd registry --dir approve --registry-url ` creates the member, mints its account credential, and prints the credential block to hand over (`--class enrichment` at approval for derived-corpora members, which are metadata-only; the default `source` fits everyone else — membership itself has no roles or tiers). The token is reusable: it authorizes key enrollment for every node the member runs (`registry token ` mints an additional one; `registry revoke-token ` revokes all after a leak). A single-use 24h invite (`registry invite `) remains for constrained handoffs. The block is `id=... token=... registry=... ma_key=...`. Organization details (legal name, representative, contacts, payment) are recorded privately by the MA in `/applications.json` — contract data, never in the public signed snapshot. New member: ``` frxd --onboarding ``` The wizard asks for a config path, accepts the pasted credential block (or field-by-field entry), generates keypairs, enrolls the new key with the MA (redeeming the single-use invite token), verifies the signed registry snapshot, takes relay defaults from the registry, optionally shares a directory, writes the config, and offers to start serving. If enrollment is unavailable (file-path registry), it prints the exact `registry add` command the operator must run. ## Relay with TLS (one line) Run `frxd` on loopback and terminate TLS with Caddy: ``` caddy reverse-proxy --from relay.federatedsearch.org --to 127.0.0.1:7700 ``` Caddyfile equivalent (apex is the public front door, `ma.` the membership service, `relay.` the relay, `git.` the code host — all on one host): ``` federatedsearch.org { reverse_proxy 127.0.0.1:7800 } ma.federatedsearch.org { reverse_proxy 127.0.0.1:7800 } relay.federatedsearch.org { reverse_proxy 127.0.0.1:7700 } git.federatedsearch.org { reverse_proxy 127.0.0.1:3000 } ``` Relay command (peers and registry gated by the MA): ``` frxd relay --listen 127.0.0.1:7700 \ --url https://relay.federatedsearch.org \ --peer https://relay2.federatedsearch.org \ --registry https://ma.federatedsearch.org/registry.json --ma-key ``` No domain or open ports? Tunnel it: ``` frxd relay --listen 127.0.0.1:7700 --allow-insecure cloudflared tunnel --url http://127.0.0.1:7700 ``` `systemd` unit example: ```ini [Unit] Description=FRX relay After=network-online.target [Service] ExecStart=/usr/local/bin/frxd relay --listen 127.0.0.1:7700 \ --url https://relay.federatedsearch.org \ --registry https://ma.federatedsearch.org/registry.json --ma-key Restart=on-failure DynamicUser=yes NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes [Install] WantedBy=multi-user.target ``` The same unit shape works for `frxd serve` (add `--config /etc/frxd/frxd.toml`). ## Registry (MA) ``` frxd registry --dir /var/lib/frxd/registry init frxd registry --dir /var/lib/frxd/registry add alice.frx.federatedsearch.org --enc-key frxd registry --dir /var/lib/frxd/registry serve --listen 127.0.0.1:7800 ``` Put the same Caddy in front, or distribute `registry.json` out of band (it is signed, so the channel does not matter). The snapshot is versioned; nodes reject rollback and fail static during outages. ## Code hosting and downloads (Gitea) The public source and release binaries live at `git.federatedsearch.org` (Gitea), so the download step on the membership page stays on infrastructure the federation operates. One-time install on the server (root): ``` VER=1.27.3 curl -fsSLO https://dl.gitea.com/gitea/$VER/gitea-$VER-linux-amd64{,.sha256} sha256sum -c gitea-$VER-linux-amd64.sha256 install -m 0755 gitea-$VER-linux-amd64 /usr/local/bin/gitea adduser --system --shell /bin/bash --gecos 'Gitea' --home /home/git --group git mkdir -p /var/lib/gitea/{custom,data,log} /etc/gitea && chown -R git:git /var/lib/gitea /etc/gitea ``` `/etc/gitea/app.ini` essentials (rest defaults; secrets via `gitea generate secret`): ```ini WORK_PATH = /var/lib/gitea [database] DB_TYPE = sqlite3 PATH = /var/lib/gitea/data/gitea.db [server] DOMAIN = git.federatedsearch.org SSH_DOMAIN = git.federatedsearch.org ROOT_URL = https://git.federatedsearch.org/ HTTP_ADDR = 127.0.0.1 HTTP_PORT = 3000 [security] INSTALL_LOCK = true [service] DISABLE_REGISTRATION = true REQUIRE_SIGNIN_VIEW = false ``` systemd unit (`User=git`, `ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini`, `WorkingDirectory=/var/lib/gitea`), then `gitea migrate --config /etc/gitea/app.ini` as the `git` user and `systemctl enable --now gitea`. Git-over-SSH uses the host sshd via the `git` user's Gitea-managed `authorized_keys`; HTTPS pushes can use an access token instead. Admin bootstrap (as `git` user): ``` gitea admin user create --admin --username --email @federatedsearch.org --random-password --config /etc/gitea/app.ini gitea admin user generate-access-token -u -t bootstrap --scopes all --config /etc/gitea/app.ini ``` The org is `frx`, the repo `frxd` → clone URL `https://git.federatedsearch.org/frx/frxd.git`. Membership stays closed (MA-approved applications); repo reads are public. Publishing a release (from the checkout): ``` git tag v0.1.0 && git push gitea v0.1.0 # build static binaries (see next section), then attach via the API: curl -X POST https://git.federatedsearch.org/api/v1/repos/frx/frxd/releases \ -H "Authorization: token " -H 'content-type: application/json' \ -d '{"tag_name":"v0.1.0","name":"v0.1.0"}' curl -X POST https://git.federatedsearch.org/api/v1/repos/frx/frxd/releases//assets?name=frxd-linux-amd64 \ -H "Authorization: token " -F attachment=@frxd-linux-amd64 ``` Asset URLs follow `/frx/frxd/releases/download//` — the membership page pins those. Bump the page when a release changes. ## Private networks and custom CAs - `ca_cert = "/etc/ssl/private-ca.pem"` in `[node]`, or `--ca-cert` on the relay: adds a private/corporate root CA for relay and registry connections. - `allow_insecure = true` / `--allow-insecure`: explicit opt-in for plain `http://` on a VPN/LAN. Without it, non-loopback `http://` endpoints are refused at startup. - `http://127.0.0.1` is always allowed for development. ## Static binary ``` rustup target add x86_64-unknown-linux-musl cargo build --release --target x86_64-unknown-linux-musl ``` `[profile.release]` enables LTO and stripping. All dependencies are pure Rust, so the musl build has no system-library requirements. `ring` needs a musl C toolchain (`musl-tools`); without host sudo, build in a container instead: ``` docker run --rm -v "$PWD":/src -w /src rust:1-slim-bookworm bash -c \ "apt-get update -qq && apt-get install -y -qq musl-tools && rustup target add x86_64-unknown-linux-musl && cargo build --release --target x86_64-unknown-linux-musl" ``` Binaries land in `target/x86_64-unknown-linux-musl/release/`; rename to `frxd-linux-amd64` / `frx-linux-amd64` for release assets, with `sha256sum` sidecar files. ## What TLS does and does not cover Envelopes are signed and registry snapshots are MA-signed, so TLS is not what protects message authenticity or registry integrity. TLS protects traffic from network observers, authenticates the relay endpoint, and hides mailbox metadata. Unicast response bodies are already encrypted end-to-end to the recipient's X25519 key.