# 🤖 `lchat try` — the one-command trial tunnel

`lchat try` is the zero-setup way to experience a real, self-run Eleven
space: download the binary, run one command, get a public
`https://try-<slug>.11ch.at` address serving a full instance from your own
machine — through NAT, with no inbound ports, no DNS record, no certificate,
and no root. Stop the command and the address retires; the space's data stays
in `./lchat-try.db`.

```sh
curl -LO https://github.com/elevenmessenger/releases/releases/download/server/lchat-linux-amd64
chmod +x lchat-linux-amd64
./lchat-linux-amd64 try
```

It prints two links: the space's address, and a one-time setup link that makes
you its admin (the same conversational setup a hosted space gets).

## How it works

Two halves, both in the single binary (`internal/tunnel/`):

- **The client** (`lchat try`) starts a normal local instance (a subprocess
  running the ordinary single-instance path against `lchat-try.db`), then
  dials out to the gateway over one **control WebSocket**
  (`wss://tunnel.11ch.at/api/tunnel/register`). The gateway assigns
  `try-<slug>.11ch.at` and sends `{"type":"ready","host":…}`. For every
  backend connection the public side needs, the gateway sends
  `{"type":"dial","id":…}` and the client dials back one **data WebSocket**
  (`/api/tunnel/conn/<id>`), which both ends treat as a plain byte pipe
  (`websocket.NetConn`) spliced to the local instance's TCP port. Ordinary
  HTTP/1.1 flows through it — including WebSocket upgrades, which is what
  makes the chat itself work through the tunnel unmodified.
- **The gateway** (`lchat tunnel serve`, unit `lchat-tunnel.service`) listens
  on `/run/lchat/tunnel.sock` behind the ordinary router. Each live tunnel is
  published as a plain registry route (`kind=service`, `host=try-…`,
  `socket=/run/lchat/tunnel.sock`), so the router needs no tunnel awareness at
  all: TLS comes from the existing `*.11ch.at` wildcard cert, and routing is
  the same host→socket lookup every instance uses. The router notices new
  routes via its registry watcher (a `PRAGMA data_version` poll every 2s), so
  the unprivileged gateway never needs rights to signal anything.

The E2EE story is unchanged: what crosses the tunnel is the same
already-ciphertext traffic the router fronts for every instance. The gateway
can see exactly what the router can see — nothing readable.

## Why single-label slugs

`try-x7k2m.11ch.at`, not `x7k2m.try.11ch.at`: TLS wildcards match one label,
and the fleet's wildcard cert is `*.11ch.at`. A nested scheme would need its
own wildcard cert and DNS; the prefix gets the same effect for free.

## Operations

- **Enable on a box:** `systemctl enable --now lchat-tunnel`, then route its
  control host once: `lchat add-service tunnel.11ch.at --socket
  /run/lchat/tunnel.sock`. (Bootstrap installs the unit everywhere; it's inert
  until enabled.)
- **Config** (`/data/tunnel.env`, optional): `LCHAT_TUNNEL_HOST`,
  `LCHAT_TUNNEL_DOMAIN`, `LCHAT_TUNNEL_SOCKET`, plus `--max` (default 50
  concurrent tunnels).
- **Guardrails:** 5 new tunnels per IP per hour, 50 concurrent tunnels, 32
  concurrent connections per tunnel, 24-hour lifetime, dial-back timeout 15s.
  And the door check: before a hostname goes public — and every 10 minutes
  after — the gateway probes the tunnelled backend through the tunnel
  (`GET /api/version`) and refuses anything that isn't an Eleven instance,
  so the trial domain never lends its name (or its TLS) to arbitrary
  content. This is a trial surface, not free hosting — the limits say so
  politely.
- **Kill switch:** `lchat tunnel kill try-<slug>.11ch.at` removes the route
  (the router stops serving it within seconds); `systemctl restart
  lchat-tunnel` additionally drops every live tunnel connection.
- **Cleanup is automatic:** a tunnel's route is removed when its client
  disconnects; on gateway restart, leftover `try-%` rows pointing at its
  socket are swept. `lchat instances` will show live trials as `service`
  rows — transient by design.
- **Self-hosters** can run their own gateway anywhere the router fronts a
  wildcard domain; `lchat try --gateway https://tunnel.your.domain` points the
  client at it.

## Honest limits

- A trial's address dies with the process (or at 24h). `lchat try` again
  mints a fresh address; the data in `lchat-try.db` persists, but members'
  saved links won't follow it. The printed copy says so — a keeper space
  should graduate to a real install (`docs/self-hosting.md`).
- The gateway is a relay for one machine's trial, not a CDN: throughput is
  whatever the trial machine's uplink gives it.
