Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and `merge --ff-only` refuses while tracked paths are dirty. Discard is not available here (workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed. - `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new prose — the commit exists so the trunk merge can proceed. - `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat owns (AGENTS.md § Live-sync tiers). `ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the inert all-zero AWS example id — and they appear inside the two skill pages that teach that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal dies with this landing. No `--no-verify` was used: the gate ran and its verdict is recorded here. Refs #61
3.8 KiB
name, description
| name | description |
|---|---|
| rotate-fleet-service-host-secret | Rotate a Dank-fleet rootless-quadlet service's host-side secret (e.g. open-notebook OPEN_NOTEBOOK_PASSWORD) safely: confirm the deployer's secret seeder is idempotent first so a future deploy won't clobber the new value, change ONLY the target line while holding encryption keys byte-identical, then restart the user unit and prove the rotation (new token 200, old token 401, no token 401). Not for git/Gitea tokens or LLM endpoint secrets. |
Rotate a fleet service's host-side secret safely
Host secrets live OUTSIDE the repo, in /home/dank/Containers/<svc>/env (mode 600). The repo only holds the knobs (scripts/<svc>.env, secrets declared EMPTY). Changing a secret = edit the host file, never the repo.
1. First: prove the deployer won't clobber it (MANDATORY)
deploy-remote.sh seeds some secrets from dankclaw/.env. Confirm the seed fn is idempotent before editing, or a later deploy.sh overwrites your value. In scripts/deploy-remote.sh, seed_<svc>_env must look like open-notebook's:
for var in A B C D; do grep -q "^${var}=." "$dest" 2>/dev/null || missing=1; done
[[ "$missing" == 0 ]] && return 0 # all present → leaves the file untouched
if [[ ! -e "$dest" ]]; then ...seed from dankclaw/.env...; fi
If it returns-early on all-present, editing $dest directly is deploy-safe. If it seeds unconditionally, edit BOTH the host file and dankclaw/.env instead.
2. Read, then rewrite only the target line
read ssh://<host>/home/dank/Containers/<svc>/env (never print secrets to chat — they leak into the transcript). Then write ssh://... overwriting with all lines, changing only the one secret. Hold encryption keys byte-identical (e.g. OPEN_NOTEBOOK_ENCRYPTION_KEY): a DB-encrypted credential store is unreadable if that key changes. Then chmod 600.
- ssh://
reademits NO edit tag, so theedittool can't target it — usewrite(full overwrite) with the exact values from the read.
3. Restart the right unit
Rootless quadlets are USER units: ssh <host> systemctl --user restart <svc>.service. (systemctl --user works over plain ssh here — linger + bus are set up.) Restart only the app unit; DB password unchanged means the DB unit needs no restart.
4. Prove the rotation behaviorally (this is the smoke test)
Hit a protected API route three ways, expecting new=200 / old=401 / none=401. Pass the bearer via a mode-600 curl config + -K (never -H "Bearer …" — argv leak):
ssh <host> 'cfg=$(mktemp); chmod 600 "$cfg"
printf "header = \"Authorization: Bearer %s\"\n" "<new-or-old>" > "$cfg"
curl -s -o /dev/null -w "%{http_code}\n" -K "$cfg" http://127.0.0.1:5056/api/<protected>'
Gotchas that bit
- Health is on the API loopback port, not the web port. open-notebook:
/healthanswers on 127.0.0.1:5056 (the FastAPI), NOT:8502(Next.js, which 404s/health). Probing the wrong port reads as "service failed" when it's up andhealthy. Excluded/auth-free paths (/,/health,/docs,/openapi.json,/redoc) — everything else needs the bearer. OPEN_NOTEBOOK_PASSWORDunset = auth OFF for anything that can reach the port (other containers dial it by name ondankclaw_default), so it's mandatory, not cosmetic; deployer refuses to start on blank.- Browser/clients keep the old secret in localStorage → they 401 on next call and re-prompt. Expected.
- Harness command-parser gotcha: adjacent/interleaved single+double quotes in one bash line (
'...'"$x"'...') crashpi-natives:command: unterminated quote. Build a curl-configheader=line with the proven single-quoted printf (printf 'header = "Authorization: token %s"\n' "$tok") and put long bodies in a file via thewritetool +jq --rawfile, not a giant inline$(printf …).