Files
esh-pfi-infrastructure/stacks/hrafn/ci
vh 11b9d1891e fix(hrafn-ci): make the deploy converge instead of accrete
The first CI run shipped clean but revealed a design gap in the playbook:
unpacking the context tarball in place overwrites tracked files and never
removes anything. Leftovers from the pre-CI hand-rsync (tests/, docs/,
ROADMAP.md, persistent-memory.md, CLAUDE.md, LICENSE) survived the deploy
and had to be cleaned off ana-docker by hand.

That is the same failure class that produced the mess in the first place:
a deploy that only ever adds cannot return the host to a known state.

- unpack to a staging dir, then rsync --delete onto the compose dir
- protect host-owned .env and .deployed by name
- add .env.example to the context tarball so converge does not delete it
- record in the workflow that the tar list is now AUTHORITATIVE: anything
  omitted is removed from the host on the next deploy

Re-validated with `elway --dry-run` (9 steps, 3 verify, parses clean).
Not yet in vh/hrafn -- infra-ops has no write access there, so this is
offered to the repo holder rather than pushed.
2026-08-22 15:31:50 -07:00
..

hrafn CI deploy — authored here, lands in vh/hrafn

These two files replace hrafn's hand-rsync deploy. They are authored and version-controlled here because infra-ops owns hrafn's uptime, but they belong in the vh/hrafn repo — infra-ops has no write access to it (claude-bot is not a collaborator), so they are handed to the repo holder rather than committed directly.

file here destination in vh/hrafn
playbooks-deploy.yaml playbooks/deploy.yaml
gitea-workflows-deploy.yaml .gitea/workflows/deploy.yaml

Keep this copy in sync if the deployed version changes, or delete it once infra-ops has write access to vh/hrafn and the repo copy is canonical.

What the change buys

The pre-CI shape was: rsync a working tree into /opt/docker/compose/hrafn/, then docker compose build && up. Two problems, both fixed here.

  1. No provenance. The image was always local/hrafn:v1, so nothing on the box could answer "what commit is running". The image is now tagged with the commit SHA, and /opt/docker/compose/hrafn/.deployed records the SHA and timestamp. Rollback becomes a retag.
  2. The whole repo lived in the compose directorytests/, docs/, ROADMAP.md, persistent-memory.md, CLAUDE.md. Only the build context ships now (Dockerfile, compose.yaml, pyproject.toml, README.md, src/).

Two design calls worth knowing

  • Tarball, not per-file upload steps. nevermore's playbook enumerates every source file as its own upload: step. That is explicit, but it fails open: add src/hrafn/newthing.py, forget the matching step, and the deploy silently ships without it. hrafn's build context travels as one archive so it cannot go partial.
  • .env is never deployed. It is host-owned, 0600, and holds the bearer token. The playbook refuses to run if it is missing or not 0600 — a guard added because the file arrived at 0644 on handoff.
  • The deploy converges, it does not accrete. The first version unpacked the tarball in place, which overwrote tracked files but never removed anything — so leftovers from the pre-CI hand-rsync (tests/, docs/, ROADMAP.md, persistent-memory.md, CLAUDE.md, LICENSE) survived the first CI deploy and had to be cleaned off the host by hand. Now the tarball unpacks to a staging dir and rsync --delete converges the compose directory onto it, so a stray file cannot outlive the next deploy. The consequence: the tar list in the workflow is authoritative — anything omitted from it is deleted from the host, except .env and .deployed.

Validation

The playbook parses and interpolates clean under elway's own parser:

scripts/elway ana-docker --playbook stacks/hrafn/ci/playbooks-deploy.yaml \
  --var hrafn_sha=abc123def456 --dry-run

That dry-run is worth running after any edit — it caught a real bug during authoring, where a comment containing a literal {{ ... }} identifier was picked up by elway's variable substitution and failed the run.

No new Actions secrets are required: DEPLOY_SSH_KEY and MGMT_REPO_TOKEN already exist at user scope on vh from the nevermore/task-board wiring.