Decisions
Why the relay is hosted the way the runbook describes.
Where do public downloads come from?
From a public releases repo that CI publishes to, so-local/link-releases,
plus https://local.link/install.sh. Installs, the install script and
upgrades all need anonymous downloads, so servers and users need no
credentials to fetch a release.
Where does server state live when servers are disposable?
Everything on a server can be rebuilt from the repo except one small set of state files, and that set is backed up. A DigitalOcean reserved IP stays with the service, so replacing a server never touches DNS, takes one command and loses nothing.
Why are certificates part of the backed-up state?
Let's Encrypt allows about 50 new certificates a week for the relay's domain. Rebuilding a server with empty certificate storage would re-issue all of them, so Caddy's data goes into the snapshot and is restored with it.
Do deploys push or pull?
They pull. Each server polls the public releases repo every 5 minutes with a systemd timer, verifies the release's signature and checksum, swaps the binary and restarts, and rolls back if the health check fails. It works for any number of servers, needs no SSH secrets in CI, and a new server updates itself.
Why does bootstrap run from the operator's checkout?
Bootstrap runs as root and installs the key the updater trusts, so it
can't come from the releases repo it is there to check. The operator
pipes it over SSH from their own checkout, and it carries the release
key, kept the same as scripts/release/release-signers by a test, as
install.sh does.
What it installs, and what it keeps
It installs the release's deploy files only after
checking them against the signed SHA256SUMS, and installs its own copy
of the key, never the release's. Releases don't carry bootstrap.sh or
the key, so there is no copy to run by mistake. Re-running bootstrap from
a checkout without a leaked key removes that key from a server, and a
snapshot restored on the way never puts it back: bootstrap writes the key
after the import, and the import keeps the server's own.
Why does a server keep a relay version floor?
A signed release stays signed: whoever holds the releases repo could put
an older signed set back, with the units, updater and relay of that
time. The updater refuses a relay older than the one installed, but a
new server has none. So bootstrap takes the checkout's version
(--min-version, which create-droplet.sh passes), refuses a release
older than it, and leaves it in /etc/link/min-relay-version, which the
updater holds to with or without a relay installed. The version is
read from the relay binary itself, which the signed sums cover, so no
separate version file has to be signed.
How do backups leave the server?
link-state backup encrypts each snapshot with age to public keys held
off the server, then uploads it with curl --aws-sigv4 to any
S3-compatible bucket (Cloudflare R2, Backblaze B2, DigitalOcean Spaces,
AWS, or Google Cloud Storage through its S3 interoperability). A stolen
server can't read its own old backups, since it holds no private key.
Why not rclone or the bucket engine
rclone's crypt would need a password on the server. The backups don't
go through the bucket engine: every bucket a single relay needs already
speaks S3, and running rclone or lnk-bucket there would add a program
to the server and blur the rule that the server never depends on local
code.
How does a relay say what it couldn't hand on?
As a measure of Link's own (link.hooks.undelivered.sum), in the
shared link-measure-contract schema, which the server can use without depending
on local programs.
Whatever reads Link's measures reads the relay's with no word of its
own, and nothing is in it but numbers: never a webhook's path, sender
or body.
What it counts, and how it's sent
It counts only the relay's own 5xx answers, which a marker on every answer it makes tells from its tunnel's: a 4xx is the sender's, and the tunnel's service answering anything at all was handed on. The relay pushes a running total to an endpoint it's given, rather than being read, so a collector beside it takes it as it takes any OTLP, and a send that's lost is made up by the next, with nothing kept on a disk.
Why is the relay server a core and its features?
So any one feature can be added or deleted on its own. The core connects agents, names their tunnels, routes requests and keeps limits, naming no feature. Tunnel hosts, custom domains, machines and the webhooks tunnel are naming schemes behind one interface, and sign-in, passwords and the warning page are visitor checks behind another (Core and Features). A relay's role is only which of them it registers.
How a custom domain finds its tunnel
From the live connections, each time: a proven domain goes to its user's connected tunnel whose connection claimed it, the newest of two. The relay keeps no table of routes beside its connections, so a standby taking a tunnel over, or a connection closing, needs no step of the domain's own, and the core never names custom domains.
Why forms before a login share one pool
The warning page's form and the sign-in's callback are both bodies a browser posts before anyone has logged in. Both are read in the same timed pool, with its caps per visitor and per network, so neither can hold a connection open by sending slowly, and the pool is the core's whichever of the two a relay serves.