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

127 lines
7.9 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.
---
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` §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:
```bash
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.