# LAN bootstrap: handing a fresh machine the binary

A machine with no spt on it is not yet a node. It has no network identity, so no
access grant can be checked for it and nothing on the node's own serving surface
can reach it. The LAN bootstrap listener exists for exactly that moment: a
second, opt-in server that hands a stranger on your LAN the spt binary and
nothing else.

<!-- [doc->REQ-WEB-LAN-BOOTSTRAP-LISTENER] -->
## It is a second listener, and it is off by default

The serving server on port `5474` is loopback-only and stays that way. The
bootstrap listener is a different server on a different port, `5470`, bound on
all interfaces, and it starts only when you say so:

```console
$ spt serve lan --bootstrap
LAN_BOOTSTRAP_UP: http://192.168.1.81:5470/install (port 5470)
sha256 x86_64-pc-windows-msvc  c1e0…4b7a
sha256 x86_64-unknown-linux-gnu  8f22…0d16
sha256 x86_64-unknown-linux-musl  a904…7c31
LAN-EXPOSED: anyone who can reach this socket may pull the binary until `spt serve lan --stop`.
```

Take it down when the bootstrap is finished:

```console
$ spt serve lan --stop
LAN_BOOTSTRAP_DOWN: the bootstrap listener is stopped
```

A bare `spt serve lan` reports the current state without changing it.

Three properties are worth stating plainly, because each one is a decision
rather than an accident:

- **While it is up, anyone who can reach the socket may pull the binary.** The
  listener has no membership concept and must never grow one — a fresh box has
  no identity to present. Your explicit start is the only gate, which is why
  the now-signal carries a standing `LAN_EXPOSED` line for the whole window.
- **It serves three routes and 404s everything else.** `/bin/<triple>/spt` (or
  `spt.exe` for a Windows triple), that file's `.release.json` sidecar, and
  `/install`. There is no fall-through into the serving router, so a routing
  mistake on this port cannot reach your docs or your served files.
- **It never comes back on its own.** No up-state is written to disk, so a
  daemon restart comes back with the listener down. Off-by-default is a
  property of every boot.

The port follows the usual layering: `SPT_LAN_BOOTSTRAP_PORT` beats
`daemon.json`'s `lan_bootstrap_port`, which beats the default `5470`. Setting
the port never starts the listener.

<!-- [doc->REQ-WEB-LAN-BOOTSTRAP-INTEGRITY] -->
## What it serves, and how you know the bytes are real

The machine at the other end is about to **execute what it downloads**, over
plain HTTP, on a network you can only mostly vouch for. So the bytes carry their
own provenance, in three parts that all have to hold.

**It serves the applied signed release set, or it serves nothing.** At start,
the listener checks that the running daemon's own binary is the host platform's
artifact in a signed release set, and that the node has actually *applied* that
set. If either fails it refuses to start and names why:

```console
$ spt serve lan --bootstrap
LAN_BOOTSTRAP_REFUSED:sha-mismatch
```

The three refusals are `unsigned-exe` (the running binary belongs to no signed
set this node trusts), `sha-mismatch` (it is not that set's host artifact — a
development build, or a hand-copied executable), and `set-not-applied` (the set
is staged but not yet running). There is no "serve it anyway, marked unsigned"
option, and a newer staged set is never served: a stranger receives what this
node *runs*, never what it is about to run.

**Each platform is checked again as it is served.** The listener holds several
platforms at once, and a triple's bytes go out only if they still hash to that
triple's digest in the same signed metadata. A platform that is missing or no
longer matches answers a 404 naming itself, and the others keep serving:

```text
LAN_BOOTSTRAP_TRIPLE_UNAVAILABLE:x86_64-unknown-linux-musl:missing
```

That isolation is deliberate: a missing Linux artifact must not block a
Windows-to-Windows bootstrap.

**You are the last check, and that is the point.** The signature and the sidecar
travel with the download, which makes them circular on their own — a tampered
binary would carry tampered trust roots. The line that closes the loop is the
one you read off two screens. The serving machine prints
`sha256 <triple> <hex>` at start; the install page bakes that same hex into the
command; and the puller prints the hash of what it actually downloaded *before*
it verifies anything. Two machines on one LAN and a human comparing two lines is
the only honest anchor a bootstrap without transport security can offer.

## On the fresh machine

Open `http://<serving-host>:5470/install`, pick your own platform's triple — the
page lists them and never guesses for you — and run what that page prints:

```console
$ curl -fsSLO http://192.168.1.81:5470/bin/x86_64-unknown-linux-gnu/spt
$ curl -fsSLO http://192.168.1.81:5470/bin/x86_64-unknown-linux-gnu/spt.release.json
$ chmod +x ./spt
$ ./spt install --expect-sha256 8f22…0d16 --release-json ./spt.release.json
INSTALL_SHA256: 8f22…0d16
INSTALL_VERIFIED: signed set 102 (0.68.0), key rel-primary-2026
INSTALL_OK: placed /home/you/.spt/bin/spt
```

`--expect-sha256` refuses anything but those exact bytes and writes nothing on a
mismatch. `--release-json` re-runs the signature and digest checks against the
keys built into the binary you just downloaded. Compare the `INSTALL_SHA256`
line against the serving machine's `sha256` line before you trust the result —
that comparison is the part no software can do for you.

From there the new node is ordinary: first run mints its identity, and it joins
a subnet the usual way.
