Docs: Postgres for self-hosting, key pinning and rotation, report digest, metrics
SQLite is documented as local development only, with no new features. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9473d304b4
commit
966c747881
+4
-2
@@ -54,7 +54,7 @@ FN_CONTACT_EMAIL=
|
||||
# FN_CONTACT_LEGAL=
|
||||
# FN_CONTACT_POSTMASTER=
|
||||
|
||||
# Where a report from inside the app is mailed. Needs the SMTP settings below.
|
||||
# Where the hourly digest of reports is mailed. Needs the SMTP settings below.
|
||||
# Blank, reports are still kept in the database and read with
|
||||
# Scripts/reports.sh, and the log says nobody was told. This is also the
|
||||
# fallback for FN_CONTACT_EMAIL when that is blank.
|
||||
@@ -184,7 +184,9 @@ FN_PUBLIC_HOST=
|
||||
FN_ROSTER_ON_SERVER=true
|
||||
|
||||
FN_FEDERATION=closed
|
||||
# Comma-separated peer hostnames, e.g.: relay.flamenet.io,relay.example.org
|
||||
# Comma-separated peers. Pin each one's federation key: host=key, or
|
||||
# host=key1|key2 during a rotation. A bare host is unpinned and logs a warning.
|
||||
# relay.flamenet.io=<base64 key>,relay.example.org=<base64 key>
|
||||
FN_FEDERATION_PEERS=
|
||||
|
||||
# This relay's federation signing identity, a 32-byte Ed25519 seed:
|
||||
|
||||
@@ -27,10 +27,13 @@ see `.env.example`.
|
||||
swift build -c release
|
||||
FN_ISSUER_PUBLIC_KEY=<base64 Ed25519 public key> \
|
||||
FN_RELAY_AUDIENCE=https://relay.flamenet.io \
|
||||
FN_DB=/var/lib/flamenet/relay.sqlite \
|
||||
FN_POSTGRES_URL=postgres://flamenet:<password>@localhost:5432/relay \
|
||||
.build/release/flamenet-relay
|
||||
```
|
||||
|
||||
Self-hosting requires Postgres. Without `FN_POSTGRES_URL` the relay uses a
|
||||
SQLite file, which is for local development only and receives no new features.
|
||||
|
||||
It refuses to start without an issuer key. There is no safe default for that
|
||||
value, and a relay that accepts unsigned tokens is worse than one that is down.
|
||||
|
||||
@@ -38,11 +41,14 @@ value, and a relay that accepts unsigned tokens is worse than one that is down.
|
||||
|---|---|---|
|
||||
| `FN_ISSUER_PUBLIC_KEY` | n/a | Required. Base64 Ed25519 public key of the token issuer. |
|
||||
| `FN_RELAY_AUDIENCE` | n/a | Required. The `aud` this relay accepts. |
|
||||
| `FN_DB` | `relay.sqlite` | SQLite file. |
|
||||
| `FN_POSTGRES_URL` | unset | Postgres. Required for self-hosting. |
|
||||
| `FN_DB` | `relay.sqlite` | SQLite file, used without `FN_POSTGRES_URL`. Local development only. |
|
||||
| `FN_HOST` / `FN_PORT` | `127.0.0.1` / `8080` | Bind address. |
|
||||
| `FN_WORKDIR` | inferred | Directory containing `Resources/` and `Public/`. Set it in a unit file. |
|
||||
| `FN_CLOCK_SKEW` | `60` | Seconds of tolerance on a proof timestamp. |
|
||||
| `FN_FEDERATION` | `closed` | `closed`, `allowlist` or `open`. Closed is a complete relay, not a limited one, and is what a self-hoster gets without configuring anything. An unrecognised value falls back to `closed` rather than refusing to boot, a typo in an optional variable should not stop a relay serving its own users. |
|
||||
| `FN_FEDERATION_PEERS` | empty | `host`, `host=key` or `host=key1\|key2`. A pinned peer publishing another key is refused. See `docs/CONFIGURATION.md` for rotation. |
|
||||
| `FN_METRICS_PORT` | unset | Prometheus `/metrics` on a second port, loopback by default. Off unless set. |
|
||||
| `FN_BUNDLE_RATE` / `FN_BUNDLE_RATE_TARGET` | `600` / `60` | Bundle fetches per hour, per requester and per pair. |
|
||||
| `FN_SEND_RATE` | `3000` | Envelopes per hour. |
|
||||
| `FN_DELIVERED_RETENTION_DAYS` | `1` | The backstop for a collected envelope the app never confirms. |
|
||||
|
||||
@@ -44,6 +44,11 @@ long, rendered from that instance's own configuration.
|
||||
throttling and sign-up limits apply to everyone at once.
|
||||
- Keep `FN_ISSUER_PRIVATE_KEY` and `FN_AT_REST_SECRET` out of the image and off
|
||||
shared disks. A leaked issuer key lets someone mint tokens for any account.
|
||||
- Pin every federation peer's key in `FN_FEDERATION_PEERS` (`host=key`).
|
||||
An unpinned peer is believed with whatever key its descriptor serves, and
|
||||
boot logs a warning for each one.
|
||||
- Keep `FN_METRICS_PORT` off the public network. The counters carry no ids,
|
||||
and they still describe your traffic.
|
||||
- Raise the file-descriptor limit (`ulimits: nofile: 65536`). Each held
|
||||
long-poll costs two; the default 1024 caps you near 450 open apps.
|
||||
- Back up the database *and* rehearse the restore. `docs/CAPACITY.md` has the
|
||||
|
||||
+54
-4
@@ -51,10 +51,14 @@ running one box either way.
|
||||
|
||||
## Storage
|
||||
|
||||
**A self-hosted relay needs Postgres.** SQLite is for local development only
|
||||
and receives no new features. Delete-on-ack already runs only on Postgres,
|
||||
because SQLite reuses row ids.
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_POSTGRES_URL` | unset | Set it and Postgres is used. |
|
||||
| `FN_DB` | `relay.sqlite` | SQLite file, used when the above is unset. **The schema is identical on both**: SQLite is not a degraded mode, and is the right choice for a relay serving a household or a company. |
|
||||
| `FN_POSTGRES_URL` | unset | Set it and Postgres is used. Required for any relay with real accounts. |
|
||||
| `FN_DB` | `relay.sqlite` | SQLite file, used when the above is unset. Local development only. |
|
||||
| `FN_ATTACHMENTS_DIR` | `attachments` | Blobs are files, not rows, in both modes. |
|
||||
| `FN_WORKDIR` | the process's own directory | Where every relative path above is resolved from. |
|
||||
| `FN_S3_ENDPOINT` | unset | Set it and attachments go to object storage instead of `FN_ATTACHMENTS_DIR`. |
|
||||
@@ -103,9 +107,28 @@ should stand alone without configuring anything.
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_FEDERATION` | `closed` | `closed`, `allowlist`, or `open`. An unrecognised value falls back to `closed` rather than refusing to boot, a typo in an optional variable should not stop a relay serving its own users. |
|
||||
| `FN_FEDERATION_PEERS` | empty | Comma-separated hostnames, used only under `allowlist`. Enforced **outbound as well as inbound**, from the same rule. Peers named here are fetched wherever they resolve: the outbound address guard, which refuses private and loopback addresses, is for names an attacker can choose under `open` federation, not for a name you typed and pinned by key. Three relays on one host resolve each other to private addresses, and would otherwise refuse every deposit between them as unreachable. |
|
||||
| `FN_FEDERATION_PEERS` | empty | Comma-separated peers, each `host`, `host=key` or `host=key1\|key2`. The key is the peer's federation public key, base64, as its `/.well-known/flamenet/relay` publishes it in `federationKey`. A pinned peer whose descriptor publishes any other key is refused with 401. A bare `host` accepts whatever key the descriptor publishes, and boot logs a warning naming it. The host list is used only under `allowlist`; pins apply under `allowlist` and `open`. Enforced **outbound as well as inbound**, from the same rule. Peers named here are fetched wherever they resolve: the outbound address guard, which refuses private and loopback addresses, is for names an attacker can choose under `open` federation, not for a name you typed and pinned by key. Three relays on one host resolve each other to private addresses, and would otherwise refuse every deposit between them as unreachable. |
|
||||
| `FN_FEDERATION_KEY` | unset | Base64 Ed25519 seed. **Required** when `FN_FEDERATION` is not `closed`: the relay refuses to boot otherwise, rather than starting unreachable and letting a stranger discover it. |
|
||||
|
||||
### Pinning and rotating a peer's key
|
||||
|
||||
Read the peer's key and pin it:
|
||||
|
||||
```bash
|
||||
curl -s https://peer.example/.well-known/flamenet/relay | jq -r .federationKey
|
||||
FN_FEDERATION_PEERS=peer.example=<that key>
|
||||
```
|
||||
|
||||
A peer rotating its key tells you the new public key first. Then:
|
||||
|
||||
1. Pin both, old first: `peer.example=<old>|<new>`. Restart. Both are accepted.
|
||||
2. The peer switches `FN_FEDERATION_KEY` to the new seed and restarts.
|
||||
3. Wait an hour: the relay caches a peer's descriptor for 3600 seconds.
|
||||
4. Drop the old key: `peer.example=<new>`. Restart.
|
||||
|
||||
Rotating your own key is the same steps from the other side: send each peer
|
||||
the new public key, wait until all of them pin both, then switch.
|
||||
|
||||
`FN_FEDERATION_KEY` is deliberately **not** the issuer key. A key that only ever
|
||||
needs to say "this relay said this" should not also be able to mint a token for
|
||||
any user on it.
|
||||
@@ -201,7 +224,8 @@ On. A person can report something to whoever runs the server, so blocking is not
|
||||
| Variable | Default | What it does |
|
||||
|---|---|---|
|
||||
| `FN_CONTENT_REPORTS` | on | Set to `false`, `0`, `no` or `off` to stop taking reports. Anything else, including a typo, leaves them on. |
|
||||
| `FN_REPORTS_TO` | unset | Where a filed report is mailed, through the SMTP settings. Unset, reports are kept in `content_reports` and read with `Scripts/reports.sh`, and the log says nobody was told. |
|
||||
| `FN_REPORTS_TO` | unset | Where the report digest is mailed, through the SMTP settings. One message lists every report filed since the last one, at most one an hour, checked every 5 minutes; an hour with no reports sends nothing. At most 200 reports per message, each note and copy cut at 1,000 characters. A digest that fails to send is retried with the same reports. The watermark is the `report_digests` table, so a restart neither loses nor repeats a report. Unset, reports are kept in `content_reports` and read with `Scripts/reports.sh`, and the log says nobody was told. |
|
||||
| `FN_REPORT_RATE` | `10`/hr | Reports one account may file per hour. Over it, the route answers 429 and nothing is stored. |
|
||||
| `FN_REPORT_RETENTION_DAYS` | `365` | How long a content report, and the plaintext copy the reporter chose to attach, is kept. Long enough to show a pattern, bounded so it is not forever. Shown on /metadata. |
|
||||
|
||||
Off is a legitimate answer, not a broken relay. If nobody is going to read them, collecting reports is a promise the server cannot keep, and saying so plainly is better than a queue nobody opens.
|
||||
@@ -327,6 +351,32 @@ service (`--all` adds retired ones) and `Scripts/bridges.sh retire <fingerprint>
|
||||
takes one out of service. A retired identity is never listed again. The script
|
||||
header shows how to list a bridge on another machine.
|
||||
|
||||
## Metrics
|
||||
|
||||
Off. Set `FN_METRICS_PORT` and the relay opens a second listener that answers
|
||||
`GET /metrics` in the Prometheus text format and nothing else. The public port
|
||||
never serves `/metrics`, and the relay refuses to boot when the two ports are
|
||||
the same.
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_METRICS_PORT` | unset (off) | Port for the metrics listener. |
|
||||
| `FN_METRICS_HOST` | `127.0.0.1` | Bind address. In Docker, set `0.0.0.0` so a scraper on the same network can reach it, and do not publish the port. |
|
||||
|
||||
| Metric | Type | Counts |
|
||||
|---|---|---|
|
||||
| `flamenet_held_polls` | gauge | Long-polls held open now. |
|
||||
| `flamenet_push_sent_total` | counter | APNs pushes Apple accepted. |
|
||||
| `flamenet_push_failed_total` | counter | APNs pushes refused, unreachable or not signed. |
|
||||
| `flamenet_push_dropped_total` | counter | Pushes dropped because the push queue was full. |
|
||||
| `flamenet_limiter_fail_open_total` | counter | Rate-limit checks allowed because Redis did not answer. |
|
||||
| `flamenet_envelopes_written_total` | counter | Envelopes committed. |
|
||||
| `flamenet_federation_deposits_in_total` | counter | Deposits accepted from peers. |
|
||||
| `flamenet_federation_deposits_out_total` | counter | Deposits a peer accepted from this relay. |
|
||||
|
||||
Counters start at zero with the process. No metric carries an id, a name or an
|
||||
address.
|
||||
|
||||
## Site copy
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|
||||
Reference in New Issue
Block a user