3d7f0505b0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
128 lines
7.9 KiB
Markdown
128 lines
7.9 KiB
Markdown
---
|
||
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/<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
|
||
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 <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 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/<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.
|