Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 60 additions & 7 deletions SELF_HOST.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Run the Dormouse server behind Tailscale
# Self-host the Dormouse server

> This is an assistant-run setup playbook. Start a fresh Claude instance in
> this repository and say: `read @SELF_HOST.md and walk me through it`.
Expand All @@ -9,9 +9,16 @@

This installs the Dormouse coordinating server on the user's own laptop,
reachable only from their tailnet at `https://<laptop>.<tailnet>.ts.net`. That
is the whole self-host story today. To keep the relay up while that machine
sleeps, run the same installer on an always-on tailnet box — see "Keeping the
relay up while the laptop sleeps".
is the whole *scripted* self-host story today. To keep the relay up while that
machine sleeps, run the same installer on an always-on tailnet box — see
"Keeping the relay up while the laptop sleeps".

**Tailscale is the front door this runbook installs, not a dependency.** Nothing
in the server or the Host is Tailscale-aware; what it supplies is a stable DNS
name, a certificate the phone already trusts, and a reverse proxy in front of
the loopback server. Another private network can supply the same, under the
contract in
[Bring your own private network](#bring-your-own-private-network).

The installer already exists — one idempotent command that ships in this
repository, in a macOS, a Windows and a Linux edition:
Expand Down Expand Up @@ -93,7 +100,10 @@ known:
- **A tailnet.** The user needs a Tailscale account with MagicDNS and HTTPS
certificates enabled, Tailscale running on this laptop, and Tailscale on the
phone that will run Pocket. A tailnet-only origin is not reachable merely
because the laptop is on the tailnet.
because the laptop is on the tailnet. All three installers refuse without the
Tailscale CLI; on another private network **never improvise around that
refusal** — read
[Bring your own private network](#bring-your-own-private-network) instead.
- **macOS, Windows or Linux.** Each installer refuses to run on the other
platforms. On a fourth OS, or on a Linux box without systemd, stop and design
the native service manager with the user rather than translating LaunchAgent,
Expand Down Expand Up @@ -134,8 +144,9 @@ known:

`standalone/scripts/build-sidecar-proxy.mjs` and
`vscode-ext/scripts/esbuild.mjs` bake that variable into their respective
Node Host bundles. The relay socket no longer lives in either webview, so
changing a webview CSP does not widen this allowlist.
Node Host bundles. A non-tailnet origin goes in whole, port included — a
source with no port matches 443 only. The relay socket no longer lives in
either webview, so changing a webview CSP does not widen this allowlist.

## What the installer does

Expand Down Expand Up @@ -669,6 +680,48 @@ which is the one thing the above does not give. That belongs with the
multi-tenant work in `docs/specs/server.md` `## Future`, not with a single-user
install, and is not designed here.

## Bring your own private network

A front door is configuration of the shipped server rather than a different
hosting model, so it needs no design: point `DORMOUSE_ORIGIN` at an `http(s)`
origin, build the Host with a matching `DORMOUSE_REMOTE_CONNECT_SRC`, and
WireGuard, a corporate VPN or a reverse proxy on a home LAN can front it.
Nothing checks any of the following for you.

- **A DNS name, never an IP address, and an origin of at most 103 characters.**
The WebAuthn `rpId` is the origin's hostname, and an IP literal is not a valid
RP ID. Nothing refuses one: the server boots and answers, and the failure
surfaces in the phone's browser at passkey registration. The length cap is the
pairing QR's and does fail at mint time (`docs/specs/server.md` → Setup tokens
and the pairing QR); it bounds the whole origin, so the scheme and any
non-default port come out of the same 103 — 95 characters of hostname under a
portless `https://`. `rpId` itself carries no port.
- **A certificate the phone already trusts.** Passkeys need a secure context.
Public trust means owning a domain and running ACME — usually DNS-01, since
the name resolves to a private address, so you hold a renewal credential the
Tailscale path never does. A private CA also works, but every phone must
install *and* fully trust the root before Pocket will load.
- **Reachability no wider than that network.** There is no generic equivalent of
the Funnel assertion, so a proxy that starts answering from the internet does
it silently — and `SECURITY.md` → "The setup password — accepted risk" is
premised on exactly that bound.
- **The invariants the installer would have held, held by you.** The loopback
bind and the durable origin are unchanged ([Installer contract](#invariants)),
but nothing pins the first, and nothing refuses to rewrite the second, so a
DHCP-dependent name is a re-enrollment waiting to happen.

Everything in "Definition of done" is then yours to reproduce by hand: no
`manage verify`, no release pointers, no rollback, and none of the `FAIL IF`
lines in `SECURITY.md` that `scripts/deploy-lint.mjs` enforces. The server holds
`state/` itself on unix (`0700`/`0600`); on Windows those modes are a no-op and
the installer's DACL was the only thing holding it, so there `state/` is yours
too (`SECURITY.md` → "Credentials at rest"). `config/`, `run/` and the service
registration are always yours.

A Tailscale-CLI-compatible control plane such as Headscale is untested here. The
open question is whether it satisfies the certificate `tailscale serve`
requests, which Serve asks for only after the release switch has landed.

## Installer contract (maintainers)

The runbook above is the operator half; this section is the *spec* for the
Expand Down
2 changes: 1 addition & 1 deletion scripts/spec-word-budgets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"AGENTS.md": 2890,
"SELF_HOST.md": 7450,
"SELF_HOST.md": 8000,
"docs/specs/alert.md": 7750,
"docs/specs/alert.rationale.md": 950,
"docs/specs/auto-update.md": 1500,
Expand Down