v0.261.0 — the controller no longer swaps itself out from under an app update (R-608, R-609)
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
The controller self-updates daily at 04:30 by default, and after any hub report once a floor sits above the box. That swap restarts the controller container. The window proposed for automatic app updates is 02:30-05:00. It contains 04:30. R-608 — a two-way lock, wired in main.go (stacks never imports selfupdate): - stacks.Manager.AnyUpdating() -> Updater.SetAppUpdatingCheck, consulted in the same three places as the existing backupRunning gate. - Updater.IsUpdateRunning -> Manager.SetSelfUpdatingCheck; UpdatePreflight refuses `self_updating`. - MEASURED: the gap was narrower than assumed. The update's `backing-up` phase already takes the backup single-flight, so that one phase was covered. The other six were not, and `starting`/`verifying` are where data may have moved. - The lock must NOT latch: a held app does not block the controller's own updates, including the release that might fix the hold. R-609 — the 409 carries `data.reason`, additively. transient (busy, updating, deploying, migrating, self_updating) vs terminal (held, downgrade). Found while writing the test: the router refuses a HELD app on its own line before the preflight, so `held` would have been the one reason missing. Five red-proofs, each seen to fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -144,6 +144,7 @@
|
||||
| `Stack.CatalogImages` vs `Stack.TemplateImages` (v0.235.0) | controller/internal/stacks/manager.go | both `map[string]string` | badge input vs "what the next `up -d` gives this app" | **THE TRAP: same type, same shape, opposite meaning after the freeze.** `TemplateImages` reads the LIVE (possibly frozen) compose file; `CatalogImages` reads the syncer's clone. `web.compareInstalledToTemplate` MUST use `CatalogImages` or it answers „Naprakész" on exactly the apps that are behind, with every test green. Red-proved |
|
||||
| `Manager.BackfillInstalledImages` (v0.234.0) | controller/internal/stacks/installed.go | `() int` | seeding `installed_images` for apps that have NO record — call ONCE at startup | Beside `BackfillDesiredState` in `cmd/controller/main.go`, after it and BEFORE the boot reconciler (pinned by an AST-walking test that asserts the ORDER). **READS only** — starts nothing, writes no compose file. **Never overwrites an existing record** (an app that has one is not even observed). **REFUSES a partial observation** (`observationCoversTemplate`): `web.compareInstalledToTemplate` reads a service-count mismatch as BEHIND, so seeding a degraded app from what is visible renders „Frissítés elérhető" over an app that is current. The bring-up paths may write a partial because they follow a SUCCESSFUL `up -d` where a gap is real news; a backfill meets any state and must be stricter |
|
||||
| `stacks.ParseComposeImages` (v0.233.0) | controller/internal/stacks/installed.go | `(composePath string) (map[string]string, error)` | compose SERVICE name -> the image the FILE pins; feeds `Stack.TemplateImages` and the update badge | yaml.v3 `services:` MAP parse, never a line scan (same reason as `DBServiceNames`). An error means CANNOT-TELL — `ScanStacks` leaves `TemplateImages` nil and the badge renders NOTHING, never "current" |
|
||||
| `stacks.Manager.AnyUpdating` / `SetSelfUpdatingCheck` + `selfupdate.Updater.SetAppUpdatingCheck` (v0.261.0, R-608) | controller/internal/stacks/update.go, internal/selfupdate/updater.go, wired in cmd/controller/main.go | `() bool` callbacks, both directions | THE two-way lock between the CONTROLLER's own swap and a guarded APP update | **Wire BOTH halves or neither** — they are wired together in `main.go`, the only place holding both objects; **`stacks` must never import `selfupdate`**. The app-update side is a sibling of `SetBackupRunningCheck` and is consulted in the SAME three places (dry run, `TriggerUpdate`, `maybeAutoUpdate`) — do not add a fourth pattern. **`AnyUpdating` MUST answer false for a HELD app** (`Stack.Updating` is cleared on done/failed/held): a latching gate would block the controller's own updates for ever, including the one that fixes the hold. Nil callbacks are SAFE and mean pre-v0.261.0 behaviour — never fail closed on an unwired gate |
|
||||
| `stacks.CatalogOrder` / `CompareImageRefs` (v0.260.0, R-524) | controller/internal/stacks/updateorder.go | `(Stack) UpdateOrder` — Unknown/Current/Behind/**Ahead** | THE one "how does this app stand against the catalog?" verdict | **Both the badge AND `Manager.UpdatePreflight`'s `downgrade` refusal read it — never re-implement the comparison.** `web.compareInstalledToTemplate` is a thin wrapper. Ahead is NARROW: every differing service must be orderable AND newer, else Behind. Ordering is `util.Version.Compare` behind a tag normaliser (`X.Y`/`X.Y.Z`, optional `v`, suffix must be IDENTICAL on both sides) — **never add a second comparator**. Queries NO registry; absent record = Unknown, never „Naprakész" |
|
||||
| `web.updateBadge` / `updateBadgeAt` / `Metadata.CatalogSince` + `CatalogSinceAge` (v0.233.0) | controller/internal/web/updatebadge.go, controller/internal/stacks/metadata.go | `(stacks.Stack) *MetaBadge` | THE "is this app current?" label — „Naprakész" / „Frissítés elérhető — N napja" | The SECOND `*MetaBadge` user the type was built for: existing `meta_badge` partial, **no new markup or CSS**. **NO RECORD RENDERS NOTHING — absent means UNKNOWN, never current** (R-166 applied to an observation; red-proved). **No version number reaches the customer** and **no registry is queried**. `catalog_since` is tolerant in the `lifecycle` style — absent/empty/malformed/**future** all degrade to a badge with no age + one WARN. LIMITATION: for the **10** floating pins (recounted 2026-09-21; the old "23" matched no definition the catalog supports) the ref can match while the image has moved — 6 of the 7 measurable ones HAVE moved — so those read „Naprakész" when they may not be |
|
||||
| `Manager.logPostStartStatus` | controller/internal/stacks/manager.go | `(name, stackDir, env)` | Async post-start verification | compose up exits 0 on crash-loops; this is the detection. Goroutine + 3s, never blocks |
|
||||
|
||||
Reference in New Issue
Block a user