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.
This commit is contained in:
@@ -38,6 +38,15 @@ then `docker compose build && up`. Two problems, both fixed here.
|
|||||||
- **`.env` is never deployed.** It is host-owned, `0600`, and holds the
|
- **`.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`
|
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.
|
— 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
|
## Validation
|
||||||
|
|
||||||
|
|||||||
@@ -67,14 +67,19 @@ jobs:
|
|||||||
chmod 600 ~/.ssh/config
|
chmod 600 ~/.ssh/config
|
||||||
|
|
||||||
- name: Build the deploy context
|
- name: Build the deploy context
|
||||||
# Only what the Dockerfile actually consumes, plus compose.yaml.
|
# Only what the Dockerfile actually consumes, plus compose.yaml
|
||||||
# Deliberately excludes tests/, docs/, persistent-memory.md,
|
# and .env.example. Deliberately excludes tests/, docs/,
|
||||||
# ROADMAP.md, CLAUDE.md, LICENSE and .env — none of them belong
|
# persistent-memory.md, ROADMAP.md, CLAUDE.md, LICENSE and .env —
|
||||||
# in a production compose directory.
|
# none of them belong in a production compose directory.
|
||||||
|
#
|
||||||
|
# This list is AUTHORITATIVE: the playbook converges the host
|
||||||
|
# directory onto this tarball with rsync --delete, so anything
|
||||||
|
# omitted here is removed from the host on the next deploy
|
||||||
|
# (except the host-owned .env and .deployed).
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
tar czf dist/hrafn-context.tgz \
|
tar czf dist/hrafn-context.tgz \
|
||||||
Dockerfile compose.yaml pyproject.toml README.md src
|
Dockerfile compose.yaml pyproject.toml README.md .env.example src
|
||||||
echo "context contents:"
|
echo "context contents:"
|
||||||
tar tzf dist/hrafn-context.tgz
|
tar tzf dist/hrafn-context.tgz
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,11 @@
|
|||||||
# Requires a build-context tarball at dist/hrafn-context.tgz. The CI
|
# Requires a build-context tarball at dist/hrafn-context.tgz. The CI
|
||||||
# workflow builds it; for a manual run, build it the same way:
|
# workflow builds it; for a manual run, build it the same way:
|
||||||
# mkdir -p dist && tar czf dist/hrafn-context.tgz \
|
# mkdir -p dist && tar czf dist/hrafn-context.tgz \
|
||||||
# Dockerfile compose.yaml pyproject.toml README.md src
|
# Dockerfile compose.yaml pyproject.toml README.md .env.example src
|
||||||
|
#
|
||||||
|
# The tarball must carry EVERYTHING that belongs in the compose dir: the
|
||||||
|
# converge step below deletes anything on the host that is not in it,
|
||||||
|
# except the host-owned .env and .deployed.
|
||||||
#
|
#
|
||||||
# DIFFERENCE 1 — tarball instead of per-file upload steps. nevermore
|
# DIFFERENCE 1 — tarball instead of per-file upload steps. nevermore
|
||||||
# enumerates every source file as its own upload step. That is explicit,
|
# enumerates every source file as its own upload step. That is explicit,
|
||||||
@@ -59,10 +63,26 @@ steps:
|
|||||||
dest: "{{ compose_dir }}/.hrafn-context.tgz"
|
dest: "{{ compose_dir }}/.hrafn-context.tgz"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
|
||||||
- name: Unpack build context
|
- name: Unpack build context into a staging dir
|
||||||
# Overwrites tracked files in place; leaves .env and any host-only
|
shell: |
|
||||||
# state alone because the archive does not contain them.
|
rm -rf {{ compose_dir }}/.stage && mkdir -p {{ compose_dir }}/.stage
|
||||||
shell: tar xzf {{ compose_dir }}/.hrafn-context.tgz -C {{ compose_dir }} && rm -f {{ compose_dir }}/.hrafn-context.tgz
|
tar xzf {{ compose_dir }}/.hrafn-context.tgz -C {{ compose_dir }}/.stage
|
||||||
|
rm -f {{ compose_dir }}/.hrafn-context.tgz
|
||||||
|
|
||||||
|
- name: Converge the compose dir onto the build context
|
||||||
|
# The first version of this playbook unpacked in place, which
|
||||||
|
# ACCRETED: it 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. rsync --delete
|
||||||
|
# makes the directory CONVERGE on the build context, so a stray file
|
||||||
|
# — from an old deploy or a debugging session — cannot outlive the
|
||||||
|
# next deploy. Host-owned state is protected by name.
|
||||||
|
shell: |
|
||||||
|
rsync -a --delete \
|
||||||
|
--exclude '.env' --exclude '.deployed' \
|
||||||
|
{{ compose_dir }}/.stage/ {{ compose_dir }}/
|
||||||
|
rm -rf {{ compose_dir }}/.stage
|
||||||
|
|
||||||
# ── build + start ───────────────────────────────────────────────────
|
# ── build + start ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user