carlos / platform Public

docs, lease: remote-host runbook for root:carlos 0640 credentials and the fresh-inode repair; the off-cloud session-token error (#267, copy approved by Paul 2026-10-10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev ea52e1b09a614e7931727902e6b6a39fc7650dcc parent d905a67
2 files changed, +6 −9
  • infra/remote-host.md +5 −8
  • internal/lease/endpoint.go +1 −1

CI passed — run details

diff --git a/infra/remote-host.md b/infra/remote-host.md
index f08826c0a..dfe9fed54 100644
--- a/infra/remote-host.md
+++ b/infra/remote-host.md
@@ -16,14 +16,13 @@ GOOS-GOARCH — linux-arm64 or linux-amd64; a 32-bit OS resolves nothing).
Roles Anywhere is the long-term, hq-grade alternative (no long-lived
key on the box at all).
-2. Stage `/etc/carlos/credentials` (mode 0600):
+2. Stage `/etc/carlos/credentials`, owned by root, group `carlos`, mode 0640:
[default]
aws_access_key_id=...
aws_secret_access_key=...
- `carlos bootstrap -offcloud` refuses to proceed if this file is
- missing or not exactly 0600 — it will name this path in the error.
+ 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.
3. Stage `/etc/carlos/host.env` — the usual agent env, plus the
credentials pointer and the three tunnel variables. All three tunnel
@@ -35,15 +34,13 @@ GOOS-GOARCH — linux-arm64 or linux-amd64; a 32-bit OS resolves nothing).
CARLOS_TUNNEL_TOKEN=<this host's token>
CARLOS_TUNNEL_NAME=<this host's channel name>
+ Do not set `AWS_PROFILE` or `AWS_DEFAULT_PROFILE` in `host.env`. The agent ignores both and logs an alert.
+
4. Stage the binary at `/opt/carlos/carlos` (matching GOOS-GOARCH).
5. Run: `carlos bootstrap -offcloud`
- 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 it also chowns the credentials file to the service user
- (still mode 0600).
+ 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.
6. `systemctl start carlos-edge.service`
diff --git a/internal/lease/endpoint.go b/internal/lease/endpoint.go
index 37deb79a4..4ec0156a9 100644
--- a/internal/lease/endpoint.go
+++ b/internal/lease/endpoint.go
@@ -125,7 +125,7 @@ func newReplicaClient(bucket, prefix, region string) (*lss3.ReplicaClient, error
return newReplicaClientForUID(bucket, prefix, region, os.Geteuid(), rootaws.OffcloudCredentials)
}
-var errRootReplicaSessionToken = errors.ErrUnsupported
+var errRootReplicaSessionToken = errors.New("root Litestream cannot use a session token from /etc/carlos/credentials. set the keys in the environment instead, or use an EC2 instance profile")
func newReplicaClientForUID(bucket, prefix, region string, euid int, offcloud func() (aws.Credentials, error)) (*lss3.ReplicaClient, error) {
c := lss3.NewReplicaClient()