Skip to content

Federation setup

This guide takes you from an empty keys directory to a working federated server. Read the Federation overview first for the trust model and endpoint surface.

  • Docker and Docker Compose (20.10+), Git, OpenSSL
  • Minimum: 2 CPU cores, 4 GB RAM, 20 GB disk; recommended: 4+ cores, 8+ GB RAM, 100+ GB disk
  • A public HTTPS URL for your server (production)
  1. Generate a server identity.

    Each federated server needs a unique Ed25519 keypair. federation-keygen generates one, encrypts it with your passphrase, and prints your server ID.

    Generate the server keypair
    mkdir -p keys && chmod 700 keys
    # Generate a strong passphrase and export it
    export ZENTALK_SERVER_KEY_PASSPHRASE="$(openssl rand -base64 32)"
    cd zentalk-api
    go run ./cmd/federation-keygen --key-path ../keys/server.key

    federation-keygen flags:

    Flag Default Description
    --key-path ./keys/server.key Where to save the encrypted keypair
    --passphrase-env ZENTALK_SERVER_KEY_PASSPHRASE Environment variable holding the passphrase
    --show-public-key false Also print the base64 public key

    The tool verifies the keypair loads back correctly. Record the printed server ID, back up the key file, and store the passphrase in a password manager or secret store — a lost keypair means a new server ID.

  2. Configure the environment.

    Set the federation variables on your API server:

    .env.federation
    ZENTALK_FEDERATION_ENABLED=true
    ZENTALK_SERVER_ID=<your-server-id>
    ZENTALK_SERVER_KEY_PATH=./keys/server.key
    ZENTALK_SERVER_KEY_PASSPHRASE=<your-passphrase>
    # Public URL — must be reachable by peer servers; HTTPS in production
    ZENTALK_PUBLIC_URL=https://zentalk.yourdomain.com
    # Peers to register with at startup
    ZENTALK_FEDERATION_BOOTSTRAP_URLS=https://bootstrap1.zentalk.chat,https://bootstrap2.zentalk.chat
    # Refuse plaintext federation traffic (required in production)
    ZENTALK_REQUIRE_HTTPS=true

    Full configuration reference:

    Variable Default Description
    ZENTALK_FEDERATION_ENABLED false Enable federation
    ZENTALK_SERVER_ID Your server ID (from step 1)
    ZENTALK_SERVER_KEY_PATH ./keys/server.key Path to the encrypted keypair
    ZENTALK_SERVER_KEY_PASSPHRASE Keypair passphrase
    ZENTALK_PUBLIC_URL This server’s public URL
    ZENTALK_FEDERATION_BOOTSTRAP_URLS Comma-separated peer/bootstrap URLs to register with at startup
    ZENTALK_FEDERATION_PEERS Per-peer Ed25519 public-key fingerprint pinning (documented in the zentalk-web repository, FEDERATION_SETUP.md; consumer code in review)
    ZENTALK_INBOX_PROCESSOR_INTERVAL 5s Inbox processing interval
    ZENTALK_OUTBOX_PROCESSOR_INTERVAL 10s Outbox retry interval
    ZENTALK_HEALTH_CHECK_INTERVAL 60s Peer health-check interval
    ZENTALK_MESSAGE_RETENTION 168h Federated message retention
    ZENTALK_MAX_INCOMING_MESSAGES_PER_SECOND 100 Incoming rate limit
    ZENTALK_MAX_OUTGOING_MESSAGES_PER_SECOND 100 Outgoing rate limit
    ZENTALK_REQUIRE_HTTPS false Require HTTPS for federation (set true in production)
    ZENTALK_ALLOWED_SERVER_IDS Comma-separated allowlist of server IDs
    ZENTALK_BLOCKED_SERVER_IDS Comma-separated blocklist of server IDs
  3. Exchange fingerprints and configure mTLS.

    Federation peers are pinned two ways, and both are exchanged out of band with the peer operator:

    • Ed25519 fingerprint pinning — share your server ID and public-key fingerprint with each peer; each side lists its peers in ZENTALK_FEDERATION_PEERS. Requests from unlisted or mismatched keys are rejected.
    • Mutual TLS — both servers present certificates from the shared federation PKI; TLS 1.3 is required and certificates hot-reload on rotation. Plaintext HTTP between federation peers is refused in production.

    Follow the PKI bootstrap runbook in the zentalk-web repository (docs/MTLS_BOOTSTRAP.md) to issue certificates; the CA helper scripts live in scripts/pki/ in the same repository.

  4. Start the server (or the two-server demo).

    For local testing, the repository ships a complete two-server federation environment — two full stacks (API server, relay, mesh node) plus a shared DHT bootstrap node and Redis:

    Two-server federation demo
    # Required secrets — the compose file refuses to start without them
    export ZENTALK_JWT_SECRET="$(openssl rand -base64 64)"
    export ZENTALK_SERVER_1_KEY_PASSPHRASE="$(openssl rand -base64 32)"
    export ZENTALK_SERVER_2_KEY_PASSPHRASE="$(openssl rand -base64 32)"
    docker compose -f docker-compose.federation.yml up -d
    docker compose -f docker-compose.federation.yml logs -f api-server-1

    Host ports in the demo:

    Service Server 1 Server 2 Shared
    Frontend 3000 3100
    API server 3001 3002
    Relay 9001 9002
    Relay metrics 9091 9092
    Mesh P2P 9101 9102
    Mesh Storage API 8081 8082
    DHT bootstrap 9000
    Redis 6379

    Each API server is configured with the other as its federation bootstrap (ZENTALK_FEDERATION_BOOTSTRAP_URLS), so they register with each other automatically at startup.

  5. Verify registration and health.

    Verify federation
    # Federation enabled, server ID loaded
    curl http://localhost:3001/api/federation/status
    # Peer reachable
    curl http://localhost:3001/api/federation/health

    /status should report "enabled": true with your server ID; once peers have discovered each other, the known-server count rises. In the demo, watch the logs of both API servers for message routing and delivery lines, then send a message between users registered on different servers.

Symptom Check
Federation does not initialize ZENTALK_FEDERATION_ENABLED=true set, key file exists at ZENTALK_SERVER_KEY_PATH, passphrase correct; grep API logs for federation
Cannot reach peers curl https://<peer>/api/federation/health, outbound HTTPS allowed by firewall, DNS resolves
Messages not delivering Peer health endpoint reachable, rate-limit errors in logs, outbox retry interval
“Replay attack detected” in logs Normal — duplicate nonces are rejected. If excessive, identify the misbehaving peer and add it to ZENTALK_BLOCKED_SERVER_IDS