Skip to content

Control-plane update transaction: pull, snapshot, recreate, health-check, revert both #380

Description

@onel

Size

L

Area

backend

Depends on

#378, #379

Summary

The control-plane update transaction itself — pull by digest, snapshot the brain's SQLite, write the declaration, recreate only what changed, health-check, and revert both images on failure of either. This is the expensive half of the update machinery and it is shared by the appliance and hosted profiles (UPDATES.md # 8: "we only build it once"), so it ships as a synchronous library with no trigger and no cloud in it.

Spec / source of truth

docs/specs/UPDATES.md # 3 (update mechanics, rollback) and # 8.3 / # 8.4 (one actor, staged-compose handoff)

Do

New package internal/hostagent/cpupdate, driven by a consumer-side Docker interface (extend the seam brainlaunch.Docker already establishes rather than opening a second Docker client):

  1. Pull by digest. Both refs are pinned digests, not tags (BUILD.md # 6). A pull failure aborts before anything on the box has changed.
  2. Snapshot the brain's SQLite to /var/lib/malmo/brain-snapshots/<old-ref>.db — only when the brain ref moved. Take it with the brain stopped so the copy is consistent; a hot copy of a file another process is writing is not a backup.
  3. Write the declaration first — ledger + staged compose (Control-plane image ledger + staged-compose rewrite: the updater's handoff point #379) — before any container is recreated. This is the # 8.3 handoff: the brain comes back up and reconciles to the same refs instead of fighting them back.
  4. Recreate only what moved. UI-only → docker compose up the control-plane project. Brain-only → stop, remove, and re-run the brain from the brainlaunch run spec so the new container is identical to a first-boot one except for the image. Both → one transaction.
  5. Health-check the brain on GET /healthz (Brain serves GET /healthz so the updater can probe it #378) and the UI with a plain HTTP probe, bounded at 60s per UPDATES.md # 3 step 3d.
  6. Revert both on failure of either: write the previous pair back to ledger + compose, recreate, restore the SQLite snapshot if the brain moved. Report which mode failed (pull / recreate / health check) so the caller can surface it.
  7. Retention: keep the previous pair and snapshot 7 days, then GC (the ledger from Control-plane image ledger + staged-compose rewrite: the updater's handoff point #379 holds the record; this slice deletes the images and snapshot files).

Structured logging with the standard fields (image, step, err); add any new recurring field to CLAUDE.md.

Touch

internal/hostagent/cpupdate/ (new), internal/hostagent/brainlaunch/, docs/specs/UPDATES.md, docs/progress/

Done when

Fake-Docker tests cover all five paths — UI only, brain only, both, health-check failure → both reverted and the snapshot restored, pull failure → nothing on the box changed — and each asserts the declaration was written before the first recreate call, not after. The package has no HTTP surface and no knowledge of where the target came from.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions