# 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 directory** — `tests/`, `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. ## Validation The playbook parses and interpolates clean under elway's own parser: ```bash 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.