Files
app-catalog-felhom.eu/REPORT.md
T

72 lines
5.2 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 — image pinning: `:latest` eliminated from the catalog (2026-07-12)
**Class:** implementation (template change + sweep gate; push = deploy).
**Baseline:** `main` @ `2ebe082` → (this commit). **Scope:** 5 compose image lines + new gate script
+ CLAUDE.md rule. No healthcheck/env/format touch-ups; no controller change; no redeploys triggered.
## Why
The controller's recovery-unit `ImagePins` (felhom-controller `internal/backup/recovery_unit.go`)
pins the compose *tag*: restoring a `:latest`-pinned app re-pulls whatever `:latest` points to at
restore time — potentially schema-incompatible with the data being restored. Load-bearing rule:
**a deployed app pins to the digest it is currently running** (the pin never causes a version jump);
undeployed apps pin to the latest verified upstream stable.
## The five pins (old → new, with evidence)
| App | New pin | Evidence (all gathered 2026-07-12) |
|-----|---------|------------------------------------|
| **calibre-web** (DEPLOYED, demo 9201) | `crocodilestick/calibre-web-automated:v4.0.6` | Running RepoDigest on 9201: `sha256:c31a738b6d5e…`; Docker Hub tag `v4.0.6` digest: `sha256:c31a738b6d5e…`**exact match** (Scenario A: pin == running image; a newer tag would have violated the no-silent-upgrade guard) |
| **bentopdf** (undeployed) | `ghcr.io/alam00000/bentopdf:v2.8.6` | GitHub latest release v2.8.6 (2026-06-28); ghcr manifest digest `sha256:eaeea1e4…` == `latest` |
| **papra** (undeployed) | `ghcr.io/papra-hq/papra:26.6.1-rootless` | ghcr `latest` digest `sha256:a7a42e22…` == `26.6.1-rootless`; the `-root` variant differs (`c5757540…`) — the **rootless** variant is what `:latest` was shipping, so behavior (permission semantics) is preserved exactly |
| **recipe-importer** (own image, undeployed) | `gitea.dooplex.hu/admin/recipe-importer:v0.9.11` | Its repo CHANGELOG declares v0.9.11 (2026-02-26); the registry **already had** `v0.9.11` (build.sh pushes `:VERSION`+`:latest` together), digest `sha256:f3cb617c…` == `latest`**Scenario C's retag was unnecessary**; no push performed |
| **termix** (undeployed) | `ghcr.io/lukegus/termix:2.5.0` | ghcr digests: `latest` == `2.5.0` == `release-2.5.0` (`sha256:4d337131…`); bare CalVer tag chosen |
All five `docker manifest inspect <ref>` checks ran on the build server (180) with per-command exit
codes checked — 5× OK, zero pipe-swallowed.
`templates.json`: contains **no image strings** (legacy Portainer-era metadata) — untouched, per §4.3.
## Sweep + red-proofs (§10 / Scenario D)
- New rerunnable gate: `scripts/check-image-pins.py` — scans every `templates/*/docker-compose.yml`
`image:` line; fails on floating tags (`latest`,`dev`,`nightly`,`edge`,`main`,`master`) AND on
untagged refs (implicit :latest); `@sha256:` counts as pinned; registry-port refs handled.
- **Final run: `image-pin gate OK — 53 templates, 0 unpinned images`, exit 0.**
- **Red-proof 1** (floating tag): reverted termix to `:latest` → exit 1 with
`templates/termix/docker-compose.yml:11 … [floating tag :latest]` → restored → exit 0. ✓
- **Red-proof 2** (bare shape): stripped termix's tag entirely → exit 1 with
`[NO TAG (implicit :latest)]` → restored → exit 0. ✓ (Both §7-D shapes proven.)
## Fleet-deployment verification (Scenario B)
- demo 9201: `docker ps` — bentopdf/papra/termix/recipe-importer **not deployed**; calibre-web
running from `:latest` (Up 4 hours at check time).
- felhotest (router.abonet.hu:33022): **unreachable** (connection refused).
- Peti's box: offline (dead fan, no ETA).
- Operator asked and approved proceeding: every pin is digest-identical to current `:latest`, so
even a hidden deployment elsewhere sees zero change vs. the status quo on its next redeploy.
## Git-sync no-op verification on 9201
- Pushed `71828a8`; sync triggered via `docker exec felhom-controller curl -s -X POST
http://127.0.0.1:8080/api/sync` → HTTP 200. (Gotcha confirmed: guest-netns
`127.0.0.1:8080` is NOT reachable — the API must be curled from inside the controller
container; the public-URL POST proxy gotcha also applies.)
- Synced file on 9201: `/opt/docker/stacks/calibre-web/docker-compose.yml:21` now reads
`image: crocodilestick/calibre-web-automated:v4.0.6`. ✓
- calibre-web container after sync: `crocodilestick/calibre-web-automated:latest | Up 5 hours
(healthy)` — **uptime unchanged, no restart**; the running image's digest equals the v4.0.6 pin,
so the next redeploy pulls the byte-identical image. ✓
## Observations (not acted on)
- When Peti's box returns: check whether bentopdf/papra/termix/recipe-importer are deployed there;
if any is running an OLDER digest than the pin, decide upgrade vs. per-node pin then. felhotest
should get the same check when reachable again.
- papra upstream publishes only `-root`/`-rootless` variant tags — any future manual upgrade must
keep the `-rootless` suffix or permissions semantics change.
- bentopdf's ghcr `tags/list` API returns a stale/partial page (newest listed was 1.15.3 while
v2.8.6 exists and serves) — tag existence must be checked via manifest HEAD/inspect, not tags/list.
- recipe-importer's registry `:latest` remains published (allowed; only templates must not use it).