7.9 KiB
name, description
| name | description |
|---|---|
| felhom-app-catalog | 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.ymlfields, 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/<app>/{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/<app>/
(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 <image>:<tag>(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
<app>-internalnetwork only. - Note upstream's own compose/env documentation — the env-var names are the contract your
deploy_fieldswill 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:
for t in wget curl node python3; do
echo "$t: $(ssh felhom-pve "pct exec 9201 -- docker run --rm --entrypoint sh <image>:<tag> -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 inspectfor the base, or run the entrypointless variant with--entrypoint <candidate> ... --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_healthyon 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'scontainer_namemust be exactly the stack name (vaultwarden/rallly convention). Sidecars:<app>-db,<app>-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 markeddata_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: apiwith a real health endpoint beats baretype: http;tcpis 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):
- Commit + push the template.
- Trigger sync now (skip the 15-min wait): dashboard "Sablonok frissítése" button, or
POST /api/sync(30 s debounce) — see thefelhom-build-deployskill. - 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/<app>/deploywith the JSON body the UI sends) are the accepted proxy — SAY which method you used). - Watch the deploy panel: every container reaches healthy (not just running — Docker's
.Statelies; the.Statushealthcheck verdict is the truth). - Curl the app's real health path from inside the guest once (proves the path exists — step 2c).
- Confirm the app answers via Traefik in-guest:
pct exec 9201 -- curl -ksSo /dev/null -w '%{http_code}' -H "Host: <sub>.<domain>" https://127.0.0.1/→ 200. - 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.mdentry (newest on top) +REPORT.mdoverwrite; REUSE.md same-commit if you changed a catalog-wide convention. No version scheme in this repo.- Never commit a secret;
deploy_fieldsgenerate:specs are the only secret mechanism. - Hungarian customer-facing copy rules: the
felhom-ui-designskill.