Run a host of your own

Run a host of your own

The relay above dies with the terminal. A host is just a relay that never sleeps, so the difference is a service unit, a push allowlist and TLS — not different software. loot serve is in the CLI you already installed.

Start it once by hand

--allow takes a pubkey in either form you can actually hold: the OpenSSH line loot whoami --pubkey prints, or its 64-hex equivalent. Repeat it once per pusher.

loot serve --dir /opt/loot/relay --addr 127.0.0.1:4000 \
  --allow "$(loot whoami --pubkey)"

# in another terminal — the relay reports the lists it parsed:
curl -s http://127.0.0.1:4000/info

/info echoes your key back as 64 hex underallowed_pubkeys. That is the same key in the other encoding, and it is the form to paste into the unit file below — a systemdExecStart splits on whitespace, and the OpenSSH line contains spaces.

⚠ Pass --allow at least once. An empty push allowlist means an open relay: every validly signed push is accepted, and every read is served to anyone who can reach the relay, internal content included, because a relay holds the internal keys (ADR 0011). With a list, only listed keys may read as well as push. It opens the grant gate with it — the grant allowlist defaults to the push one, and --allow-grant replaces that default rather than adding to it. Open is the right posture for a throwaway on 127.0.0.1 and the wrong one for anything reachable; the startup banner names which posture you got, every time. Only omitting the flag opens the relay: an --allow whose key went missing — a truncated paste, an unfilled template variable — refuses to start rather than inverting what you asked for.

Make it survive a reboot

# /etc/systemd/system/loot-relay.service
[Unit]
Description=loot relay (zero-knowledge push/pull host)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/loot serve --dir /opt/loot/relay --addr 127.0.0.1:4000 --allow <64-hex>
Restart=on-failure
RestartSec=2
# The relay never needs to read outside its data dir; keep it contained.
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/loot/relay
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Then systemctl daemon-reload && systemctl enable --now loot-relay. This is the unit that runs relay.millerbyte.com, hardening included — the relay writes nothing outside its data dir, soProtectSystem=strict costs it nothing.

Bind to 127.0.0.1 and terminate TLS in front of it — nginx and Let’s Encrypt, with only nginx exposed. The relay speaks plain HTTP and should never be the public listener.

What you are signing up for

⚠ The store only grows, and has no ceiling. A relay never forgets a change, so everything it holds stays reachable — and nothing reachable may be reclaimed. The only drains are a gc over torn writes and a reap of undelivered grants, which is genuine garbage rather than history. A relay is measured, not capped (ADR 0048): watch the volume, because it will not level off on its own.

⚠ A relay is not blind to everything. It can never read restricted content, or embargoed content before its reveal — those keys are never sealed to it. It can read internal content: those keys ride along so that peers pulling from it get readable files at all (ADR 0011). Run a relay for people you would hand internal files to, or mark what matters restricted.

What this is not: a relay is not the forge. No accounts, no web UI, no browser read path, and no key retirement — a relay gates pushes and reads on its push allowlist alone, so rotating a key on this site does not stop that key pushing to somebody’s relay. Hosting on loot.millerbyte.com is invite-only by construction; this is the path that needs no invitation.