Deploying
trueseal-relay ships as a single binary. Three things to get one running: a keypair, a config, and a storage backend.
Source: github.com/julianbonomini/trueseal-relay. Binary releases on the repo’s Releases page.
Keypair generation
The relay identity is a long-term X25519 keypair. Generate it once at setup:
trueseal-relay -genkey -keyout keypair.hex
That writes the hex-encoded private key to keypair.hex (mode 0600) and prints the public key to stdout:
keypair written to keypair.hex
relay public key (share with clients): a1b2c3d4...
Guard the private key. Anyone with it can impersonate your relay — devices verify the relay’s identity using this key on every handshake. The public key is safe to share around: bake it into your client binary, publish it on a landing page, drop it in your self-hosting README.
Note: Relay impersonation doesn’t compromise data security. Everything is end-to-end encrypted before it reaches the relay, so an impersonator gets ciphertext they can’t decrypt. The damage is limited to delivery — blobs pushed to a rogue relay never reach their recipients. No data is exposed.
Configuration
trueseal-relay reads config from a TOML file (default relay.toml) with optional environment variable overrides. Any TRUESEAL_RELAY_* env var takes precedence over the file.
Minimal relay.toml:
[relay]
keypair_path = "/etc/trueseal-relay/keypair.hex"
[store]
type = "sqlite"
sqlite_path = "/var/lib/trueseal-relay/inbox.db"
Full reference:
| Field | Env var | Default | Description |
|---|---|---|---|
relay.keypair_path | TRUESEAL_RELAY_KEYPAIR_PATH | — | Path to hex-encoded private key. Required. |
relay.listen_push | TRUESEAL_RELAY_LISTEN_PUSH | :7701 | Push Session listener address. |
relay.listen_receive | TRUESEAL_RELAY_LISTEN_RECEIVE | :7700 | Receive Session listener address. |
relay.ttl | TRUESEAL_RELAY_TTL | 720h (30 days) | Blob TTL before reaping. |
relay.reap_interval | TRUESEAL_RELAY_REAP_INTERVAL | 1h | How often the reaper runs. |
relay.max_envelope_bytes | TRUESEAL_RELAY_MAX_ENVELOPE_BYTES | 1048576 (1 MiB) | Max envelope size. Larger blobs are rejected with Error. |
store.type | TRUESEAL_RELAY_STORE_TYPE | sqlite | Storage backend: sqlite or postgres. |
store.sqlite_path | TRUESEAL_RELAY_STORE_SQLITE_PATH | — | SQLite database file path. Required when store.type = sqlite. |
Single-node deployment
This is the default and the recommended setup for self-hosted instances. SQLite, no external dependencies.
trueseal-relay -config relay.toml
Or Docker-friendly with no config file:
trueseal-relay -no-config
With -no-config, every setting has to come from environment variables.
Example docker-compose.yml:
services:
trueseal-relay:
image: trueseal-relay:latest
command: ["-no-config"]
environment:
TRUESEAL_RELAY_KEYPAIR_PATH: /secrets/keypair.hex
TRUESEAL_RELAY_STORE_SQLITE_PATH: /data/inbox.db
volumes:
- ./keypair.hex:/secrets/keypair.hex:ro
- relay-data:/data
ports:
- "7700:7700"
- "7701:7701"
volumes:
relay-data:
Graceful shutdown
trueseal-relay handles SIGINT and SIGTERM. On signal, it stops accepting new connections and waits up to 30 seconds for active sessions to drain cleanly before exiting.
Operational notes
- Firewall. Open TCP 7700 (receive) and 7701 (push) to the internet. Devices connect outbound; the relay never initiates connections.
- Disk. Size SQLite storage based on your expected group size, blob size, and TTL. A conservative rule of thumb:
devices × blobs_per_day × avg_blob_bytes × ttl_days. - Logs. trueseal-relay logs connection events and public keys only. It never logs envelope content. Don’t add content logging — doing so would break the zero-knowledge property the whole stack rests on.