Skip to content

SCRUM-242 — Deploy: secrets and root user bootstrap

Plan ref: OPS-3 (docs/11-admin-plane-plan.md), decision D6. Stacked on SCRUM-240. Needs the admin-auth image from SCRUM-208 (genkey -totp).

What exists

Piece Notes
generate-secrets.sh also creates deploy/secrets/admin_auth/ (0700) and root_password (32 chars, 0600) if missing; chowns both to uid 65532 when run as root, otherwise prints the exact sudo chown. Idempotent; valkey.conf is rewritten only when its content changes
up.sh after postgres + migrations: checks secrets/admin_auth is owned by 65532 (prints the sudo chown if not); genkey -totp if totp.key is missing; genkey (key file + DB row) if signing_key.pem is missing; bootstrap-root every run (idempotent)
rotate-root-password.sh new password (0600, owner kept), bootstrap-root -rotate (also revokes root's sessions), restores the previous file byte for byte if rotation fails
.gitignore deploy/.gitignore ignores secrets/, .env, valkey/valkey.conf
README "Root account" where the password lives, sudo cat over SSH, first sign-in at http://localhost:8090/admin/ through the tunnel, rotation; root is password-only by design (D5) and exists to create personal admin accounts

First run on a fresh VM: generate-secrets.sh → (sudo chown if it asks) → up.sh.

How to verify

cd deploy
sh scripts/test-generate-secrets.sh
sed 's/^\([A-Z_]*PASSWORD\)=$/\1=x/' .env.example > /tmp/t.env
docker compose --env-file /tmp/t.env -f compose.yaml config --quiet && sh scripts/check-compose.sh /tmp/t.env
git check-ignore -v deploy/secrets/admin_auth/root_password

Results at time of writing

  • test-generate-secrets.sh: PASS — files and modes created, a second run changes no checksum, mode or mtime, no secret value in the output.
  • compose config, check-compose, sh -n: pass; the secret path is git-ignored.
  • Not yet run end-to-end on a VM (no docker on the dev box; the shared host's cutover, SCRUM-244, needs the team's sign-off).

How it was built

DeepSeek run scoped (Landlock) to deploy (150 s, ~28k output tokens, reasoning effort low). Claude review added the ownership preflight in up.sh: without the sudo chown, genkey would have failed inside the container with a bare permission error.