carlos / platform Public

docs: fleet demo path — placement at create, registry-backed channels, customer root

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 357d965063fd9c5f9b2d2f54edf8d65f78f1425d parent cc219e3
5 files changed, +43 −50
  • infra/dev/region-pi.md +22 −30
  • infra/modules/README.md +3 −4
  • infra/modules/region-remote-host/README.md +3 −4
  • infra/remote-host.md +9 −0
  • scripts/seed-pi-demo.sh +6 −12
diff --git a/infra/dev/region-pi.md b/infra/dev/region-pi.md
index e32af707e..e33d00c83 100644
--- a/infra/dev/region-pi.md
+++ b/infra/dev/region-pi.md
@@ -10,29 +10,27 @@ Graviton fleet runs. 32-bit resolves nothing.
Follow infra/remote-host.md with these dev-platform values:
-- Tofu: instantiate infra/modules/region-remote-host in the dev root
- (platform-infrastructure repo) as:
- module "region_pi" {
- source = "git::https://github.com/carlosframework/platform.git//infra/modules/region-remote-host"
- region_code = "pi"
- data_bucket_name = "carlos-dev-pi-640012953448"
- deployment_bucket_arn = "arn:aws:s3:::carlos-dev-640012953448"
- }
- Apply from the platform-infrastructure checkout (operator step — the
- root module is not in this repo).
-- /etc/carlos/credentials: the module's access key output (0600).
+- Tofu: the customer root lives in `paulca/pi-fleet` (private), consuming
+ `infra/modules/region-remote-host` by git URL. Apply from that repo; secret
+ via `tofu output -raw pi_secret_access_key` there.
+- /etc/carlos/credentials: the customer root's access key output (0600).
- /etc/carlos/host.env additions beyond the standard agent env:
AWS_SHARED_CREDENTIALS_FILE=/etc/carlos/credentials
- CARLOS_HIBERNATE_BUCKET=carlos-dev-pi-640012953448
+ CARLOS_HIBERNATE_BUCKET=pi-fleet-640012953448
+ CARLOS_FLEET=pi
+ CARLOS_REGION=fleet/pi
+ CARLOS_STATUS_PREFIX=fleets/pi/
STATUS_BOX_REGION="Pi · demo"
- CARLOS_TUNNEL_URL=https://<dev tunnel hostname>
- CARLOS_TUNNEL_TOKEN=<from the edge's CARLOS_TUNNEL_CLIENTS entry>
- CARLOS_TUNNEL_NAME=pi-box
-- Dev edge host.env additions:
- CARLOS_TUNNEL_HOST=<dev tunnel hostname, DNS → dev edge>
- CARLOS_TUNNEL_CLIENTS=pi-box=<token>
- (Restart the dev edge to pick these up. Mint the token with
- `openssl rand -hex 32`; it lives only in these two env files.)
+ CARLOS_TUNNEL_URL=https://tunnel.dev.carlosframework.com
+ CARLOS_TUNNEL_TOKEN=<from carlos fleets add-box pi pi-box — shown once>
+ CARLOS_TUNNEL_NAME=pi/pi-box
+ `CARLOS_FLEET_BOX` is optional and defaults from `CARLOS_TUNNEL_NAME`'s
+ label half.
+- Fleet registration: `carlos fleets create pi -bucket pi-fleet-640012953448`
+ (idempotent on the console) and `carlos fleets add-box pi pi-box` mint the
+ fleet and box token (token shown once; rotation is NOT revocation — a
+ compromised box needs detach). The dev edge picks up channels within ~2s,
+ no restart required.
- No live fallback: no other host gets IAM on the pi bucket. A dark Pi
shows UNREACHABLE in the fleet view and `carlos status -live`; instances
resume when it re-dials. This is the approved demo posture.
@@ -40,21 +38,15 @@ Follow infra/remote-host.md with these dev-platform values:
Seeding: scripts/seed-pi-demo.sh (batches of ≤10/day — console limiter;
re-running an already-seeded batch is safe but not free, since the limiter
counts attempts, not successes, so only re-run a batch that actually
-failed). Requires:
-
-- `CARLOS_PI_TUNNEL_NAME=pi-box` — the same channel name set as
- `CARLOS_TUNNEL_NAME` on the Pi and matched in the dev edge's
- `CARLOS_TUNNEL_CLIENTS=pi-box=<token>` entry above.
-- A carlos CLI build carrying PR #52's `apps create` verb — seeding is
- blocked until #52 merges and a release ships.
-- App hostnames on dev resolve as `<app>.dev.carlosframework.com` — that's
- what the script's echoed `<hostname-of-app>` means.
+failed). Requires a carlos CLI build carrying PR #52's `apps create` verb —
+seeding is blocked until #52 merges and a release ships. Apps are placed at
+create via `-place fleet/pi`.
Target: ≥5 resident + ≥50 parked.
## Demo acceptance (run these, tick them)
-- [ ] Pi appears in `carlos status -live` with metrics + tunnel=connected.
+- [ ] Console fleet page `/a/<sqid>/fleets/pi` shows `pi-box` with metrics + tunnel connected.
- [ ] A resident instance serves through the dev edge over the channel.
- [ ] A parked instance wakes on first request through the channel.
- [ ] Pull Wi-Fi, restore it: next request succeeds after re-dial
diff --git a/infra/modules/README.md b/infra/modules/README.md
index 4fecbcbda..5687b9b81 100644
--- a/infra/modules/README.md
+++ b/infra/modules/README.md
@@ -80,14 +80,13 @@ wants one.
## region-remote-host
-What an off-cloud CARLOS host (a Raspberry Pi today, tito's hq box later)
+What an off-cloud CARLOS host (customer fleets and tito's hq)
needs from AWS when there is no EC2 instance profile to lean on: a region
data bucket plus a scoped IAM user + access key, limited to exactly the
deployment bucket and this region's data bucket — the same
wrong-region-fails-at-IAM residency property `region-host` gives cloud
-hosts. Two consumers: the dev Raspberry Pi (`region_code = "pi"`) and
-tito's hq box (`region_code = "hq"`, later); the module is reusable as-is
-for both.
+hosts. Consumed by customer fleets (paulca/pi-fleet is the live example) and
+the platform's tito hq; the module is reusable as-is for both.
| Variable | Description |
|---|---|
diff --git a/infra/modules/region-remote-host/README.md b/infra/modules/region-remote-host/README.md
index 4f07c0abc..e68f31059 100644
--- a/infra/modules/region-remote-host/README.md
+++ b/infra/modules/region-remote-host/README.md
@@ -6,10 +6,9 @@ inline policy and access key, limited to exactly the deployment bucket and
this region's data bucket (wrong-region-fails-at-IAM, the same residency
property `region-host` gives cloud hosts).
-Two consumers: the dev Raspberry Pi (`region_code = "pi"`) landing in this
-PR, and tito's hq box (`region_code = "hq"`) in a later step of the
-remote-host plan. The module is reusable as-is for both — they differ only
-in account and variable values, not shape.
+Consumed by customer fleets (paulca/pi-fleet is the live example) and
+the platform's tito hq box. The module is reusable as-is for both — they
+differ only in account and variable values, not shape.
The S3 action list in the inline policy is not independently chosen: it is
copied verbatim from `infra/modules/region-host/main.tf`'s
diff --git a/infra/remote-host.md b/infra/remote-host.md
index 7c02f1dc1..e2319dc43 100644
--- a/infra/remote-host.md
+++ b/infra/remote-host.md
@@ -69,6 +69,15 @@ route first, then back it with the tunnel channel:
carlos add <h>
carlos route -host <h> -tunnel <name>
+## Fleet boxes (customer fleets)
+
+Fleet boxes use the same agent with the fleet env contract documented in
+`infra/dev/region-pi.md` (the worked example) and specified in
+`docs/superpowers/specs/2026-08-05-customer-fleets-design.md`. The `carlos add`
+and `carlos route -tunnel` ceremony documented above applies only to platform
+remote boxes (tito hq), NOT fleet boxes — fleet instances are placed at app
+create time and boxes auto-join fleet registries.
+
## Security note
In `-dev` (HTTP) edge mode the registration bearer token travels in clear
diff --git a/scripts/seed-pi-demo.sh b/scripts/seed-pi-demo.sh
index a46503622..47d1e12cb 100755
--- a/scripts/seed-pi-demo.sh
+++ b/scripts/seed-pi-demo.sh
@@ -15,9 +15,9 @@
# "that name is taken" (HTTP 409), and this script treats that refusal as
# success rather than a fresh error.
#
-# Requires a carlos CLI build carrying PR #52's `apps create` verb (API
-# mode ONLY — sign in first with `carlos auth login -console <url>`; direct
-# bucket mode has no claims table to write to and this verb refuses it).
+# Requires a carlos CLI build carrying PR #52's `apps create` verb with
+# `-place` flag (API mode ONLY — sign in first with `carlos auth login -console <url>`;
+# direct bucket mode has no claims table to write to and this verb refuses it).
# PR #52 is unmerged as of this writing (branch worktree-cli-app-create) —
# seeding cannot run until it lands. Note also: neither main nor PR #52
# has an `apps list`/`ls` verb over the console API, so idempotence here is
@@ -26,14 +26,13 @@ set -euo pipefail
START=${1:?usage: seed-pi-demo.sh <start-index> <count>}
COUNT=${2:?usage: seed-pi-demo.sh <start-index> <count>}
-TUNNEL_NAME=${CARLOS_PI_TUNNEL_NAME:?set CARLOS_PI_TUNNEL_NAME (the Pi tunnel channel name)}
if (( COUNT > 10 )); then
echo "warning: console API limits app creation to 10/address/day — run this in batches of <=10" >&2
fi
for i in $(seq "$START" $((START + COUNT - 1))); do
app=$(printf 'pi-demo-%03d' "$i")
- if out=$(carlos apps create -app "$app" 2>&1); then
+ if out=$(carlos apps create -app "$app" -place fleet/pi 2>&1); then
echo "created: $app"
elif [[ "$out" == *"that name is taken"* ]]; then
echo "exists: $app"
@@ -45,10 +44,5 @@ for i in $(seq "$START" $((START + COUNT - 1))); do
done
echo
-echo "Next, on the DEV EDGE box, add the route then back it with the Pi channel"
-echo "(carlos route repoints an EXISTING route — it does not create one):"
-echo " carlos add <hostname-of-app>"
-echo " carlos route -host <hostname-of-app> -tunnel ${TUNNEL_NAME}"
-echo "and on the PI, add the matching local routes (carlos add / adopt as usual)."
-echo "First 5 apps stay resident (no hibernate flag); the rest get the"
-echo "-hibernate flag on their Pi-local routes so they park."
+echo "Apps are fleet-placed at create. Instances land on the fleet's boxes"
+echo "automatically once the Pi is joined (runbook: infra/dev/region-pi.md)."