Files
felhom-agent/REPORT.md
T

70 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 a `GuestExecutor`
seam) + 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 single `pct exec` seam).
- **`internal/localapi/server.go`** — `ControllerSwap` option + 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 ./...` — all `ok`.
## Live test on demo 9201 (operator-authorized; agent v0.42.0 deployed to felhom-pve)
- **Bug found live + fixed:** `--selftest=controller-swap` printed flag-usage — `controller-swap` was
missing from `selftestFlag.Set`'s allowlist. Fixed (commit `b17d07b`), rebuilt, redeployed.
- **Swap 0.77→0.84** (`--selftest=controller-swap`, after pre-pulling 0.84): state=**done**, new controller
`Up (healthy)`, `/etc/felhom-controller-image` = 0.84.0. (~9s to healthy.)
- **Rollback** (swap to a present-but-broken `:99.99.99` built from a controller base with `ENTRYPOINT
/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)
1. **Agent host-report `controller_version` is hardcoded `""`** (`internal/hub/collect.go:180`, "slice 8
fills" — never done). So the hub's `guests.controller_version` stays empty; the controller's own report
path carries the real version (verified `0.85.1`). Pre-existing, untouched here.
2. **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 `swapping` state 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.
3. **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: register `controller-swap` in the `--selftest` allowlist (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).