Files
felhom.eu/skills/felhom-app-catalog/SKILL.md
T

7.9 KiB
Raw Blame History

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 §12 — 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/<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>-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:

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 inspect for 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_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: <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 24 (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/<app>/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: <sub>.<domain>" 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.