Merge origin/main
This commit is contained in:
@@ -33,8 +33,8 @@ value, and a relay that accepts unsigned tokens is worse than one that is down.
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_ISSUER_PUBLIC_KEY` | — | Required. Base64 Ed25519 public key of the token issuer. |
|
||||
| `FN_RELAY_AUDIENCE` | — | Required. The `aud` this relay accepts. |
|
||||
| `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_HOST` / `FN_PORT` | `127.0.0.1` / `8080` | Bind address. |
|
||||
| `FN_WORKDIR` | inferred | Directory containing `Resources/` and `Public/`. Set it in a unit file. |
|
||||
|
||||
@@ -47,21 +47,21 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Message someone on a different server</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Like email: your friend doesn’t have to be on the same server as you, and each server picks who it will exchange messages with. #if(iosLive):On Android and iPhone, #else:Out now on Android, and on iPhone as a TestFlight beta, #endif and you both need it, since it takes two sides to cross.</small></td>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Like email: your friend doesn’t have to be on the same server as you, and each server picks who it will exchange messages with. You both need it, since it takes two sides to cross.</small></td>
|
||||
<td><strong class="no">No</strong><br><small>Deliberately. Letting servers talk to each other means more companies learn who is talking to whom, which Signal has chosen not to do.</small></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">You keep your name if you move servers</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Your address comes from your passphrase, not from a number a server handed you. Sign in somewhere else with the same passphrase and you are still the same person to everyone who knows you. #if(iosLive):On Android and iPhone.#else:Out now on Android, and on iPhone as a TestFlight beta.#endif</small></td>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Your address comes from your passphrase, not from a number a server handed you. Sign in somewhere else with the same passphrase and you are still the same person to everyone who knows you.</small></td>
|
||||
<td><strong class="no">No</strong><br><small>Your account lives on Signal's servers.</small></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Messages can wait for you in more than one place</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Keep a mailbox on two servers, and losing one costs you neither your messages nor your address. Add the second <em>before</em> you need it. #if(iosLive):On Android and iPhone.#else:Out now on Android, and on iPhone as a TestFlight beta.#endif</small></td>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Keep a mailbox on two servers, and losing one costs you neither your messages nor your address. Add the second <em>before</em> you need it.</small></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
@@ -75,21 +75,21 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Safe from the computers that are coming, when a conversation starts</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Stops someone recording your messages today and reading them years from now, once the computers to do it exist. #if(iosLive):On Android and iPhone, #else:Out now on Android, and on iPhone as a TestFlight beta, #endif and it counts only once you both have it.</small></td>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Stops someone recording your messages today and reading them years from now, once the computers to do it exist. It counts only once you both have it.</small></td>
|
||||
<td><strong class="yes">Yes</strong><br><small>Yes, at the start of a conversation.</small></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="yes">Yes</strong><br><small>Yes. Apple has done this longest.</small></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">, and every message after that</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>The row above protects the start of a conversation; this carries the same protection through every message, with fresh maths each time rather than once at the beginning. #if(iosLive):On Android and iPhone, #else:Out now on Android, and on iPhone as a TestFlight beta, #endif and like everything here it counts only once you both have it.</small></td>
|
||||
<th scope="row">And every message after that</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>The row above protects the start of a conversation; this carries the same protection through every message, with fresh maths each time rather than once at the beginning. Like everything here, it counts only once you both have it.</small></td>
|
||||
<td><strong class="yes">Yes</strong><br><small>Signal added this in October 2025 and is rolling it out gradually. It works alongside what they already had rather than replacing it.</small></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="yes">Yes</strong><br><small>Yes. Apple has done this longest.</small></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Your app checks your contact is really them</th>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Your app checks, by itself and with nothing to switch on, that your contact’s key really is theirs. A server that goes quiet about a key it already vouched for is treated as caught rather than as having a bad day. #if(iosLive):On Android and iPhone.#else:Out now on Android, and on iPhone as a TestFlight beta.#endif</small></td>
|
||||
<td class="mine">#if(iosLive):<strong class="part">Both apps</strong>#else:<strong class="part">Android & iPhone beta</strong>#endif<br><small>Your app checks, by itself and with nothing to switch on, that your contact’s key really is theirs. A server that goes quiet about a key it already vouched for is treated as caught rather than as having a bad day.</small></td>
|
||||
<td><strong class="yes">Yes</strong></td>
|
||||
<td><strong class="no">No</strong></td>
|
||||
<td><strong class="yes">Yes</strong><br><small>Contact Key Verification, opt-in.</small></td>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- GENERATED by Scripts/build-doc-pages.sh from flamenet-e2e/README.md.
|
||||
Edits made HERE are deleted the next time that script runs.
|
||||
Change the markdown source and re-run it.
|
||||
source-sha256: 7f29c22485475af0 -->
|
||||
source-sha256: 1d0581b4b6b91fcb -->
|
||||
<article class="band doc doc-generated">
|
||||
<div class="wrap wrap-narrow">
|
||||
<h1 id="flamenet-e2e">flamenet-e2e</h1>
|
||||
@@ -195,13 +195,13 @@ alongside <code>test/relay.mjs</code>.</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ratchet</td>
|
||||
<td>,</td>
|
||||
<td>n/a</td>
|
||||
<td>unchanged</td>
|
||||
<td>every step mixes a fresh ML-KEM secret</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AEAD, safety numbers</td>
|
||||
<td>,</td>
|
||||
<td>n/a</td>
|
||||
<td>unchanged</td>
|
||||
<td>unchanged</td>
|
||||
</tr>
|
||||
|
||||
@@ -200,16 +200,17 @@
|
||||
<span class="eyebrow">Get it</span>
|
||||
<h2>Get <span class="hl">Flamenet Messenger</span></h2>
|
||||
<p class="band-lede">
|
||||
Android is ready to install today. The iPhone version is still being
|
||||
prepared, this page will say so the moment it opens.
|
||||
Android is ready to install today, and the iPhone app is open as a
|
||||
TestFlight beta.
|
||||
</p>
|
||||
<div class="platforms">
|
||||
<!-- Reads "Not open yet" until it is. Do not describe a submission that
|
||||
has not happened; see git history for why this wording is careful. -->
|
||||
<span class="platform platform-soon">
|
||||
<!-- A beta, not the App Store: the link can be withdrawn, and "out on
|
||||
iPhone" would overclaim. Same wording as /get, which this duplicates. -->
|
||||
<a class="platform" href="https://testflight.apple.com/join/SZF8uKU6"
|
||||
rel="noopener">
|
||||
<span class="platform-name">iPhone</span>
|
||||
<span class="platform-state">Not open yet</span>
|
||||
</span>
|
||||
<span class="platform-state">Join the TestFlight beta</span>
|
||||
</a>
|
||||
<a class="platform" href="/downloads/flamenet-messenger.apk" download>
|
||||
<span class="platform-name">Android</span>
|
||||
<!-- Filled in from downloads/android-latest.json, the same file the app
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- GENERATED by Scripts/build-doc-pages.sh from README.md.
|
||||
Edits made HERE are deleted the next time that script runs.
|
||||
Change the markdown source and re-run it.
|
||||
source-sha256: 2d2373097668df4f -->
|
||||
source-sha256: 73c2dc8d67e48bf3 -->
|
||||
<article class="band doc doc-generated">
|
||||
<div class="wrap wrap-narrow">
|
||||
<h1 id="flamenet-relay">flamenet-relay</h1>
|
||||
@@ -46,12 +46,12 @@ one that is down.</p>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>FN_ISSUER_PUBLIC_KEY</code></td>
|
||||
<td>—</td>
|
||||
<td>n/a</td>
|
||||
<td>Required. Base64 Ed25519 public key of the token issuer.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>FN_RELAY_AUDIENCE</code></td>
|
||||
<td>—</td>
|
||||
<td>n/a</td>
|
||||
<td>Required. The <code>aud</code> this relay accepts.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
+30
-26
@@ -3,7 +3,7 @@
|
||||
<!-- GENERATED by Scripts/build-doc-pages.sh from flamenet-e2e/docs/SPEC.md.
|
||||
Edits made HERE are deleted the next time that script runs.
|
||||
Change the markdown source and re-run it.
|
||||
source-sha256: 78979d93f61a4bf2 -->
|
||||
source-sha256: 822aed4d9c79bbbb -->
|
||||
<article class="band doc doc-generated">
|
||||
<div class="wrap wrap-narrow">
|
||||
<h1 id="flamenet-messenger-end-to-end-encryption-spec">Flamenet
|
||||
@@ -131,7 +131,7 @@ verification is anchored to.</li>
|
||||
<tr>
|
||||
<td><code>SPK_sig</code></td>
|
||||
<td>Ed25519 sig</td>
|
||||
<td>,</td>
|
||||
<td>n/a</td>
|
||||
<td><code>Ed25519_sign(IK_sig_priv, SPK_pub)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -418,11 +418,11 @@ receivers MUST ignore it unless the sender is a member of
|
||||
</ul>
|
||||
<p>The group <strong>roster</strong> lives in a server-side registry
|
||||
(§8.9) in plaintext, and that is a real leak (§22). The
|
||||
<strong>name</strong> does not: it is not stored in a column the relay
|
||||
never read, which made it the single most revealing string the server
|
||||
held for a field that bought it nothing. Members learn it from each
|
||||
other over the pairwise ratchets group traffic already uses, and hold it
|
||||
locally.</p>
|
||||
<strong>name</strong> does not, and no longer did as of 2026-08-30: it
|
||||
was stored in a column the relay never read, which made it the single
|
||||
most revealing string the server held for a field that bought it
|
||||
nothing. Members learn it from each other over the pairwise ratchets
|
||||
group traffic already uses, and hold it locally.</p>
|
||||
<p>Two deliveries carry it, and both are needed:</p>
|
||||
<ul>
|
||||
<li>an <strong>announcement</strong> at creation and on adding a member,
|
||||
@@ -1004,8 +1004,8 @@ paragraph previously said a group pin could not resolve, because a group
|
||||
transcript did not record a <code>mid</code> for each message. That
|
||||
stopped being true: <code>Session.sendGroup</code> mints a
|
||||
<code>mid</code> and records it, and <code>GroupPinResolvesTest</code>
|
||||
(Android) pins a message in a group thread and reads the message back
|
||||
out of the transcript by the id the pin names.</p>
|
||||
(Android, 2026-09-04) pins a message in a group thread and reads the
|
||||
message back out of the transcript by the id the pin names.</p>
|
||||
<p>The pin stores an id and never a copy of the text, so it follows an
|
||||
edit rather than quoting words that are no longer in the conversation,
|
||||
and it does not outlive the message being deleted. The same test covers
|
||||
@@ -1567,7 +1567,7 @@ message differently from a 1:1 message and cannot tell them apart.</p>
|
||||
the owner. Cap: <strong>32 members</strong> including the creator
|
||||
(fan-out cost is per-member; see §6.7).</p>
|
||||
<p>A <code>name</code> MAY be present and MUST be ignored: it is
|
||||
accepted only so clients written written before it keep working, and the
|
||||
accepted only so clients written before 2026-08-30 keep working, and the
|
||||
relay neither reads nor stores it. Clients from here on MUST omit the
|
||||
field and tell members the name over the ratchet (§6.7).</p>
|
||||
<p>Every member must be <strong>reachable</strong>: at least one
|
||||
@@ -1683,12 +1683,13 @@ client-side. Harness: <code>test/relay.mjs</code>, which drives the
|
||||
compiled Swift relay rather than a stand-in.</li>
|
||||
<li><strong>iOS ↔︎ live server</strong>, v1 only: a real
|
||||
<code>register → send → poll → decrypt</code> round trip against
|
||||
production.</li>
|
||||
production (2026-08-11).</li>
|
||||
<li>Each of the <strong>iOS</strong> and <strong>Web</strong> engines
|
||||
separately passes X3DH, Double Ratchet, out-of-order (skipped keys), GCM
|
||||
tamper rejection and safety numbers.</li>
|
||||
</ul>
|
||||
<h3 id="android-verified">Android, verified</h3>
|
||||
<h3 id="android-verified-as-of-2026-08-29">Android, verified as of
|
||||
2026-08-29</h3>
|
||||
<p>The Android engine exists and is exchanged with the web engine. It
|
||||
lives in <a
|
||||
href="https://gitlab.com/paulhitt/flamenet-messenger-android">flamenet-messenger-android</a>
|
||||
@@ -1713,7 +1714,7 @@ initiator. Verified by <code>test/interop-swift-kotlin.mjs</code>, the
|
||||
Swift driver compiled from the iOS app's own <code>Sources/E2E</code>,
|
||||
the Kotlin side driven through <code>session_*</code> so the negotiation
|
||||
decision belongs to <code>E2ESession</code> and not to the harness.
|
||||
<strong>77 assertions, 0 failures.</strong></p>
|
||||
<strong>77 assertions, 0 failures, re-run 2026-09-07.</strong></p>
|
||||
<p>The rule for this section: establish what has been run.
|
||||
<code>PQKEM.kt</code> being present is not evidence; the cross-engine
|
||||
run is. A spec that understates what exists sends an auditor away from a
|
||||
@@ -1721,9 +1722,10 @@ third of the client code, and one that overstates it sends them to code
|
||||
that does not do what it says.</p>
|
||||
<blockquote>
|
||||
<p>Do not infer interop from structural similarity. An earlier version
|
||||
of An engine can be structurally correct and still fail to talk to the
|
||||
server, when the two disagree about a wire format no component test
|
||||
exercises. Byte-identical constructions are necessary and nowhere near
|
||||
of this section did exactly that, and it was wrong for months: the iOS
|
||||
engine was structurally correct and still could not talk to the server,
|
||||
because the two disagreed about a wire format no component test
|
||||
exercised. Byte-identical constructions are necessary and nowhere near
|
||||
sufficient. What makes the Android claim above sayable is the fixture
|
||||
exchange, not the code reading correctly.</p>
|
||||
</blockquote>
|
||||
@@ -1835,7 +1837,7 @@ device</h3>
|
||||
<tr>
|
||||
<td><code>PQSPK_sig</code></td>
|
||||
<td>Ed25519 sig</td>
|
||||
<td>,</td>
|
||||
<td>n/a</td>
|
||||
<td>see below</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1847,7 +1849,7 @@ device</h3>
|
||||
<tr>
|
||||
<td><code>PQOPK_sig_i</code></td>
|
||||
<td>Ed25519 sig</td>
|
||||
<td>,</td>
|
||||
<td>n/a</td>
|
||||
<td>see below</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -2129,7 +2131,8 @@ round-trips.</li>
|
||||
<p>§10's warning applies with full force: <strong>do not write these
|
||||
claims into the spec until the runs have actually happened.</strong> v1
|
||||
asserted interop for months that was impossible.</p>
|
||||
<p><strong>Status</strong>: what has actually been run, and on what:</p>
|
||||
<p><strong>Status as of 2026-08-19</strong>: what has actually been run,
|
||||
and on what:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -2577,7 +2580,7 @@ in the wrong order (§13.2) derives a different root key with no error
|
||||
anywhere. Also pinned: a distinct ML-KEM key per step, so a reused one,
|
||||
which still decrypts, and silently gives up the healing §13.1 exists
|
||||
for, fails.</p>
|
||||
<p>That run is also what caught a defect in the shipped code. The engine
|
||||
<p>That run is also what found the shipped code was wrong. The engine
|
||||
was attaching <code>pqdh</code>/<code>pqct</code> to messages for any
|
||||
peer that published a PQ prekey, which every v2 device does. A build
|
||||
with that would have broken every conversation with the released 0.6
|
||||
@@ -2585,7 +2588,7 @@ client, different root key AND different AAD, so the first message never
|
||||
opens. Sixteen Kotlin unit tests did not catch it and could not: both
|
||||
sides of a self-test share the author's reading. Recorded because it is
|
||||
the argument for this table's whole discipline.</p>
|
||||
<p>Kotlin against Swift directly has been run:
|
||||
<p>Kotlin against Swift directly has now been run, as of 2026-08-31:
|
||||
<code>test/interop-swift-kotlin.mjs</code>, 22 assertions, both
|
||||
directions, no web engine anywhere in the path. This closes an inference
|
||||
that was never valid, two engines each agreeing with a third agree with
|
||||
@@ -3431,11 +3434,12 @@ every group over time.</p>
|
||||
<p>This is the largest remaining metadata leak in the protocol. Message
|
||||
<em>contents</em> in a group are end-to-end encrypted; <em>who is in
|
||||
it</em> is not protected at all.</p>
|
||||
<p>The group's <strong>name</strong> is not part of this leak: it is not
|
||||
stored on the relay at all (§6.7, §8.9). That was the cheap half, the
|
||||
name was a column nothing read, so removing it cost a compatibility
|
||||
placeholder and nothing else. The roster is the expensive half, and it
|
||||
is still open; the rest of this section is about that.</p>
|
||||
<p>The group's <strong>name</strong> was part of this leak and is not
|
||||
any more: as of 2026-08-30 it is not stored on the relay at all (§6.7,
|
||||
§8.9). That was the cheap half, the name was a column nothing read, so
|
||||
removing it cost a compatibility placeholder and nothing else. The
|
||||
roster is the expensive half, and it is still open; the rest of this
|
||||
section is about that.</p>
|
||||
<h3 id="222-what-it-would-take">22.2 What it would take</h3>
|
||||
<p>The same primitive as §21, applied to membership rather than
|
||||
sending:</p>
|
||||
|
||||
@@ -76,7 +76,6 @@ services:
|
||||
FN_RELAY_AUDIENCE: "${FN_PUBLIC_HOST:?set FN_PUBLIC_HOST in .env}"
|
||||
FN_ISSUER_PUBLIC_KEY: "${FN_ISSUER_PUBLIC_KEY:?generate an issuer keypair - see .env.example}"
|
||||
FN_ISSUER_PRIVATE_KEY: "${FN_ISSUER_PRIVATE_KEY:?generate an issuer keypair - see .env.example}"
|
||||
FN_AT_REST_SECRET: "${FN_AT_REST_SECRET:?set FN_AT_REST_SECRET in .env}"
|
||||
# Federation proper, see the Federation section of .env.example.
|
||||
FN_FEDERATION: "${FN_FEDERATION:-allowlist}"
|
||||
FN_FEDERATION_PEERS: "${FN_FEDERATION_PEERS:-}"
|
||||
|
||||
@@ -85,7 +85,6 @@ services:
|
||||
FN_RELAY_AUDIENCE: "relay.flamenet.io"
|
||||
FN_ISSUER_PUBLIC_KEY: "${FN_ISSUER_PUBLIC_KEY:?generate an issuer keypair - see .env.example}"
|
||||
FN_ISSUER_PRIVATE_KEY: "${FN_ISSUER_PRIVATE_KEY:?generate an issuer keypair - see .env.example}"
|
||||
FN_AT_REST_SECRET: "${FN_AT_REST_SECRET:?set FN_AT_REST_SECRET in .env}"
|
||||
# The shared FlameNet account service's signing key, when this relay
|
||||
# accepts FlameNet sessions. Empty on a self-hosted relay, which is its
|
||||
# own identity provider and registers no such route.
|
||||
|
||||
@@ -57,7 +57,6 @@ services:
|
||||
# every restart.
|
||||
FN_ISSUER_PUBLIC_KEY: "${FN_ISSUER_PUBLIC_KEY:?generate an issuer keypair - see .env.example}"
|
||||
FN_ISSUER_PRIVATE_KEY: "${FN_ISSUER_PRIVATE_KEY:?generate an issuer keypair - see .env.example}"
|
||||
FN_AT_REST_SECRET: "${FN_AT_REST_SECRET:?set FN_AT_REST_SECRET in .env}"
|
||||
# Donations are off unless both are set. Card details never reach this
|
||||
# service: the form posts an amount and the relay redirects to the
|
||||
# processor's own hosted checkout.
|
||||
|
||||
@@ -200,6 +200,53 @@ All optional. Each is absent rather than half-enabled when unconfigured.
|
||||
| `FN_DONATE_ACCOUNT` | The connected account to charge, if `FN_DONATE_SECRET` belongs to a Stripe *platform* rather than to the account meant to receive the money. Sent as `Stripe-Account`, which makes the payment a direct charge there. Blank charges whatever account the key itself belongs to, the right answer when you are self-hosting with your own key, and the wrong one with a platform key, where Stripe answers 200 and the money lands on the platform with nothing on the page looking wrong. With this set, the publishable key stays the **platform's**. |
|
||||
|
||||
|
||||
## iPhone push (APNs)
|
||||
|
||||
Only the iOS app needs this; Android is woken directly. All five must be set
|
||||
together, and the relay logs that push is disabled if any is missing.
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_APNS_KEY_P8` | unset | Path to the `.p8` signing key from Apple. Mount it as a secret; it is not a file to bake into an image. |
|
||||
| `FN_APNS_KEY_ID` | unset | The key's ten-character id. |
|
||||
| `FN_APNS_TEAM_ID` | unset | Your Apple developer team id. |
|
||||
| `FN_APNS_TOPIC` | `net.flamenet.messenger` | The app's bundle id. Change it if you ship your own build. |
|
||||
| `FN_APNS_ENV` | `sandbox` | `sandbox` or `production`. A token from a TestFlight or App Store build is meaningless to the sandbox host and the other way round, and APNs returns the same 400 either way. Getting this wrong means no iPhone is ever woken, so set it to `production` the moment you ship a build that is not from Xcode. |
|
||||
|
||||
## Behind a proxy
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_TRUSTED_PROXIES` | `0` | How many proxy hops to trust when reading `X-Forwarded-For`. Left at 0 the relay uses the socket address, which is your proxy, so every request looks like it comes from one IP and per-IP limits apply to everyone at once. Set it to the number of proxies in front of the relay, not higher: a client can forge the header for any hop you trust beyond the ones you actually run. |
|
||||
|
||||
## Pluggable transport (obfs4)
|
||||
|
||||
Reachability, not privacy: a second address that does not look like a relay,
|
||||
for networks that block the first. It is what `docker-compose.prod.yml` runs
|
||||
under the `obfs4` profile, and the variables are read by that compose file
|
||||
rather than by the relay.
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_OBFS4_PORT` | `8443` | Published on every interface, unlike the relay. A bridge only loopback can reach helps nobody; what makes that safe is that the protocol answers nobody who cannot complete a handshake. |
|
||||
| `FN_OBFS4_ADVERTISE` | unset | The public address to put in the bridge line handed to clients. A container cannot know its own, so without this the line says `<this-host>` and is not dialable. |
|
||||
| `FN_OBFS4_IMAGE` | `flamenet-obfs4bridge:prod` | Set it to an image you can pull. |
|
||||
| `FN_OBFS4_CONTEXT` | `../flamenet-elligator2` | Build context, a sibling checkout. That repository is private, so its registry image cannot be pulled anonymously. |
|
||||
| `FN_OBFS4_UPSTREAM` | `relay:8080` | Where the bridge forwards to. |
|
||||
|
||||
The bridge's state volume holds its identity key, which clients pin through the
|
||||
`cert` in the bridge line. Losing it invalidates every bridge line you have
|
||||
handed out.
|
||||
|
||||
## Site copy
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FN_IOS_LIVE` | `false` | Flips clauses on the public pages that describe iPhone availability. Turning it on states general availability, so it belongs on after an App Store release and not during a beta. |
|
||||
| `FN_DELETE_ON_ACK` | see Retention | Delete a delivered envelope the moment the recipient's app confirms it, rather than after the delivered-retention window. The metadata page reads this and describes whichever is true. |
|
||||
| `FN_DISPOSABLE_EMAIL_DOMAINS` | unset | Comma-separated domains refused at sign-up. Empty means none are. |
|
||||
| `FN_FEDERATION_SCREEN` | unset | Advisory IP screening for federated peers. It does not gate federation; the allowlist and the pinned key do. |
|
||||
|
||||
## Watching the key log (relay#32)
|
||||
|
||||
| Variable | Default | What it does |
|
||||
|
||||
Reference in New Issue
Block a user