# 🤖 Relay mode — self-hosting with zero routing

`lchat relay` is the permanent sibling of `lchat try` (issue #738, Phase 1):
your instance runs on your own machine, dials out, and keeps a **stable
public hostname** — no port forward, no domain, no DNS, no certificate, no
root. Ships dormant: it only works against a gateway that offers a relay
domain, and the fleet gateway doesn't yet.

```sh
lchat relay                 # run a space from ./lchat-relay.db, keep it relayed
lchat relay --local 127.0.0.1:1747   # relay an instance you already run
```

## What makes a relay different from a trial

| | `lchat try` | `lchat relay` |
|---|---|---|
| Hostname | random, dies with the process | claimed by your keypair, permanent |
| Lifetime | 24 hours max | unlimited |
| Disconnect | address retires | address sleeps (503) for a grace window, then retires; reconnect revives it |
| Reconnect | new address | same address, automatic (backoff ≤ ~32s) |
| Admission | 5/IP/hour | same for *new* claims; reconnects are never rate-limited |

Everything else is identical: the door probe (only Eleven instances), the
per-tunnel connection cap, the re-probe every 10 minutes, and the E2EE
posture — the relay carries the same ciphertext any Eleven server carries.

## The identity

On first run the client writes an Ed25519 seed to `<db>.relay-id` (or
`lchat-relay.id` with `--local`, or `--identity <path>`). **The file is the
hostname**: claiming works by signing a gateway nonce, so whoever holds the
key holds the name. Back it up; lose it and the name is gone (the gateway
operator can free an orphaned claim by deleting its row in the claims db).

Wire shape (internal/tunnel): hello carries `mode:"relay"`; the gateway
answers `challenge{nonce}`; the client sends `claim{pubkey,sig}`. A hello
without `mode` is a trial and follows the original flow byte-for-byte, so
old clients are unaffected.

## Gateway operations

```sh
lchat tunnel serve --relay-domain <domain> [--claims /data/tunnel-claims.db] [--max-relays 200] [--claim-idle-days 45]
```

- **Use a dedicated domain**, not the trial/instance domain — relayed
  content shares its reputation. It needs a wildcard A record and a
  wildcard cert (same DNS-01 setup 11ch.at uses).
- Claims persist in the gateway's own sqlite (`relay_claims`: host, pubkey,
  timestamps). Routes are the ordinary registry `service` rows; on gateway
  restart stale `r-%` rows are swept and reappear as clients reconnect
  (within ~32s).
- Grace window: 15 minutes by default. A sleeping relay answers 503 with an
  honest "this space's machine is offline" message.
- `lchat tunnel kill r-….<domain>` withdraws the route; the claim stays, so
  the same keypair gets the same hostname back on reconnect. Add `--forget`
  to also delete the claim — the hostname returns to the pool and the
  keypair starts fresh. That's the kick for a test or abusive relay.
- Abandoned claims free themselves: an hourly janitor deletes claims whose
  owner hasn't connected for `--claim-idle-days` (default 45).
  Currently-connected relays are never swept, however old their claim.

## Honest limits (put these on any page that sells it)

- Your space's uptime is your machine's uptime. Lid closes, space naps.
- Throughput is your home uplink, relayed twice through the gateway box.
- TLS terminates at the gateway (hosted-grade transport trust; content is
  E2EE regardless). Phase 2 — SNI passthrough, where your machine holds its
  own certificate and the relay sees only opaque bytes — is designed in
  #738 and is the reason relay routes stay a distinct concept.

## Not in Phase 1 (see #738)

Memorable/chosen slugs, bring-your-own-domain (CNAME), SNI passthrough,
relay plans/billing, and gateway sharding.
