From 3d7f0505b01bfeca659883a1be494dfbe6712c7e Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 3 Jul 2026 19:48:53 +0200 Subject: [PATCH] =?UTF-8?q?skills:=20NEW=20felhom-app-catalog=20(4th=20ski?= =?UTF-8?q?ll)=20=E2=80=94=20catalog=20authoring=20workflow,=20proven=20on?= =?UTF-8?q?=20SparkyFitness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6 --- CONTEXT.md | 6 ++ REPORT.md | 115 +++++++++++++------------- scripts/CHANGELOG.md | 14 ++++ skills/felhom-app-catalog/SKILL.md | 127 +++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 57 deletions(-) create mode 100644 skills/felhom-app-catalog/SKILL.md diff --git a/CONTEXT.md b/CONTEXT.md index 8b07b48..ab07769 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -3,6 +3,12 @@ > Created with the REUSE.md rollout (2026-07-03). Authoritative history: `hub/CHANGELOG.md` (hub), > `website/CHANGELOG.md`, `scripts/CHANGELOG.md`; end-of-task detail in `REPORT.md`. +- **2026-07-03 — 4th skill: `felhom-app-catalog`** (catalog authoring workflow; points at + app-catalog REUSE.md §1–2, no duplicated tables; auto-discovered by `install_skills.py`, 4/4 + found fresh-session). Proven by finalizing SparkyFitness live on demo (image-inspected + healthchecks, probe-container naming rule discovered + REUSE row added, UI-applied, controller + probe healthy). App-count drift recorded: website says "45+", per-category spans sum 49, real + catalog 53 — website fix is a separate task. - **2026-07-03 — GOLDEN 0.98.3 LIVE + VOUCHED; guide D.1b RETIRED (drill B1+B5 FIXED)** — `build-golden.sh` v2.0.0 (mandatory controller tag + baked bootstrap `.path` unit); golden baked with controller 0.98.3, clean-room validated (drill VM) through all four scenarios incl. the diff --git a/REPORT.md b/REPORT.md index 15de4c3..17e11e3 100644 --- a/REPORT.md +++ b/REPORT.md @@ -4,75 +4,76 @@ --- -# REPORT — Golden rebuild 0.98.3 (drill B5 + B1) — docs half (2026-07-03) +# REPORT — `felhom-app-catalog` skill (4th) + SparkyFitness finalized as its worked example (2026-07-03) -Implementation half in `felhom-agent/REPORT.md` (build-golden.sh v2.0.0 @ `ceca355`). Full drill -evidence: **`documentation/audits/DRILL-golden-098-2026-07-03.md`** (A–D transcripts, unit states, -resolution-order + fetch/sha proofs, cleanup, observations). This repo's changes are docs-only. +Companion half in `app-catalog-felhom.eu/REPORT.md` (the template changes + live-deploy evidence). ## Baselines | Repo | Base → head | |---|---| -| felhom.eu | `2e33a8b` → this push (docs) | -| felhom-agent | `c9f963d` → `ceca355` (script + CHANGELOG) | -| felhom-controller | untouched; **0.98.3** reconfirmed current + pullable, and is the baked tag | +| felhom.eu | `7ad111c` → this push (`skills/felhom-app-catalog/SKILL.md` NEW + docs) | +| app-catalog-felhom.eu | `15cdf7b` → `c7490f7` (sparkyfitness finalized + REUSE.md probe-naming row) | +| felhom-controller | READ-ONLY (sync.go + healthprobe.go consulted to state consumer behavior accurately) | -## What shipped (system-level) +## The skill -- **Golden 0.98.3** — bakes controller 0.98.3 + the `felhom-controller-bootstrap.path` unit; - published to Gitea (`felhom-golden/0.98.3/golden.tar.zst`, HTTP 201, round-trip sha - `b9a02ef1b6f02b9b58babc4c6aad9cf6c053ebdfba116c78c8e7830de757fd01`) and **operator-vouched** in - the Day-0 manifest (now: agent 0.63.0 + golden 0.98.3 — verified via `/api/v1/artifacts`). -- **B5 dead:** clean-room Day-0 install (Scenario C, local golden; Scenario D, vouched Gitea - fetch + sha verify) lands controller **0.98.3 on first boot**; `selfupdate/check` reports - up-to-date → the box self-manages; agent selftest clean; bentopdf deploys + answers 200. -- **B1 dead:** isolated proof — service condition-failed + path `active (waiting)` on a mount-less - boot; `pct set -mp9 …` against the RUNNING guest started the controller in ~1 s, no reboot - (`uptime -s` unchanged). Installer v1.9.1 reboot retained as belt (removal = recorded cleanup). +- **Workflow, not reference:** research → inspect-the-image healthcheck choice → compose → + `.felhom.yml` → live UI deploy on demo → verify healthy → reconcile count → finish. Every field + table is a POINTER to app-catalog `REUSE.md` §1–2 / `README.md` §format — the shipped SKILL.md + contains no field tables at all (checked); the one new fact it teaches (probe-container naming) + was placed in REUSE.md and is referenced from the skill. +- **Installer:** no change needed — `install_skills.py` auto-discovers `skills/*/SKILL.md` dirs + (line 69). Ran it: `felhom-app-catalog junction -> E:\git\felhom.eu\skills\felhom-app-catalog`. +- **Fresh-session discovery:** `claude -p "List your custom skills…"` → all 4 listed + (`felhom-app-catalog`, `felhom-build-deploy`, `felhom-testing`, `felhom-ui-design`). -## Docs changed (this repo) +## Two verified facts the exercise added to the knowledge base -- `documentation/runbooks/day0-install.md` — **D.1b retired** to a one-line `selfupdate/check` - verification; old procedure → Part F troubleshooting row keyed on "golden older than 0.86.0"; - header versions line (script v1.9.1 / agent v0.63.0 / golden v0.98.3); A.3 drilled-known-good - pair + vouch-≥0.98.3 note; A.4 floor text rewritten + raise-floor recommendation. -- `documentation/audits/DRILL-day0-cleanroom-2026-07-03.md` — ledger **B1, B5 → FIXED**; R6 - belt-note. -- `documentation/backlog/FOLLOWUP-golden-default-controller-tag.md` + `backlog/README.md` — - **RESOLVED** (M18/M19 convention: file kept + annotated, README entry marked FIXED; the note's - `:0.43.0` numbers were history — the live default had already rotted to `:0.85.1`, which is the - form of the problem the mandatory arg kills). -- NEW `documentation/audits/DRILL-golden-098-2026-07-03.md` — the evidence doc. +1. **Probe-container naming (REUSE.md row, new):** the controller-side `healthcheck.checks[]` + probe dials the container named EXACTLY like the stack dir; fallback = first running + prefix-match — in a multi-container stack that can be the DB. Convention: exposed service's + `container_name` = stack name (vaultwarden/rallly already comply; sparkyfitness now does). +2. **BusyBox `ash` `command -v` takes ONE argument** — `command -v wget curl node python3` + reported "wget only" in both SparkyFitness images while per-tool runs showed curl (and node in + the server) present. The skill's inspect command is a per-tool loop for this reason. -## Key proofs (short form; transcripts in the evidence doc) +## SparkyFitness — markers resolved by inspection (no more VERIFY-BEFORE-FINALIZE) -| Gate | Evidence | -|---|---| -| B5 red-proof | no-arg `build-golden.sh` dies with usage, exit 1, before any `pct` op (run on Windows + in the drill VM) | -| Scenario A | `[golden] build-golden.sh v2.0.0 — baking controller …0.98.3`; vzdump log: mp0 AND mp1 **included**; guest 9100 destroyed | -| Scenario B | before: `ConditionPathExists … not met` + path `active (waiting)`; after mp9 hot-plug: service SUCCESS @ +1 s, container `Up (healthy)` 0.98.3, boot time unchanged | -| Scenario C | `[SKIP] using local golden: …18_01_21.tar.zst` (resolution order); first boot 0.98.3; `update_available:false`; hub rows agent 0.63.0 / controller 0.98.3; bentopdf 200 | -| Publish | pre-delete 404 → PUT **201** → round-trip GET sha **matches** | -| Scenario D | `fetching golden v0.98.3 from Gitea` → `verified sha256 b9a02ef1… matches the hub manifest` → SUCCESS; first boot 0.98.3; up-to-date | -| Cleanup | all 8 drill-1 hub tables at count **0**, demo-felhom + peti-felhom intact; drill VM reverted to `virgin` (kept); bake cred file removed | +| Image (both tags RESOLVE) | wget | curl | node | python3 | Family chosen | +|---|---|---|---|---|---| +| `codewithcj/sparkyfitness:v0.17.2` (nginx SPA) | /usr/bin/wget | /usr/bin/curl | no | no | BusyBox-wget `--spider :80/` (kept — most common family) | +| `codewithcj/sparkyfitness_server:v0.17.2` (Node) | /usr/bin/wget | /usr/bin/curl | /usr/local/bin/node v24.17.0 | no | node-exec HTTP GET `:3010/api/health` status<400 (real status check, kept) | -Secrets: registry read-cred via 0600 env file only; the bake script's in-guest -`docker logout + rm /root/.docker/config.json` line is present and ran before archiving; publish -used the build server's out-of-band Gitea admin credential; nothing committed. +`/api/health` existence proven live: `{"status":"UP"}`. `mem_limit` arithmetic verified: +512 (db) + 1024 (server) + 256 (frontend) = **1792M** = the `.felhom.yml` value (comment added). -## Observations / operator follow-ups +## Live validation (method stated per claim) -1. **SECURITY:** the customer-config `git.token` (held by every customer box) is a Gitea **admin** - token with **package-WRITE** — the bake proved it by successfully publishing with it. The - manifest-sha chain protects installs from tampered artifacts, but the capability shouldn't exist - customer-side: issue a scoped read-only account/token + rotate. -2. `build-golden.sh`'s publish block auto-fires whenever `REGISTRY_*` is set (needed for the pull - too) → it published BEFORE Scenario C; deleted (204) and re-published after the gate. Candidate - cleanup: a `GOLDEN_PUBLISH=1` opt-in flag. -3. The installer's post-provision reboot is now redundant (path unit wins first) — candidate - removal in a future installer version; kept per the task rules. -4. Recommended: raise the global controller floor to 0.98.3 (UI, 1 min) for drift protection. -5. Drill-environment note: launching the drill VM with `dhcpstart=10.0.2.30` (+ explicit - `hostfwd…-10.0.2.15:22`) eliminates the prior drill's slirp DHCP/IP-collision quirk — worth - using in every future drill. +SparkyFitness was ALREADY deployed on demo from the draft (4 h prior, three containers healthy) — +so the live action was applying the FINALIZED template: "Sablonok frissítése" then the card's +"Frissítés", both clicked in the real dashboard via the browser bridge (claude-in-chrome). +Compose recreated only the renamed frontend (`sparkyfitness`, healthy in 21 s); server + db +untouched (Up 4 h) → the two `data_key` secrets were NOT regenerated, as required. Verifications: +git-sync file content, `docker ps`, `/api/health`, traefik Host-header curl (200), and +`GET /api/stacks/sparkyfitness` (the endpoint the UI polls) — controller probe verdict +`healthy: true, http :80/ → 200 (6ms)` against the renamed container. Kept deployed (7.3 option A; +expendable demo box, real catalog app). + +## App-count reconciliation (recorded, NOT fixed) + +Real catalog: **53** templates. Website `gyik.html` claims "több mint 45" (true but lagging); +`alkalmazasok.html` per-category `app-count` spans sum **49** ≠ 53. Website fixes are +`felhom-ui-design` + `site_gates.py` territory — separate task. + +## Observations (not acted on) + +- The controller UI card and `docker ps` agreed throughout; the probe's DEBUG-level per-stack log + lines are invisible at INFO — the `GET /api/stacks/` endpoint is the reliable verdict + source for validation. +- `paperless-ngx`'s containers (`paperless-webserver`…) match NEITHER the exact-name nor the + prefix fallback of its stack name `paperless-ngx` — its controller-side probe likely never runs + (silently "skipped no container"). Candidate follow-up: rename or add a probe-container hint; + NOT touched here. +- The pre-rename fallback DID probe the right container on demo (probe was green before the + update) — the rename converts order-dependent luck into the deterministic convention. diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index 8000f52..1e2d488 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,5 +1,19 @@ # Felhom scripts — Changelog +## skills — NEW: felhom-app-catalog (4th skill) + SparkyFitness as its worked example (2026-07-03) + +`skills/felhom-app-catalog/SKILL.md` — the catalog **authoring workflow** (research → inspect the +image for the healthcheck family → write compose/.felhom.yml → deploy live through the dashboard → +verify healthy → reconcile the app count). Deliberately points at app-catalog `REUSE.md` §1–2 + +`README.md` §format for every field table (one-fact-one-place; no duplication). Unique content: +the never-guess-the-healthcheck rule with the per-tool image-inspection loop (BusyBox `ash` +`command -v` gotcha: it silently ignores all but its first argument — verified), the +probe-container naming rule (controller probes the container named exactly like the stack — +verified in `felhom-controller/internal/stacks/healthprobe.go`, row added to app-catalog REUSE.md), +the Hungarian-quote YAML kill, and the deploy-is-the-test doctrine. No installer change needed — +`install_skills.py` auto-discovers `skills/*/SKILL.md`; fresh-session discovery probe listed all 4. +Proven by finalizing `sparkyfitness` end-to-end on demo (see app-catalog-felhom.eu CHANGELOG). + ## docs — golden 0.98.3 live: D.1b RETIRED, drill ledger B1/B5 → FIXED, backlog note resolved (2026-07-03) Companion to felhom-agent's `build-golden.sh` v2.0.0 (@ `ceca355`): the golden now bakes the CURRENT diff --git a/skills/felhom-app-catalog/SKILL.md b/skills/felhom-app-catalog/SKILL.md new file mode 100644 index 0000000..2f03b2f --- /dev/null +++ b/skills/felhom-app-catalog/SKILL.md @@ -0,0 +1,127 @@ +--- +name: felhom-app-catalog +description: The authoring workflow for the Felhom app catalog (app-catalog-felhom.eu) — use when adding a new app to the catalog, writing or fixing a `.felhom.yml` or a catalog `docker-compose.yml`, choosing or fixing a healthcheck, setting memory limits, wiring app email (SMTP shim), validating a template live, or reconciling the app count. Contains the research→inspect→write→deploy-live→verify procedure and the traps (healthcheck guessed instead of inspected, Hungarian-quote YAML kill, probe container naming). +--- + +# Felhom app-catalog authoring workflow + +This skill is the **procedure**. The **reference** lives in the catalog repo — point there, never +restate it: + +- `app-catalog-felhom.eu/REUSE.md` §1–2 — required `.felhom.yml` fields, the 5 Docker-healthcheck + families, controller-probe types, secret generators, memory rules, SMTP shim, canonical templates. +- `app-catalog-felhom.eu/README.md` §".felhom.yml Format" + §"Docker Compose template standards" — + the field spec. +- Copy-from examples: `templates/paperless-ngx/` (multi-container, full field spectrum), + `templates/vaultwarden/` (single-container). Named there so you don't reinvent. + +Repo facts: one dir per app, exactly `templates//{docker-compose.yml,.felhom.yml}`. +`templates.json` + `generate-customer.sh` are LEGACY — never touch for new apps. **Push to `main` +IS the deploy**: the controller git-syncs the two template files to `/opt/docker/stacks//` +(SHA-256 change detection, ≤15 min, never overwrites a deployed `app.yaml`). + +## 1. Research the upstream + +- Find the official image(s) + a real published version tag. **Pin it — never `:latest`**, and prove + the tag resolves: `docker manifest inspect :` (anon) or run it on the demo guest. +- Identify the topology: single container, or app + DB/cache sidecars? Which ONE service does + Traefik expose? Everything else goes on the `-internal` network only. +- Note upstream's own compose/env documentation — the env-var names are the contract your + `deploy_fields` will feed. + +## 2. Pick the healthcheck family by INSPECTING the image — never guess + +The core rule. A guessed `wget` probe ENOENTs at runtime and the container flaps unhealthy forever +(that was rallly's real shipped bug — and the SparkyFitness draft repeated the pattern until +inspection). On the demo guest: + +```bash +SSH=/c/Windows/System32/OpenSSH/ssh.exe; export MSYS_NO_PATHCONV=1 +for t in wget curl node python3; do + echo "$t: $($SSH felhom-pve "pct exec 9201 -- docker run --rm --entrypoint sh : -c 'command -v $t'" 2>/dev/null | tr -d '\r')" +done +``` + +One tool per run — BusyBox `ash`'s `command -v` silently ignores every argument after the first, so +a single `command -v wget curl node python3` reports ONLY wget's presence and hides the rest +(verified against the SparkyFitness images: the one-shot form said "wget only"; per-tool runs showed +curl and node too). + +- Then choose the family from REUSE.md §2 (BusyBox-wget / curl / Node-exec / Python-socket) — + matching what actually exists in THAT image. Images with none of the four: shell TCP probe + (`/dev/tcp`) or reconsider the tag. +- If the image has no `sh`, inspect instead: `docker image inspect` for the base, or run the + entrypointless variant with `--entrypoint ... --version`. +- Confirm the port you probe is the port the process actually listens on (upstream docs + the + compose env you set, e.g. an `NGINX_LISTEN_PORT`); confirm the health path exists (curl it once + the app runs — step 5). +- DB/cache sidecars: use the DB-sidecar row verbatim (`pg_isready` / `healthcheck.sh` / + `redis-cli ping`) + `depends_on: condition: service_healthy` on the consumer. + +## 3. Write the compose file + +Copy the `paperless-ngx` skeleton; conform to README §"Docker Compose template standards" (memory +limit on EVERY service, no `reservations`, `restart: unless-stopped`, `TZ=Europe/Budapest`, +explicit `container_name`, `${VAR}` syntax, Traefik labels only on the exposed service, internal +network for the rest, Hungarian header comment with the RAM math). + +- **Probe-naming rule (verified in `felhom-controller/internal/stacks/healthprobe.go`):** the + controller-side health probe dials the container whose **name equals the stack (directory) name + exactly**, falling back to the FIRST running prefix-match — which in a multi-container stack can + be the DB. So: **the Traefik-exposed service's `container_name` must be exactly the stack name** + (vaultwarden/rallly convention). Sidecars: `-db`, `-redis`, …. +- **Hungarian-quote YAML gotcha:** in Hungarian text inside YAML, „…” quoting is fine, but if you + quote a YAML scalar with ASCII `"`, a Hungarian ” or a stray ASCII `"` inside SILENTLY kills the + whole file at parse time. Close what you open — U+201D belongs in the text, ASCII `"` only as the + YAML delimiter pair. Gate every edit: `python -c "import yaml; yaml.safe_load(open('...'))"` on + BOTH files. + +## 4. Write `.felhom.yml` + +Required fields + conventions: REUSE.md §2 rows 2–4 (fields, `deploy_fields`, probe) — follow them, +don't re-derive. The parts people get wrong: + +- `mem_limit` = **SUM of the compose limits** — show the arithmetic in a comment like paperless + (`768+256+128=1152M`). `mem_request` = expected steady-state, display-only. +- Secrets: `generate:` per REUSE.md vocabulary. A secret that encrypts stored data or signs + sessions/2FA must be marked `data_key: true` + a comment saying WHY it must never be regenerated + (restore RECOVERS it; regenerating destroys data / locks out 2FA users). +- `healthcheck.checks[]` targets the EXPOSED service's real port/path (`type: api` with a real + health endpoint beats bare `type: http`; `tcp` is the last resort). Remember: it probes the + container named like the stack (step 3). + +## 5. Deploy live to verify — the non-negotiable step + +A template that only "looks right" is NOT done. On the demo box (guest 9201; expendable): + +1. Commit + push the template. +2. Trigger sync now (skip the 15-min wait): dashboard **"Sablonok frissítése"** button, or + `POST /api/sync` (30 s debounce) — see the `felhom-build-deploy` skill. +3. Deploy through the **dashboard UI** (the deploy wizard — real customer pipeline; no raw + CLI/compose shortcut. If no browser bridge is available, the wizard's own endpoints + (`POST /api/stacks//deploy` with the JSON body the UI sends) are the accepted proxy — + SAY which method you used). +4. Watch the deploy panel: every container reaches **healthy** (not just running — Docker's + `.State` lies; the `.Status` healthcheck verdict is the truth). +5. Curl the app's real health path from inside the guest once (proves the path exists — step 2c). +6. Confirm the app answers via Traefik in-guest: + `pct exec 9201 -- curl -ksSo /dev/null -w '%{http_code}' -H "Host: ." https://127.0.0.1/` → 200. +7. Confirm the controller UI shows the app **healthy** (the controller-side probe passed — if it + shows running-but-not-healthy, your `healthcheck.checks[]` or the container naming is wrong). + +If any probe assumption from step 2 was wrong, fix the template and repeat — the deploy is the test. + +## 6. Reconcile the app count + +The website claims an app count ("több mint 45 alkalmazás" in `felhom.eu/website/gyik.html`; the +per-category `app-count` spans in `alkalmazasok.html`). After adding an app, compare +`ls templates | wc -l` with those claims; if drifted, RECORD it in your report — website fixes are +a separate task (`felhom-ui-design` + `site_gates.py` territory), don't do them from here. + +## 7. Finish + +- Update `README.md`'s App Catalog + Variable-types tables (every app is listed — convention). +- `CHANGELOG.md` entry (newest on top) + `REPORT.md` overwrite; REUSE.md same-commit if you changed + a catalog-wide convention. No version scheme in this repo. +- Never commit a secret; `deploy_fields` `generate:` specs are the only secret mechanism. +- Hungarian customer-facing copy rules: the `felhom-ui-design` skill.