Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
5.0 KiB
REPORT — v0.42.0: agentic controller update — in-guest image swap + rollback (Phase 1)
Repo: felhom-agent · Version: v0.41.0 → v0.42.0 · Date: 2026-06-26
What & why
The controller's old self-update (docker compose up -d on an in-container compose path) is dead in the
LXC architecture. Phase 1 moves the container swap to the host agent: the in-guest controller pulls
the target image, then the agent — external to the controller container, so it survives the controller
being killed mid-swap — rewrites /etc/felhom-controller-image, restarts the golden's
felhom-controller-bootstrap.service, verifies health, and rolls back if the new controller doesn't
come up. The guest is never left without a controller.
Baseline
felhom-agent 4725396 v0.41.0 → v0.42.0. Cross-repo: felhom-controller v0.85.0 (the pull + delegate half).
Changes
internal/localapi/controllerswap.go(new) —ControllerSwapper(testable, over aGuestExecutorseam) + handlers:POST /controller/swap {image}(withGuest) → 202{status, previous_image, target_image}, then async: record previous to/var/lib/felhom-agent/controller-swap-<vmid>.json(crash-safety) → confirm the target image is present in the guest (else abort, no swap) → write the image file → restart the bootstrap unit → poll the new controller to healthy (docker inspect, ≤90s) → roll back + restart if not. Strict image-ref gate (gitea.dooplex.hu/admin/felhom-controller:<semver>); single-flight per guest (409).GET /controller/swap/status→{state, current, previous, target, error}.
internal/localapi/guestbind.go—GuestBinder.GuestExec(the singlepct execseam).internal/localapi/server.go—ControllerSwapoption + route wiring + single-flight map.cmd/felhom-agent/main.go—ControllerSwap: guestBinder;--selftest=controller-swap -vmid -image(+ the allowlist fix — see below); version → 0.42.0.
Tests (internal/localapi/controllerswap_test.go) — all green
happy swap; rollback-on-unhealthy (image reverted to previous + restart called ≥2×); image-absent → no swap (no file write); running-with-no-healthcheck → healthy; bad image → 400; single-flight → 409.
- Companion red-proof: disabling the verify→rollback left the guest on the bad image and failed
TestControllerSwap_RollbackOnUnhealthy(image file = …:0.84.0 after rollback, want previous …:0.77.0) → reverted → green. The rollback assertion is load-bearing. - localapi package: 70 → 71 tests.
go build ./... && go vet ./... && go test ./...— allok.
Live test on demo 9201 (operator-authorized; agent v0.42.0 deployed to felhom-pve)
- Bug found live + fixed:
--selftest=controller-swapprinted flag-usage —controller-swapwas missing fromselftestFlag.Set's allowlist. Fixed (commitb17d07b), rebuilt, redeployed. - Swap 0.77→0.84 (
--selftest=controller-swap, after pre-pulling 0.84): state=done, new controllerUp (healthy),/etc/felhom-controller-image= 0.84.0. (~9s to healthy.) - Rollback (swap to a present-but-broken
:99.99.99built from a controller base withENTRYPOINT /bin/false): 90s verify timeout → rolled back to 0.84.0 healthy, state=failed, current=0.84.0 — the guest was never left without a controller. - E2E (real UI button) via claude-in-chrome on
felhom.demo-felhom.eu/settings: with the demo on 0.85.0 (new code) and 0.85.1 pushed as latest, clicked "Frissítés telepítése" → the controller pulled 0.85.1 in-guest and called the agent → agent log: swap → "new controller healthy"0.85.1→ UI reloaded showing "Sikeres (0.85.0 → 0.85.1)". No "docker-compose.yml nem elérhető". - Hub reflects the version via the controller's report path:
0.84.0 → 0.85.0 → 0.85.1.
Observations (out of scope — not acted on)
- Agent host-report
controller_versionis hardcoded""(internal/hub/collect.go:180, "slice 8 fills" — never done). So the hub'sguests.controller_versionstays empty; the controller's own report path carries the real version (verified0.85.1). Pre-existing, untouched here. - Agent-restart-mid-swap auto-resume: the swap records previous to a state file (so a manual/operator
rollback is possible), but the agent does not yet auto-reconcile a
swappingstate file on its own restart. The common failure (controller killed mid-swap) is fully handled; agent-crash-mid-swap leaves a recoverable record. A startup reconcile is a candidate follow-up. - Golden freshness: the golden bakes controller 0.77.0; a golden rebuild at current is wanted so new provisions start current (independent of this feature).
Commits
6f14b66— v0.42.0 swap + rollback + tests.b17d07b— fix: registercontroller-swapin the--selftestallowlist (found live).
No secrets in any committed file; the registry token used for the live pre-pull stayed on the host (stdin login, logged out).