carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# Remote host runbook (software half)
2
3A CARLOS host is anything that can reach the deployment bucket. A remote
4host is OUTBOUND-ONLY: its whole network surface is outbound 443 to S3 and
5the edge tunnel. No inbound port, no SSH path required by the platform.
6
7Prereqs: systemd Debian-family OS, 64-bit (the artifact key is
8GOOS-GOARCH — linux-arm64 or linux-amd64; a 32-bit OS resolves nothing).
9
10## Steps
11
121. Provision the IAM principal + region data bucket
13 (`infra/modules/region-remote-host`). Scope: deployment bucket + this
14 host's own region bucket, and NOTHING else — wrong-region-fails-at-IAM
15 holds off-cloud too. A scoped access key is acceptable to start; IAM
16 Roles Anywhere is the long-term, hq-grade alternative (no long-lived
17 key on the box at all).
18
192. Stage `/etc/carlos/credentials`, owned by root, group `carlos`, mode 0640:
20
21 [default]
22 aws_access_key_id=...
23 aws_secret_access_key=...
24
25 Static keys only. `carlos bootstrap -offcloud` refuses to proceed if this file is missing or is not mode 0600 or 0640. It will name this path in the error. The agent will not use a file that holds anything else, such as a `credential_process` line or a second profile.
26
273. Stage `/etc/carlos/host.env` — the usual agent env, plus the
28 credentials pointer and the three tunnel variables. All three tunnel
29 variables are required together; if any is missing the dial-out loop
30 stays inert (no crash, no retry storm — it just never dials):
31
32 AWS_SHARED_CREDENTIALS_FILE=/etc/carlos/credentials
33 CARLOS_TUNNEL_URL=https://<edge's reserved tunnel hostname>
34 CARLOS_TUNNEL_TOKEN=<this host's token>
35 CARLOS_TUNNEL_NAME=<this host's channel name>
36
37 Do not set `AWS_PROFILE` or `AWS_DEFAULT_PROFILE` in `host.env`. The agent ignores both and logs an alert.
38
394. Stage the binary at `/opt/carlos/carlos` (matching GOOS-GOARCH).
40
415. Run: `carlos bootstrap -offcloud`
42
43 This does everything the normal `carlos bootstrap` does (units, dirs, service user, timers) and, before any of it, checks the credentials precondition from step 2. A failed check leaves the box untouched. On a live root, bootstrap and every agent start rewrite the credentials file as a new file owned by root, group `carlos`, mode 0640, keeping only the static keys. The service user can read it but not change it. A file that is already right is left alone.
44
456. `systemctl start carlos-edge.service`
46
477. Verify:
48 - `journalctl -u carlos-edge` shows "tunnel connected".
49 - The box appears in `carlos status -live` within one heartbeat
50 (5 minutes + jitter).
51
52## Edge side (once per edge box, not per remote host)
53
54Set in the edge box's `host.env`:
55
56 CARLOS_TUNNEL_HOST=<reserved hostname>
57 CARLOS_TUNNEL_CLIENTS=name=token,name2=token2,...
58
59`CARLOS_TUNNEL_HOST` must be a bare hostname — no port, no scheme. Each
60name in `CARLOS_TUNNEL_CLIENTS` must match `^[a-z0-9-]{1,32}$` or the edge
61refuses to boot.
62
63`carlos route` repoints an EXISTING route — it does not create one. Add the
64route first, then back it with the tunnel channel:
65
66 carlos add <h>
67 carlos route -host <h> -tunnel <name>
68
69## Fleet boxes (customer fleets)
70
71Fleet boxes use the same agent with the fleet env contract specified in the
72customer-fleets design spec (private `platform-infrastructure` repo, which
73also holds the worked Pi example). The `carlos add`
74and `carlos route -tunnel` ceremony documented above applies only to platform
75remote boxes (tito hq), NOT fleet boxes — fleet instances are placed at app
76create time and boxes auto-join fleet registries.
77
78## Security note
79
80In `-dev` (HTTP) edge mode the registration bearer token travels in clear
81text. Tunnel hosts must only ever dial a real-TLS edge in production —
82never point `CARLOS_TUNNEL_URL` at an HTTP edge outside a dev sandbox.
83
84## Known limitations
85
86- **Apps see the edge's IP, not the real client's.** A tunnel-backed request
87 crosses two reverse-proxy hops: client → edge (over the internet), then
88 edge → app (over the tunnel, on the remote host's own local edge). The
89 second hop re-stamps `X-Forwarded-For`/`X-Forwarded-Proto` from its own
90 immediate peer, so an app on a tunnel-backed host sees the edge's IP as
91 client, and `X-Forwarded-Proto: http` even though the original request was
92 TLS. This fails safe — client identity is dropped, never spoofed — but any
93 app on a tunnel-backed host that logs or gates on client IP will be wrong
94 today.
95- **No WebSockets to a tunnel-backed host.** The tunnel transport can't carry
96 a `Connection: Upgrade` handshake; every WebSocket attempt to an app on a
97 tunnel-backed host fails with a 502 (loud, not a hang). Plain HTTP request/
98 response is unaffected. RFC 8441 extended CONNECT is the likely future fix.
99
100## Related
101
102The hq PHYSICAL half (racking, power, network uplink for a region-hq box)
103lands separately in `infra/tito/region-hq.md`. For a concrete Pi
104instantiation of this runbook (values, seeding), see the private
105`platform-infrastructure` repo.
106