FIRST-ADMIN.md: the 53-app first-admin audit (decision 45); CHANGELOG + REPORT
gates / gates (push) Successful in 1s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-28 18:59:24 +02:00
parent ccd17da3c4
commit a791aae057
3 changed files with 102 additions and 13 deletions
+13
View File
@@ -1,3 +1,16 @@
## No app goes live with a login a stranger knows — first two apps; the audit (2026-09-28 evening, `09` §3 decision 45)
- **`FIRST-ADMIN.md`** (new): how each of the 53 apps gets its first admin — class, fix route, status, and whether each
claim was measured or read. 2 fixed, 37 open (3 hard-coded defaults, 34 open first-run screens), 14 fine.
- **claper** (`9dc8a05`) and **bookstack**: a generated `ADMIN_PASSWORD` (`type: password`, shown on the app page as the
first password) and an `after_install:` command through the app's own CLI that replaces the known default
(`bin/claper rpc … update_user_password`; `artisan bookstack:create-admin --initial`). Needs controller ≥ 0.279.0 (the
floor). Proven on 9202 on a fresh install each: the default no longer logs in, the generated password does, a wrong
one does not; for claper a restore keeps it. First steps (hu + en) now say to sign in with the first password.
- **Copy freeze:** the new and changed Hungarian strings of both apps signed off (`--capture-freeze`, diff = those strings only).
- Found, not changed: romm's `default_creds` (`admin / admin`) does not log in on demo-hp (stale); zipline's looks
stale; calibre-web's route needs a generated password with a special character (its password policy).
## claper → PostgreSQL 17, calcom → 1536M and PostgreSQL 18 (2026-09-28, `09` §3 decision 43)
- **claper: PostgreSQL 16 → 17** (`4a249b9`, alone in its commit, written by `upgrade-test.py --write-ladder`). Its upstream
+82
View File
@@ -0,0 +1,82 @@
# FIRST-ADMIN — how each app gets its first admin account (the audit behind `09` §3 decision 45)
**The rule (operator, 2026-09-28): an app is never published with a login a stranger knows.** The box publishes every
app on the household's domain (`*.domain` through the tunnel). Where the box can set the first admin password, it
generates one at install and shows it on the app page. Where it cannot, the app stays in the catalog, and the install
dialog and the app page say what the default login is and to change it at once.
**Mechanisms (controller ≥ 0.279.0):**
- `after_install:` in `.felhom.yml` — one command in the app's own container after a FRESH install, with generated
deploy values filled in; `success:` marker required. Never after a restore or a kept-data load. (`internal/stacks/after_install.go`)
- The page's default-login rule — `app_info.default_creds` is shown, with the sentence „Ez az alkalmazás egy ismert,
közös jelszóval indul: %s. Telepítés után azonnal változtasd meg." / "This app starts with a known, shared password:
%s. Change it right after the install.", while that login is in effect; hidden once `after_install` replaced it.
(`internal/web/known_login.go`)
**Classes:** 1 generated by us at install · 2 set by the household in our dialog · 3 a hard-coded default · 4 an open
first-run screen (the first visitor creates the admin) · 5 no login by design · 6 unknown.
**Sources:** **M** = measured on a box (named) · **R** = read in this catalog · **U** = upstream, read or remembered,
NOT measured. Every U must be measured before a fix is built on it.
**Status 2026-09-28:** 2 apps fixed (claper, bookstack). 37 apps of class 3/4 remain (3 of class 3, 34 of class 4) — they are the work of the next
sessions (R-707). Until each is fixed, a class-3 app shows the sentence (its `default_creds` is in the catalog); a
class-4 app has no default to show, so its risk is the window until the household opens it first.
| app | class | how the first admin exists | fix route | status | source |
|---|---|---|---|---|---|
| actualbudget | 4 | first visitor sets the server password | (b) `POST /account/bootstrap` | open | R, harness fixture |
| adventurelog | 4 | open sign-up | (a) `DJANGO_ADMIN_*` env; (b) `createsuperuser --noinput` | open | R; U (env) |
| audiobookshelf | 4 | first visitor creates root | (b) `POST /init` | open | R, fixture |
| bentopdf | 5 | browser-only PDF tool, no accounts | – | fine | R |
| **bookstack** | 3 | `admin@admin.com / password` | (b) `artisan bookstack:create-admin --initial` | **FIXED** — catalog, decision 45 | **M 9202**: default fails, generated works; **M demo-hp**: default still works on the installed app (unchanged, page warns) |
| calcom | 4 | first visitor becomes admin (`/api/auth/setup`) | (b) `POST /api/auth/setup`; (a) `NEXT_PUBLIC_DISABLE_SIGNUP` | open | **M 9202** (setup 200 once, then 400) |
| calibre-web | 3 | `admin / admin123` | (b) `cps.py -p /config/app.db -s admin:<pw>` — **needs a password with a special character** (its policy), our generator has none | open — needs a controller generator | **M 9202** (default works; `-s` refused an alphanumeric one); **M demo-hp**: default works (page warns) |
| **claper** | 3 (+ open sign-up) | seeds `admin@claper.co / claper` | (b) `bin/claper rpc … update_user_password` | **FIXED** — catalog, decision 45 | **M 9202**: default fails, generated works, a restore keeps it |
| code-server | 1 | `PASSWORD` generated, applied every start | – | fine | R |
| crafty-controller | 1 | `CRAFTY_PASSWORD` → default.json | – | fine | R |
| docmost | 4 | first registered user is admin | (b) `POST /api/auth/setup` | open | R, fixture |
| emby | 4 | setup wizard | (b) `/Startup/*` API | open | U |
| ghost | 4 | `/ghost/` setup | (b) `POST /ghost/api/admin/authentication/setup/` | open | U |
| gitea | 4 | web installer open (no `INSTALL_LOCK`) | (a) `INSTALL_LOCK` + (b) `gitea admin user create` | open | R; R-624 |
| glance | 5 | config-file dashboard, no users | – | fine | R |
| gokapi | 1 | `GOKAPI_PASSWORD` before first serve | – | fine | R |
| grafana | 1 | `GF_SECURITY_ADMIN_PASSWORD` — **falls back to `admin` if empty** (R-708) | – | fine while the field is set | R |
| gramps-web | 4 | first-run onboarding | (b) `python3 -m gramps_webapi user add` | open | U |
| home-assistant | 4 | onboarding | (b) `POST /api/onboarding/users` | open | R, fixture |
| homebox | 4 | open registration | (b) register API; (a) disable registration after | open | U |
| homepage | 5 | static start page | – | fine | R |
| immich | 4 | first visitor admin sign-up | (b) `POST /api/auth/admin-sign-up` | open | U |
| jellyfin | 4 | startup wizard | (b) `/Startup/*` | open | U |
| kimai | 1 | `ADMIN_PASSWORD` → `ADMINPASS` | – | fine | R |
| komga | 4 | first visitor claims | (b) `POST /api/v1/claim` | open | U |
| mealie | 3 | `changeme@example.com / MyPassword` | (b) login + users API | open | R; fixture (login works) |
| n8n | 4 | owner setup | (b) `POST /rest/owner/setup` | open | R, fixture |
| navidrome | 4 | first user is admin | (a) `ND_DEVAUTOCREATEADMINPASSWORD`; (b) `/auth/createAdmin` | open | R, fixture; U (env) |
| nextcloud | 1 | `NEXTCLOUD_ADMIN_PASSWORD` → auto-install | – | fine | R; **M** demo-hp 2026-09-28 |
| onlyoffice | 5 | JWT-protected API, no login screen | – | fine | R |
| opengist | 4 | first registered user is admin | (b) `POST /register`; (a) `OG_DISABLE_SIGNUP` | open | R, fixture |
| outline | 4 | e-mail / magic-link onboarding | (c) none found (R-624) | open — page note needed | R |
| paperless-ngx | 1 | `PAPERLESS_ADMIN_PASSWORD` | – | fine | R |
| papra | 4 | open e-mail sign-up | (b) sign-up API; (a) disable registration | open | R, fixture |
| plant-it | 4 | open, `USERS_LIMIT=-1` | (a) `USERS_LIMIT=1` + (b) sign-up | open | U |
| plex | 2 | the household's plex.tv claim token | – | fine | R |
| privatebin | 5 | anonymous pastes by design | – | fine | R |
| radarr | 4 | first visitor sets auth | (b) `PUT /api/v3/config/host` with the apikey | open | U |
| rallly | 4 | magic link to any e-mail | (a) `INITIAL_ADMIN_EMAIL` + `ALLOWED_EMAILS` | open | U |
| recipe-importer | 4 | our own image, open until set | (c) now; our own code | open | R |
| romm | 4 (catalog said 3) | catalog note `admin / admin` is **stale**: on demo-hp it answers 401 like a wrong password; a first unauthenticated `POST /api/users` created the user (harness) | (b) `POST /api/users` | open — **the page warns with a login that does not exist** | **M demo-hp** (401); fixture |
| seerr | 4 | setup wizard (needs a media server) | (c) | open | U |
| sonarr | 4 | as radarr | (b) | open | U |
| sparkyfitness | 4 | open registration | (a) `SPARKY_FITNESS_ADMIN_EMAIL` + disable sign-up | open | U |
| tandoor | 4 | setup page while no users | (b) `createsuperuser --noinput` | open | R, fixture |
| termix | 4 | first registered user is admin | (b) user-create API | open | U |
| uptime-kuma | 4 | first visitor creates admin | (b) socket.io `setup` | open | U |
| vaultwarden | 1 | `ADMIN_TOKEN` generated; invite-only (R-512) | – | fine | R |
| vikunja | 4 | open registration | (b) `vikunja user create`; (a) disable registration | open | R, fixture |
| wanderer | 4 | `PUBLIC_DISABLE_SIGNUP=false` | (a) disable after first user | open | U |
| wger | 3 | `admin / adminadmin` | (b) `manage.py shell -c` set_password | open | R |
| wishlist | 4 | first sign-up is admin | (b) `POST /signup` | open | R, fixture |
| zipline | 4 (catalog said 3) | catalog note `admin / zipline` looks **stale** (v4 sets up on first run) | (b) setup API (U) | open | R; audit logs |
**Counts (computed from the table):** class 1: 8 · class 2: 1 · class 3: 5 (bookstack, calibre-web, claper, mealie, wger;
romm and zipline were listed as 3 and are 4) · class 4: 34 · class 5: 5. **Fixed: 2.** **Open: 37.** Fine: 14.
+7 -13
View File
@@ -1,17 +1,11 @@
# REPORT — 2026-09-28: claper → PostgreSQL 17; calcom → 1536M and PostgreSQL 18
# REPORT — 2026-09-28 evening: no app goes live with a login a stranger knows (decision 45) — the audit and the first two apps
Brief: "golden, Day-0 vouch, kept data from off-site…" Part C. Architecture read: `09-update-architecture.md` §3
decisions 35–42 (42's reasoning applied as decision 43).
Architecture: `09-update-architecture.md` §3 decision 45 (new); `01-topology-and-trust.md` links the audit.
| app | move | bench (LXC 9401, demo-hp) | box 9202 (guarded Update) | result |
| app | before | route | proof (9202, fresh install, controller 0.279.0) | live |
|---|---|---|---|---|
| claper | postgres 16 → 17 | proven — converted 30.8 s, 32 tables equal, seed back, peaks 75.8 % / 78.3 %, 0 kills | proven — CONVERTED in 7.2 s, done 47 s, PG_VERSION 17, seed back | live catalog `4a249b9`, engine gate ALLOWED |
| calcom | memory 768M → 1536M (`9555e73`) | — | 768M OOM-killed every start; 1536M anon peak 817 MiB (53 %) | fixed, R-703 closed |
| calcom | postgres 16 → 18 | proven — converted 35.0 s, 122 tables equal, seed back, anon 61.5 %, 0 kills | proven — converted, done 68 s, PG_VERSION 18, seed back | live catalog `037f956`, engine gate ALLOWED |
| claper | seeds admin@claper.co / claper (measured: authenticates) | `bin/claper rpc` update_user_password | default fails · generated works · wrong fails · restore keeps it | `9dc8a05` |
| bookstack | admin@admin.com / password (measured, also on demo-hp) | `artisan bookstack:create-admin --initial` | default fails · generated works · wrong fails | this commit |
| calibre-web | admin / admin123 (measured, also on demo-hp) | `cps.py -s` refuses an alphanumeric password | — | open: needs a special-character generator |
- Fixture: claper `rpc register_user` + authenticate readback (negative control: wrong password refuses) — `c5015ce`.
- calcom fixture: `POST /api/auth/setup` + public page readback (negative control: unknown name 404) — `b35fc7f`.
- New rows: R-702 (claper default admin `admin@claper.co`/`claper`, P1, open), R-703 (calcom OOM at 768 MB — closed by `9555e73`), R-704 (a crash-loop hold survives remove + reinstall).
- Bench LXC 9401 created twice and destroyed twice (hostname checked), template removed; `local-lvm` 55.6 % at teardown.
Evidence: `felhom.eu/documentation/audits/pg-calcom-claper-2026-09-28/`.
The full table: `FIRST-ADMIN.md`. Evidence: `felhom.eu/documentation/audits/logins-nvme-2026-09-28/B/`.