8ddd3c9da5
BusyBox wget (+ node/python/curl one-shots, incl mealie's socket tuple) resolve localhost -> IPv6 ::1 with no cross-family fallback; an IPv4-only-binding app reads docker-unhealthy while serving (vaultwarden, re-run 2026-07-06). Escalates that instance to the class. Scoped strictly to healthcheck test: lines (diff-reviewed: no env/config/label changed; .felhom.yml already clean). New REUSE.md convention row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
9.4 KiB
9.4 KiB
REUSE.md — app-catalog-felhom.eu
Before adding or editing an app, check here. Conventions to copy, traps to avoid. Maintenance: update in the SAME commit that changes a catalog-wide convention. Entries cite real files. Line numbers are landmarks only — reconfirm before editing.
1. Canonical helpers
None — this repo is templates/config, not code. See §2/§5.
2. Canonical patterns (copy structure from THE named file)
| Pattern | Canonical file | Key traits |
|---|---|---|
| The one canonical example app | templates/paperless-ngx/ (both files) |
Multi-container (app + postgres + redis), HDD + userdata mounts, full deploy_fields spectrum (domain/subdomain/secret/password/text/path/select). Copy this structure for any new app. |
.felhom.yml required fields |
templates/paperless-ngx/.felhom.yml |
All 53 apps: display_name, description (Hungarian), category, subdomain, slug, resources{mem_request, mem_limit, pi_compatible, needs_hdd}, deploy_fields, app_info{tagline, use_cases, first_steps, ...}, healthcheck. Optional: smtp_mapping (email-capable apps), open_path (non-root landing page, e.g. ghost). |
| deploy_fields conventions | templates/paperless-ngx/.felhom.yml (deploy_fields: block) |
Every app starts with DOMAIN (type domain) + SUBDOMAIN (type subdomain, locked_after_deploy: true). Secrets: type: secret + generate: — dominant generators password:24 (DB passwords) and hex:32 (app secret keys); password:16 for shown admin passwords (type: password). HDD apps add HDD_PATH (type: path, placeholder /mnt/felhom-drives/hdd_1, locked). Labels/descriptions in Hungarian. |
| Controller-side health probe | templates/vaultwarden/.felhom.yml (healthcheck: block) |
healthcheck.checks[] with type: http (port only), type: api (port + path + expect.status: 200), or type: tcp (port only — mealie, crafty-controller). Prefer api with a real health path when the app has one. |
| Docker healthcheck host | ALL templates/*/docker-compose.yml (healthcheck.test:) |
Always 127.0.0.1, never localhost. BusyBox wget (and node/python/curl one-shots) resolve localhost→IPv6 ::1 with NO cross-address-family fallback; an app that binds IPv4-only then reads docker-unhealthy while fully serving (vaultwarden, re-run 2026-07-06 — swept all 48 templates). |
| Docker healthcheck — BusyBox/wget images | templates/vaultwarden/docker-compose.yml (~L49) |
test: ["CMD", "wget", "--spider", "-q", "http://localhost:<port>/<path>"]. Most common family (~20 apps, e.g. homebox, glance). |
| Docker healthcheck — curl-capable images | templates/paperless-ngx/docker-compose.yml (~L76) |
test: ["CMD", "curl", "-f", "http://localhost:<port>/<path>"] (~18 apps: jellyfin, immich, sonarr…). |
| Docker healthcheck — Node images (no wget/curl) | templates/rallly/docker-compose.yml (~L49) |
test: ["CMD", "node", "-e", "require('http').get(...)"] — used when the image lacks wget (that was rallly's actual bug). |
| Docker healthcheck — Python images | templates/mealie/docker-compose.yml (~L47) |
test: ["CMD-SHELL", "python3 -c \"import socket; s=socket.create_connection(('localhost',<port>),2); s.close()\""] (mealie, crafty-controller). tandoor/wger use urllib.request variants for real HTTP checks. |
| Docker healthcheck — DB/Redis sidecars | templates/paperless-ngx/docker-compose.yml (~L107, L129) |
postgres: pg_isready -U <user> -d <db>; mariadb: healthcheck.sh --connect --innodb_initialized; redis: redis-cli ping. App container gets depends_on: <db>: condition: service_healthy. |
| Memory convention | templates/paperless-ngx/docker-compose.yml (~L71) + .felhom.yml resources: |
EVERY service has deploy.resources.limits.memory (compose is the enforcement). NO reservations anywhere. .felhom.yml mem_limit = SUM of all containers' limits (see paperless header comment: 768+256+128=1152M); mem_request = expected steady-state usage, display-only. |
| Compose file skeleton | templates/paperless-ngx/docker-compose.yml (header) |
Header comment (app, domain, DB type, RAM math, Pi), restart: unless-stopped, TZ=Europe/Budapest, explicit container_name, traefik-public external network + <app>-internal for DBs, Traefik labels with Host(`${SUBDOMAIN}.${DOMAIN}`), named volumes for DB/config (NVMe), ${HDD_PATH}/appdata/<app>/... for bulk data, ${USERDATA_PATH}/... for customer-browsable content. |
| App-email (SMTP shim) opt-in | templates/vaultwarden/.felhom.yml (smtp_mapping:) + README.md §smtp_mapping |
smtp_mapping maps shim host/port/security/from to the app's own env names; compose MUST reference the mapped ${VAR:-} keys with empty defaults. STARTTLS if the app can accept self-signed certs, else security_value: "NONE" plaintext (or the :2526 plaintext listener for STARTTLS-insistent clients — see calcom/nextcloud). TRAP: an image that treats defined-but-EMPTY mail vars as "set" (vaultwarden — campaign F1 2026-07-06) needs its own enable-flag gated false in compose and flipped "true" via smtp_mapping.extra; boot-prove a fresh email-off deploy for every new smtp-mapped app. |
| Probe-container naming | templates/vaultwarden/docker-compose.yml (container_name: vaultwarden) + templates/sparkyfitness/ |
The controller-side healthcheck.checks[] probe dials the container whose name equals the stack (directory) name exactly; fallback = the FIRST running prefix-match, which in a multi-container stack can be the DB (verified: felhom-controller/internal/stacks/healthprobe.go findProbeContainer). So the Traefik-exposed service's container_name must be exactly the stack name; sidecars <app>-db, <app>-redis, …. |
3. Dangerous lookalikes — do NOT copy
| Trap | Why it bites | Use instead |
|---|---|---|
templates/gokapi/docker-compose.yml custom entrypoint seeding config.json + --deployment-password |
One-off hack because Gokapi has no env-var headless setup (pinned to ConfigVersion 21 / v1.9.6). Copying this entrypoint pattern to another app will break on image updates. | Normal env-var config via deploy_fields; entrypoint-seeding only as last resort. |
templates.json + scripts/generate-customer.sh |
LEGACY Portainer-only mechanism (marked so in README.md). New apps do NOT need entries here; the controller syncs templates/<app>/ directly. |
Just templates/<app>/{docker-compose.yml,.felhom.yml}. |
templates/uptime-kuma/docker-compose.yml test: ["CMD", "extra/healthcheck"] |
Image-provided binary, unique to this image — not a portable pattern. | Pick the wget/curl/node/python family matching your image (§2). |
4. Seams & interfaces (cross-repo)
- Controller pulls this repo (default 15m) and copies
templates/<app>/docker-compose.yml+.felhom.ymlto/opt/docker/stacks/<app>/; it NEVER overwrites deployedapp.yaml/.env; SHA-256 change detection. Contract description:README.md§"How It Works". .felhom.ymlis the contract surface consumed by felhom-controller (repofelhom-controller/):deploy_fieldsdrive the deploy wizard,resourcesthe deploy screen hints,healthcheck.checksthe controller-side probe,smtp_mappingthe app-email injection at deploy/redeploy.- Assets are NOT in this repo: logo/screenshots resolve from felhom.eu via
slug({assets.base_url}/assets/{slug}-logo.webp— see comment block intemplates/paperless-ngx/.felhom.yml). - Commit+push to main IS the deploy: the controller picks changes up on next sync.
5. Extension points (adding a new app)
templates/<app>/docker-compose.yml— copytemplates/paperless-ngx/docker-compose.ymlskeleton; every service needscontainer_name,restart: unless-stopped,TZ=Europe/Budapest,deploy.resources.limits.memory, a healthcheck (family per §2), Traefik labels on the web service,traefik-publicexternal +<app>-internalnetwork if it has a DB.templates/<app>/.felhom.yml— copytemplates/paperless-ngx/.felhom.yml; required keys per §2; DOMAIN + SUBDOMAIN fields always;mem_limit= sum of compose limits; Hungarian user-facing text;healthcheck.checksprobe.- Update
README.mdApp Catalog + Variable-types tables (convention — every existing app is listed). - Skip
templates.json/generate-customer.sh(legacy, §3). - Email-capable app: add
smtp_mapping+ matching${VAR:-}compose lines (§2 last row).
6. Known inconsistencies (observed — NOT fixed)
README.mdliststemplates/filebrowser/in the tree, but no such directory exists; converselytemplates/recipe-importer/exists but is absent from README's tree and both catalog tables.README.mdfield-type table (§".felhom.yml Format") omitssubdomain(used by all 53 apps) andsecret_input(templates/romm/.felhom.yml), and listsbooleanwhich no descriptor uses.README.mdsayssmtp_mappingis "Currently mapped: Vaultwarden, Mealie" — six descriptors now carry it (calcom, gitea, mealie, nextcloud, rallly, vaultwarden).- Healthcheck URL style drifts:
localhostvs127.0.0.1, trailing-slash vs none,curl -fvscurl -sf, and two distinct Node one-liner styles (compact rallly vs verbose docmost). - One unquoted generator value (
generate: password:24) among otherwise-quotedgenerate: "..."values. - The in-file comment block in
.felhom.ymlheaders (e.g.templates/paperless-ngx/.felhom.yml"Generator types") omitsbase64key:N, whichREADME.mddocuments and one app uses.