diff --git a/NEW-APP-CHECKLIST.md b/NEW-APP-CHECKLIST.md new file mode 100644 index 0000000..9909dea --- /dev/null +++ b/NEW-APP-CHECKLIST.md @@ -0,0 +1,119 @@ +# NEW-APP CHECKLIST — draft (reviewer, 2026-10-01) + +**What this is.** The list of things every new catalog app must have measured and recorded BEFORE it reaches the live +catalog. Each item exists because something went wrong on a real app — the "why" column names it. The filled-in copy +per app is the app's **onboarding record**; a gate refuses a new app without a complete one. + +**How to read an item.** Each answer is **done** (with an evidence path), **n/a** (with a one-line reason), or **open** +(blocks publishing). "Measured" means on the bench or on scratch guest 9202 through the product — never read from +upstream alone. Upstream reading is allowed to PLAN a test, never to CLOSE an item. + +**Where it lives (proposed — CC confirms):** the checklist in `app-catalog-felhom.eu/NEW-APP-CHECKLIST.md`; one record per +app in `app-catalog-felhom.eu/onboarding/.md` (outside `templates/`, so the box never syncs it — the controller copies +only `docker-compose.yml` and `.felhom.yml`, `sync.go:364`). The new app is tested from the drill catalog on 9202 and +reaches the live catalog only with a complete record. + +--- + +## 0. Fit — should we offer it at all? + +| # | Check | How | Why (what went wrong before) | +|---|---|---|---| +| 0.1 | Open-source licence; we pull the upstream image, never redistribute | read the repo | the business sells installation, not software | +| 0.2 | Upstream is alive: a release in the last 6 months, issues answered | read the repo | plant-it went `abandoned`, its image is gone | +| 0.3 | An official image with **version tags** (not `latest`-only), amd64 (+arm64 if Pi) | registry | pins and digests need real tags | +| 0.4 | **Telemetry / phone-home** — on by default? a switch to turn it off? | docs + one packet capture or log read on 9202 | data sovereignty is the pitch | +| 0.5 | Needs the internet at runtime (claim tokens, first-start downloads)? | docs + a first start | plex (plex.tv claim), adventurelog (world data), immich (geodata) | +| 0.6 | Needs ports other than HTTP(S)? The tunnel carries HTTP only | compose + docs | gitea SSH, media DLNA, game servers — say what the household loses | +| 0.7 | Phone / desktop apps: do they work through the tunnel and the setup gate? | docs + one real client if one exists | immich's phone app vs the gate (decision 46) | +| 0.8 | What a household gets from it, in one sentence (Hungarian) | — | the catalog card and `use_cases` | + +## 1. Images and the database + +| # | Check | How | Why | +|---|---|---|---| +| 1.1 | Every image pinned to a concrete tag; resolvable | `catalog_gates.py ` (gates 1, 2) | `:latest` breaks restore fidelity | +| 1.2 | Database engine and major = **what the app's own upstream compose runs** | upstream compose at the pinned tag | decision 42's rule; avoids an early conversion | +| 1.3 | MariaDB sidecar: `MARIADB_AUTO_UPGRADE=1`; PostgreSQL 18: mount at `/var/lib/postgresql` | compose | R-459; PG 18 refuses `/data` | +| 1.4 | The app migrates its own database at start, **or** the switch that makes it do so is set | an update on 9202, login read back | wger: no migration without `DJANGO_*` switch, update said "done", login 500 (R-738) | +| 1.5 | The app runs its production server, not a dev server | process list in the container | wger `runserver` (R-755) | +| 1.6 | Every secret the app needs is generated (no image default, no empty key) | compose + a login on 9202 | wger JWT key missing → login 500 (R-737) | + +## 2. Storage and backup + +| # | Check | How | Why | +|---|---|---|---| +| 2.1 | Every path the app writes is mounted — **measured** | gate 3, `check-volume-persistence.py ` | papra backed up an empty folder (R-156) | +| 2.2 | Where each path lives: named volume (NVMe) / `${HDD_PATH}/appdata` / `${USERDATA_PATH}` | compose, REUSE.md skeleton | the household can browse userdata, not appdata | +| 2.3 | Backup class for every HDD path (`mandatory` / `excluded` …) and `data_paths` labels | `.felhom.yml backup:` | DB rows that point at files need those files (07) | +| 2.4 | File owner / uid fits the box (PUID/PGID where the image wants them) | first start on 9202 | linuxserver images chown their tree | +| 2.5 | **A backup and a restore through the product, data read back** | 9202: seed → backup → remove → restore → read back | the promise is the restore, not the backup | +| 2.6 | "Remove with data" and "remove keep data" both work | 9202 | R-756 (refused with a folder present) | +| 2.7 | Off-site size estimate for a typical household | measured size after seed + a sentence | decision 50's size warning | + +## 3. Accounts and strangers + +| # | Check | How | Why | +|---|---|---|---| +| 3.1 | First-admin class (FIRST-ADMIN.md 1–6), **measured** | fresh install on 9202 | decision 45 | +| 3.2 | Known default login → `after_install` with a generated password (and name, if the name is public and lockable) | 9202: default fails, generated works, wrong fails | bookstack, calibre-web (decisions 45, 61) | +| 3.3 | Open first-run screen → `setup_gate` (+ probe that **flips**, measured before and after) | 9202 as a stranger | 32 apps, decision 46 | +| 3.4 | Open sign-up after the setup → `signup_block` / `after_setup` switch; case-insensitive | 9202 as a stranger | decisions 47–49 | +| 3.5 | The install window: a stranger reaches nothing before the password is replaced | poll once a second during install | R-741 | +| 3.6 | **Lock-out**: N wrong passwords by a stranger — who is locked (name / address / everyone), for how long | 9202 through traefik | mealie 24 h, wger everyone (R-747, R-752, R-753) | +| 3.7 | The household can change its password and add family members; the page says how | 9202 | `add_people` copy | +| 3.8 | Secrets pass to commands as arguments, never inside program code | review `after_install` | security review 2026-09-29 | + +## 4. Health + +| # | Check | How | Why | +|---|---|---|---| +| 4.1 | Compose healthcheck of the right family, `127.0.0.1` not `localhost` | REUSE.md §2 | vaultwarden IPv6 trap | +| 4.2 | The controller probe dials what the compose healthcheck dials; a real health path where one exists | gate 7 | a wrong probe stops a working app after an update (R-618) | +| 4.3 | Healthy within `start_period` on a **cold first start** (incl. one-time imports) | 9202, timed | immich geodata import | +| 4.4 | Negative control: a broken app reads unhealthy | stop the DB, read the status | a probe that is always green proves nothing | +| 4.5 | The probe-named container is the stack name; sidecars `-db` … | compose | `findProbeContainer` fallback picks the DB | + +## 5. Resources + +| # | Check | How | Why | +|---|---|---|---| +| 5.1 | **First start from birth, swap OFF**: peak `anon`, `oom_kill` = 0 | bench, sampled every 2 s | immich DB killed at 512 MiB, hidden by swap on 9202 (R-732, R-733) | +| 5.2 | 10-minute light-load soak: peak < 80 % of the limit, 0 kills, 0 restarts | harness memory watch | romm OOM at +76 s (decision 22) | +| 5.3 | `mem_limit` in `.felhom.yml` = the sum of the compose limits | gate / review | immich's header was 128 MB off | +| 5.4 | Node/Java apps: does the heap size itself from the limit? | two watches at two limits | R-693 (docmost) | +| 5.5 | Pi-compatible (yes/no), disk it pulls (image size) | registry | old images filled 9202 (R-736) | + +## 6. Updates + +| # | Check | How | Why | +|---|---|---|---| +| 6.1 | An upgrade fixture: seed + read-back **through the app's own front door**, negative control | `upgrade_fixtures*.py` | without it the app never updates itself | +| 6.2 | A first ladder step proven on bench + 9202 (`--write-ladder`), **or** "manual only" with the reason | harness | 35 of 53 update at night; the rest need a person | +| 6.3 | The undo works on a real failure (one forced-fail case) | 9202 | zipline's real failed jump, undone in 20 s | +| 6.4 | `files_may_change` mark understood (which files change at start) | bench | immich's six marker files (R-734) | +| 6.5 | Tag shape is stable upstream (no `v` dropped, no flavour prefix) | tag list | gramps-web, jellyfin, kimai (R-731) | + +## 7. Mail + +| # | Check | How | Why | +|---|---|---|---| +| 7.1 | Sends mail? → `smtp_mapping` + `${VAR:-}` compose lines; a fresh install with mail OFF boots | 9202 | vaultwarden empty-vars trap | + +## 8. Household-facing text and listing + +| # | Check | How | Why | +|---|---|---|---| +| 8.1 | Hungarian + English, informal „te", no „kérjük"; parity green; freeze updated | `check-copy-i18n.py` | operator rule | +| 8.2 | `app_info`: tagline, use_cases, first_steps, default_creds (if any), add_people | read on 9202's page | the page is the manual | +| 8.3 | Logo + screenshots on felhom.eu by `slug` | the asset URL answers 200 | the card is blank otherwise | +| 8.4 | README tables, FIRST-ADMIN row, `category`, `catalog_since` | review | REUSE.md §5 | + +## 9. Sign-off + +| # | Check | How | +|---|---|---| +| 9.1 | `python3 scripts/catalog_gates.py ` — all green (exit 0) | bench / scratch | +| 9.2 | A fresh install on 9202 from the drill catalog, as a household and as a stranger, start to finish | 9202 | +| 9.3 | Every item above done or n/a-with-reason; every finding that is not fixed is a register row | the record | +| 9.4 | Published to the live catalog in ONE commit with its record | the gate checks | \ No newline at end of file