# REPORT — Add SparkyFitness to the app catalog (+ live deploy & health check) **Date:** 2026-06-28 **Task class:** Implementation — pure catalog addition (no Go/controller/agent/hub code change). **Outcome:** ✅ Template added, synced, deployed live on guest 9201, all 3 containers healthy, end-to-end `/api` path verified through the public edge. One acceptance item (interactive account register/login) was **deliberately left for a human** — see "NOT validated" below. --- ## 1. Confirmed baselines | Repo | Baseline | After | |------|----------|-------| | app-catalog-felhom.eu | `main` @ `d7a6a17` (working tree clean; `templates/sparkyfitness/` was 404) | template commit `4351d08`, docs commit (this push) | | felhom-controller (running on demo) | **v0.87.0** (`gitea.dooplex.hu/admin/felhom-controller:0.87.0`, healthy on guest 9201) | unchanged — template proven-compatible against 0.87.0 | The running controller renders this template's `type: subdomain` + `data_key` fields correctly (deploy screen showed Domain auto-filled, Subdomain `sparky`, and all 4 auto-secrets) — so **no STOP condition**; no controller patch was needed. ## 2. Files created - `templates/sparkyfitness/docker-compose.yml` - `templates/sparkyfitness/.felhom.yml` Commits on `main`: - `4351d08` — the two template files (after lint + image probes). - (this push) — CHANGELOG + README + REPORT docs. `templates.json` (legacy Portainer index) intentionally left untouched. No other catalog apps modified. ## 3. §1.3 image healthcheck-tooling findings (verified on build server 192.168.0.180, v0.17.2) | Image | Probe authored | Finding | Final probe kept | |-------|----------------|---------|------------------| | `codewithcj/sparkyfitness_server:v0.17.2` | `node -e` GET `/api/health` | `NODE_OK` (node on PATH via `--entrypoint node`) | **kept** the node-exec probe | | `codewithcj/sparkyfitness:v0.17.2` (frontend) | `wget --spider` | both present: `/usr/bin/wget` **and** `/usr/bin/curl` | **kept** the `wget --spider` probe | No probe changes were required. Compose lints clean (`docker compose config -q` with the 4 `${...}` vars set to dummies → `LINT_OK`). ## 4. Deploy method + evidence **Method:** real UI flow via **claude-in-chrome** (no hand-written `app.yaml`). 1. `https://felhom.demo-felhom.eu` → Alkalmazások → **"Sablonok frissítése"** (sync). App count 55→56, Telepíthető 52→53; SparkyFitness card appeared (`/stacks/sparkyfitness/deploy`). 2. Deploy screen: Domain auto = `demo-felhom.eu`; Subdomain default `sparky`; 4 secrets shown "Automatikusan generálva"; memory gate **696 MB / 1664 MB (41%)** → fits, **not refused** (Scenario C did not trigger). Clicked **"Telepítés indítása"** → "Konfiguráció mentve" → containers started. **`docker ps` (guest 9201):** ``` sparkyfitness-frontend codewithcj/sparkyfitness:v0.17.2 Up (healthy) sparkyfitness-server codewithcj/sparkyfitness_server:v0.17.2 Up (healthy) sparkyfitness-db postgres:15-alpine Up (healthy) ``` **Server log excerpts (clean — Scenario A):** ``` [INFO] Successfully applied migration: ...20260622120000_add_water_estimated_to_exercise_entries.sql [INFO] Ensuring permissions for role: "sparkyapp" [INFO] Successfully ensured permissions for role: "sparkyapp" [INFO] Permissions granted to application user. [INFO] Successfully applied all RLS policies. [AUTH] Better Auth handler successfully mounted. [INFO] SparkyFitnessServer listening on port 3010 [INFO] Incoming request: GET /api/health ``` No crash loop; **no EACCES** on `/app/SparkyFitnessServer/uploads`. Both DB roles confirmed present in the DB (`SELECT rolname ... → sparky, sparkyapp`). **Secrets (Scenario B) — verified without exposing values** (from `docker inspect` env of the server): - `SPARKY_FITNESS_API_ENCRYPTION_KEY` length = **64** (hex:32 ✓) - `BETTER_AUTH_SECRET` length = **64** (✓) - `SPARKY_FITNESS_FRONTEND_URL = https://sparky.demo-felhom.eu` (public https ✓ — CORS/Better-Auth origin) - `SPARKY_FITNESS_DB_USER = sparky`, `SPARKY_FITNESS_APP_DB_USER = sparkyapp` (two distinct roles ✓) - `SPARKY_FITNESS_DISABLE_SIGNUP = false` (household can register ✓) All four secrets are `locked_after_deploy`; the two crypto keys carry `data_key: true`. **Browser checks:** - `https://sparky.demo-felhom.eu/login` → SparkyFitness **native email/password login + Sign Up** page loads over a **valid TLS cert** (no warning). No OIDC buttons (correct for the customer template). - `https://sparky.demo-felhom.eu/api/health` (through the public Cloudflare edge → Traefik → frontend nginx `/api` proxy → server) → **`{"status":"UP"}`**. This proves the frontend→server proxy + public routing + TLS end-to-end. ## 5. NOT yet validated (explicit) - **Interactive account register + login.** Deliberately not performed by me: creating an account / entering credentials to authenticate falls under a browser-safety guardrail (prohibited even on request). Everything up to the authenticated API path is proven (healthy server, `/api/health` proxied through the public edge, CORS origin correctly set). **Recommended human step:** open `https://sparky.demo-felhom.eu`, click **Sign Up**, register a throwaway account, confirm login reaches the dashboard (≈30s). Expected to pass given the verified config. - **Backup / restore of this app** (incl. the `data_key` recovery path for the two crypto keys) — not exercised here. - **Upload body-size limits** for profile/exercise images (the k3s ingress set `proxy-body-size: 50m`; the Felhom Traefik path was not size-tested). - **Garmin / MCP integrations** — out of scope (commented out upstream; not enabled). ## 6. Observations (noticed, not acted on) - Upstream's compose uses `postgres:18` + `:latest`; per the validated k3s manifest we pinned `postgres:15-alpine` with an explicit `PGDATA` subdir — kept that proven path. - The frontend image ships **both** wget and curl; the server image runs node as a valid exec entrypoint — both healthchecks are robust without extra tooling. - Demo guest 9201 has only 2 GB total RAM; SparkyFitness `mem_limit` is 1792M. The `mem_request` (400M) gate passed and real usage is far below the limit, but on a 2 GB box co-tenancy headroom is thin — worth noting for nodes running several heavier apps.