28 KiB
Changelog
2026-07-19 — docs: workspace-root pointer follows the CC move to DooPlex
Docs only, no template change. Claude Code now runs on DooPlex (192.168.0.180, Debian 13)
instead of the Windows workstation. CLAUDE.md's cross-repo pointer becomes
/mnt/5_hdd/felhom.eu/git/CLAUDE.md. This repo carried no other environment-specific content —
it was the only one of the four that needed nothing else.
2026-07-19 — CAMPAIGN 7: full catalog sweep (53/53 apps deployed + validated on the demo box)
Every app in the catalog was bumped to its newest stable upstream tag where one existed, then
actually deployed through the controller's real endpoints on the demo box (controller 0.146.0),
validated (all containers healthy, HTTP through the real Traefik ingress, log scan), and removed
again through the real delete flow. Full evidence + result matrix:
felhom.eu/documentation/audits/CAMPAIGN-7-catalog-sweep-2026-07-19.md.
Result: 45 apps pass end-to-end, 4 do not, 1 is not automatable (plex needs a real PLEX_CLAIM).
Version bumps — ~40 templates moved to current upstream, 15 of them across a major
(bookstack 25.02→26.05, immich v2→v3, calcom v4→v6, nextcloud 31→34, grafana 11→13, n8n 1→2,
outline 0.82→1.9, vikunja 0.24→2.3, tandoor 1→2, romm 4→5, radarr 5→6, privatebin 1→2,
onlyoffice 8→9, claper 1→2, gramps-web v24→v25). uptime-kuma moved off the floating :2 tag
to 2.4.0. DB/cache sidecar majors were deliberately NOT bumped — rationale in the campaign
doc §4 (a DB major is the application's decision, and postgres:16-alpine already tracks 16.x).
13 template fixes, every one live-re-validated:
- 7 broken healthchecks. This is not cosmetic: Traefik will not route to an
unhealthycontainer, so a probe that cannot run makes the app return 404 to the customer while it serves 200 on its own port. adventurelog (wget in a distroless image → Node-exec at an absolute path), emby (curl absent, BusyBox only), papra + wishlist (node-only images), homebox (--spidersends HEAD, endpoint answers 405 to HEAD / 200 to GET), zipline (v4 renamed/api/health→/api/healthcheck), tandoor (start_periodtoo short for gunicorn). - 5 apps that had NEVER been deployable and were fixed: papra (missing required
AUTH_SECRET, now a generateddata_keysecret), zipline (v4CORE_DATABASE_URL→DATABASE_URL), wishlist (dead Docker Hub image → followed upstream toghcr.io/cmintey/wishlist:v0.66.0), homebox (upstream dropped thevtag prefix + new requiredHBOX_AUTH_API_KEY_PEPPER), wger (2.6 needs the fullDJANGO_DB_*set and listens on :8000, not :80 — the Traefik port was wrong too). - 4 memory/OOM corrections proven by a live OOM: gramps-web 384M→1024M, n8n 512M→1536M
(V8 heap), rallly 256M→768M, tandoor 512M→1024M (+ its
mem_limitsum was already wrong). - gokapi reverted v2.2.4 → v1.9.6: v2 refuses to run against the seeded ConfigVersion-21 config and demands an intermediate v2.0.0 pass, even on a fresh deploy. Shipping it would have broken every new gokapi deploy. Needs a dedicated v2 config-migration task.
Still failing (recorded, not fixed): glance (needs a seeded glance.yml; PROVEN pre-existing —
the pre-campaign v0.7.4 pin fails identically), gokapi (above), plant-it and wanderer
(their images do not resolve at all — neither the new tag nor the one the catalog already shipped).
2026-07-14 — backup classification backup: blocks for the 13 bind-bearing apps (controller v0.132.0)
Adds the referential-coupling backup: classification block to every catalog app that binds
${HDD_PATH}/${USERDATA_PATH} (13 apps: immich, paperless-ngx, nextcloud, calibre-web,
audiobookshelf, komga, navidrome, radarr, sonarr, emby, jellyfin, plex, romm). Each block lists its
userdata:/hdd: binds with a class ∈ {mandatory, optional, excluded} (COUPLED /
DECOUPLED-precious / DECOUPLED-bulk); classes are operator-ruled (Viktor, 2026-07-14) + spike SQ2.
Requires controller v0.132.0, which parses + validates these blocks (Task 2 of the
backup-classification-redesign arc, felhom.eu/documentation/audits/SPIKE-backup-classification-2026-07-14.md).
The classification is INERT — no backup tier changes behavior yet; Task 3 (tier policy engine)
and Task 4 (manual .fab UI) consume it. All 13 blocks were verified against the shipped controller
parser: parse-clean, every bind resolves explicit to its ruled class (zero validation errors).
Notes: audiobookshelf media/audiobooks = optional (consistency with komga/romm curated media;
the spike proposed excluded — PENDING a Viktor veto). radarr/sonarr downloads = excluded (transient
cross-app queue). emby/jellyfin/plex media = excluded (:ro readers; state in volumes). The 42
volume-only apps get no block (classification moot — state rides in the recovery unit's volume dumps).
2026-07-12 — image pinning sweep: :latest eliminated from all templates (5 pins) + standing gate
A catalog sweep found 5/53 templates with unpinned images. Beyond version discipline, :latest
breaks restore fidelity: the controller's recovery-unit ImagePins pins the tag, so restoring a
:latest app re-pulls whatever :latest means at restore time — potentially schema-incompatible
with the data being restored. Rule applied: a deployed app pins to the digest it is RUNNING
(pin ≠ upgrade); undeployed apps pin to the verified upstream stable. All five pins are
digest-identical to what :latest resolved to on 2026-07-12 — a pure no-op for running apps.
| App | Old | New | Evidence |
|---|---|---|---|
| bentopdf | ghcr.io/alam00000/bentopdf:latest |
:v2.8.6 |
digest == latest (eaeea1e4…); undeployed |
| calibre-web | crocodilestick/calibre-web-automated:latest |
:v4.0.6 |
digest == RUNNING image on demo 9201 (c31a738b…) |
| papra | ghcr.io/papra-hq/papra:latest |
:26.6.1-rootless |
latest == the -rootless variant (a7a42e22…); -root differs — variant preserved |
| recipe-importer | gitea.dooplex.hu/admin/recipe-importer:latest |
:v0.9.11 |
tag pre-existed in registry, digest == latest (f3cb617c…) — no retag needed |
| termix | ghcr.io/lukegus/termix:latest |
:2.5.0 |
digest == latest == release-2.5.0 (4d337131…); undeployed |
- New rerunnable gate
scripts/check-image-pins.py: fails on:latest/dev/nightly/edge/main/masterAND on untagged image refs (implicit :latest);@sha256:digests count as pinned. Red-proofed both shapes (revert→exit 1→restore). - Standing rule added to
CLAUDE.md(never :latest / untagged; deployed apps pin to running digest). templates.jsoncarries no image strings (legacy metadata only) — untouched.- Fleet caveat: non-deployment of bentopdf/papra/termix verified on demo 9201 only; felhotest unreachable + Peti's box offline at sweep time (operator approved proceeding — pins are digest-equal to latest, so worst case equals the status quo).
2026-07-06 — healthcheck sweep: localhost → 127.0.0.1 across all 48 templates
Escalation of the re-run vaultwarden observation
(felhom.eu/documentation/audits/RERUN-p1p3-2026-07-06.md) from an instance to a class: 48/53
templates used localhost in their docker healthcheck test: line. BusyBox wget (and the node /
python / curl one-shot forms, incl. mealie's socket.create_connection) resolve localhost→IPv6
::1 with no cross-address-family fallback, so an IPv4-only-binding app reads docker-unhealthy
while fully serving. Mechanical sweep localhost→127.0.0.1, scoped strictly to the healthcheck
test: lines (diff-reviewed: no app env/config/label line changed; .felhom.yml files were already
clean). Industry practice — never localhost in container healthchecks. New REUSE.md convention row.
2026-07-06 — vaultwarden F1 fix: _ENABLE_SMTP boot-gate (campaign finding, pilot-blocking)
The no-mercy campaign (felhom.eu audits/CAMPAIGN-nomercy-2026-07-06.md, finding F1) proved that a
FRESH vaultwarden deploy with app-email off — the default state — crash-loops: the template always
defines SMTP_HOST=${SMTP_HOST:-} / SMTP_FROM=${SMTP_FROM:-}, and vaultwarden treats a
defined-but-EMPTY env var as "set", so its config validation (smtp_host.is_some() == smtp_from.is_empty()) errors out and the process exits. The old comment ("empty SMTP_HOST = mail
stays disabled") was wrong for this image. Empirically proven on the pinned
vaultwarden/server:1.33.2-alpine (probe P1: defined-empty pair → exact campaign error, exit 12;
P2: _ENABLE_SMTP=false + same empty pair → boots; P3: _ENABLE_SMTP=true + host+from → boots).
Fix: gate the whole SMTP group with vaultwarden's own _ENABLE_SMTP flag — compose default
false (validation skipped, mail off, clean boot), flipped to "true" by the app-email injection
via smtp_mapping.extra (no controller change needed — extra already rides smtpEnv). The ON
path is byte-identical to the previously send-tested state plus the flag.
Sweep note (no edits): the other five smtp-mapped templates (calcom, gitea, mealie, nextcloud,
rallly) are boot-proven tolerant of defined-empty mail env — all ran healthy as fresh email-off
deploys during the campaign; gitea's GITEA__mailer__SMTP_ADDR=${...:-} pattern likewise.
Vaultwarden was the only strict image. New REUSE.md trap row: strict images need an enable-flag
gated false in compose + "true" in smtp_mapping.extra; boot-prove fresh email-off deploys.
2026-07-03 — sparkyfitness FINALIZED + live-validated (both VERIFY markers resolved); REUSE probe-naming row
The first worked example of the new felhom-app-catalog skill (felhom.eu). Both
VERIFY-BEFORE-FINALIZE healthcheck guesses resolved by inspecting the real images on the demo box:
frontend (Alpine/nginx) HAS BusyBox wget → drafted wget --spider :80/ probe confirmed + kept;
server HAS node v24.17.0 → node-exec :3010/api/health probe confirmed (path proven live:
{"status":"UP"}). Frontend container_name renamed → sparkyfitness (= the stack name): the
controller-side probe dials the exact-name container, fallback is the FIRST prefix match (could be
the DB) — new REUSE.md §2 "Probe-container naming" row records the convention (verified in
felhom-controller healthprobe.go). Mem-sum comment added (512+1024+256 = 1792M, value unchanged).
Live-validated on demo via the real dashboard UI (sync + Frissítés): 3/3 containers healthy,
controller probe healthy: true (http :80 → 200), sparky.demo-felhom.eu 200 via Traefik;
data_key secrets untouched (server/db containers not recreated). Kept deployed.
2026-07-03 — docs: CLAUDE.md light expansion
The minimal REUSE-rollout stub expanded to a proper (still ~30-line) CLAUDE.md: what the repo is
(one dir per app, two template files, Hungarian customer text), the push-to-main = deploy contract
(controller sync ≤15 min / manual trigger), legacy templates.json warning, and pointers
(REUSE.md, README format spec, the felhom-build-deploy skill). No template changes.
2026-07-03 — docs: REUSE.md introduced
Cross-repo reuse-map rollout (docs-only). New REUSE.md: catalog conventions verified against all
53 apps — the canonical example app (paperless-ngx), .felhom.yml required fields, healthcheck
family per image type (BusyBox wget / curl / Node / Python / DB sidecars), memory-limit convention,
new-app checklist, and traps (gokapi entrypoint hack, legacy templates.json). Known README drift
recorded in §6 (NOT fixed). Also a minimal CLAUDE.md carrying the REUSE.md pointer + maintenance
rule (full CLAUDE.md is a separate task).
2026-06-29 — App-email: calcom + nextcloud (tls_mode=plaintext :2526 + nextcloud split-From)
- nextcloud —
smtp_mappingwithtls_mode: plaintext(controller injects port 2526, the plaintext-only listener) + split From (from_var=MAIL_FROM_ADDRESS+from_domain_var=MAIL_DOMAIN→ nextcloud@felhom.eu). Compose references the injected${SMTP_*}/${MAIL_*}. Live-confirmed: real password-reset delivered via plaintext :2526 (Symfony Mailer never attempted STARTTLS). - calcom —
smtp_mappingwithtls_mode: plaintext(EMAIL_SERVER_HOST/PORT, EMAIL_FROM=calcom@felhom.eu). Plus three pre-existing template fixes (calcom never deployed before — the image pin was invalid): (1) imagev4.8.7→v4.6.9(the pinned tag has no published image); (2) added requiredDATABASE_DIRECT_URL(Prismamigrate deployfails without it → incomplete schema → 500s); (3) healthcheck/api/health→/api/auth/providers(the old path 404s in v4.x → container stayed unhealthy → Traefik wouldn't route). - Both apps point at the controller's
:2526plaintext-only listener because their SMTP clients opportunistically STARTTLS-upgrade and can't skip the self-signed cert — the listener simply doesn't offer STARTTLS, so they stay plaintext (accepted on the single-tenant app bridge).
2026-06-29 — App-email rollout: gitea + rallly (calcom/nextcloud/immich = findings)
- gitea 1.23.4 — added
smtp_mapping(STARTTLS viaGITEA__mailer__PROTOCOL=smtp+starttls+FORCE_TRUST_SERVER_CERT=trueto trust the shim's self-signed cert; singleGITEA__mailer__FROM). Compose references the injectedGITEA__mailer__*keys; env applied every boot. - rallly — added
smtp_mapping(Nodemailer STARTTLS,SMTP_SECURE=false+SMTP_REJECT_UNAUTHORIZED=falseto accept the self-signed cert; singleNOREPLY_EMAIL). Also fixed three pre-existing template bugs that made rallly undeployable (never caught because the bad pin never ran): (1) image pin3.12.1doesn't exist →3.11.2; (2) healthcheck usedwget, absent from the rallly image (exit 127) → container unhealthy → Traefik wouldn't route it → replaced with a Node http check; (3) added requiredSUPPORT_EMAIL+ a validNOREPLY_EMAILdefault (rallly refuses to boot without them). - Both gitea and rallly send-tested live end-to-end (app → shim → hub → Resend): gitea password-reset
(From
gitea@felhom.eu) and rallly registration code (Fromrallly@felhom.eu) both delivered. - NOT wired — reported as findings (
felhom.eu/documentation/audits/FINDING-app-email-rollout-2026-06-29.md):- cal.com v4.8.7 — hard-codes TLS
rejectUnauthorized:truewith no override; opportunistic STARTTLS against the self-signed shim fails. Needs a non-STARTTLS-advertising plaintext listener (mechanism change). - nextcloud 31 — no cert-skip env (same opportunistic-STARTTLS gap) and a split From
(
MAIL_FROM_ADDRESS+MAIL_DOMAIN) the single-from_varmapping can't express. - immich v2.5.5 — no SMTP env vars at all; config is admin-UI/DB or an
IMMICH_CONFIG_FILEJSON. Does not fit env-injection; left for a future config-file-injection mechanism (or manual admin-UI setup).
- cal.com v4.8.7 — hard-codes TLS
2026-06-29 — App-email: smtp_mapping for Vaultwarden + Mealie
- Added the
smtp_mappingblock totemplates/vaultwarden/.felhom.ymlandtemplates/mealie/.felhom.yml, enabling managed outbound email (app → in-controller shim → hub → Resend) for the two spike-proven apps (SPIKE-smtp-app-relay-2026-06-28). The controller injectsSMTP_*at deploy/redeploy when app-email is on (global + per-app); the From address is<app>@felhom.eu. SMTP auth creds are intentionally left unset (the shim accepts no-auth on the Docker network).- Vaultwarden: STARTTLS (
SMTP_SECURITY=starttls) +SMTP_ACCEPT_INVALID_CERTS/HOSTNAMES=trueto accept the shim's self-signed cert. - Mealie: plaintext (
SMTP_AUTH_STRATEGY=NONE) on :2525 — Mealie has no accept-invalid-cert option, so STARTTLS to a self-signed shim would fail; plaintext to the Docker-network-only shim is the spike-validated mode.
- Vaultwarden: STARTTLS (
- Both
docker-compose.ymlfiles now reference the injected${SMTP_*}keys (with harmless defaults) so the values reach the container; emptySMTP_HOSTkeeps mail disabled when the toggle is off. - Documented the
smtp_mappingpattern inREADME.mdso further apps are easy adds.
2026-06-28 — Add SparkyFitness (v0.17.2) — nutrition/workout tracker
- New app
templates/sparkyfitness/{docker-compose.yml,.felhom.yml}: a self-hosted nutrition/calorie + workout/weight tracker (alternative to wger). Three containers — nginx frontend (SPA :80, the sole Traefik ingress, proxies/api+/uploadsinternally) + Node server (:3010) + dedicated postgres:15-alpine. Server + DB stay on the internal network with no Traefik labels. - Native email/password auth (no OIDC/Authentik — that's DooPlex-specific); subdomain
sparky(deliberately ≠ wger'sfitnessto avoid a Host() collision).pi_compatible: false,needs_hdd: false. - Two DB roles:
sparky(POSTGRES superuser, runs init/migrations) +sparkyapp(limited app role the server auto-creates on first boot) — separateDB_PASSWORD/APP_DB_PASSWORD.PGDATAin apgdatasubdir of the named volume. Four auto-generated,locked_after_deploysecrets;API_ENCRYPTION_KEY+BETTER_AUTH_SECRETcarrydata_key: true(restore recovers, never regenerates — both are 64-char hex). - Transcribed from the validated k3s manifest
homelab-manifests/workout-system/sparkyfitness.yaml(pinned image tags, two-DB-role model, never-change crypto keys,/api/health, pg15 + PGDATA subdir). - Image-probe findings (build server, v0.17.2): server keeps the
node -e/api/healthprobe (node present); frontend keeps thewget --spiderprobe (bothwgetandcurlpresent). No probe changes needed. - Live-validated on guest 9201 (controller v0.87.0): synced via "Sablonok frissítése"; deployed through
the real dashboard flow (Domain auto, Subdomain
sparky, 4 secrets auto-gen). All 3 containers healthy; server log shows clean migrations +sparkyapprole created + RLS applied, no crash loop, no uploads EACCES;GET /api/healththrough the public edge returns{"status":"UP"}; login/register page serves over a valid TLS cert athttps://sparky.demo-felhom.eu.
2026-06-26 — crafty-controller: image bump 4.4.8→4.10.7 + publish Java port range + connection guidance
- Image bump
crafty-4:4.4.8→4.10.7(latest stable; 4.10.8/4.11.0 don't exist in the registry). 6 minor versions of fixes incl. security CVEs. Java 25 verified present in 4.10.7 (/usr/lib/jvm/java-25-openjdk-amd64, defaultjava -version= openjdk 25.0.3; 8/11/17/21 also available) — so the latest-Minecraft (26.x, needs Java 25) blocker is resolved. Healthcheck + Traefik https-backend labels unchanged (Crafty still serves HTTPS on 8443). - Published the Java game-port range
25565-25575:25565-25575(TCP, 11 ports = up to 11 Java servers; first server 25565, rest 25566–25575). Nonetwork_mode: host(would break Traefik routing). Bedrock UDP 19132 intentionally out of scope. - App-page guidance (
.felhom.ymlfirst_steps + prerequisites): how to set the server port within 25565–25575, how to connect on the LAN (manual IP:port — "scan for LAN" won't auto-list), and that internet access needs operator port-forwarding. (Static text — can't show the live LAN IP.) - Live-verified on guest 9201: 4.10.7 healthy; public URL 302; the guest's bridged LAN IP
192.168.0.121reaches the real Crafty "test" server on25565(TCP OPEN + Minecraft SLP handshake returns JSON status);:25575reachable,:25600closed (negative control). In-place upgrade preserved the admin, the operator's configured MFA, and the test server. - Correction (earlier draft was wrong): an earlier note here claimed the upgrade "locked out the
admin (TOTP)." That was a misdiagnosis — the
totp_datarow + recovery codes were operator-configured MFA, so the 401 on a password-only login was correct behaviour, NOT an upgrade bug. There is no upgrade regression; the bump preserves data and MFA correctly.
2026-06-26 — crafty-controller: seed a felhom-generated admin password (replaces Crafty's ugly random one)
- crafty-controller: instead of reading Crafty's auto-generated (long, symbol-laden) random admin
password, we now seed a clean felhom-generated one — same pattern as gokapi, so initial passwords are
consistent across the catalog.
- Crafty's image ships
app/config_original/default.json = {"username":"admin","password":"crafty"}; "crafty" is 6 chars < Crafty's 8-char minimum, so Crafty rejected it and generated a random password. - New
CRAFTY_PASSWORDdeploy field (type: password,generate: password:24, locked after deploy — mirrors gokapi'sGOKAPI_PASSWORD). The compose entrypoint overwrites thedefault.jsontemplate with this password before the launcher runs; on fresh install Crafty creates theadminuser with it. initial_credentials.filerepointeddefault-creds.txt→default.json(same json/username/password keys), so the controller's app-page "Kezdeti belépési adatok" card shows the seeded password — the customer sees the same value at deploy time and on the app page.- Catalog-only change (reuses felhom-controller v0.84.0's initial_credentials reader + the gokapi-style seed). Requires a fresh install to take effect (the seed is only read on first run).
- Crafty's image ships
2026-06-26 — crafty-controller: surface the auto-generated initial admin password on the app page
- crafty-controller: Crafty writes a random admin password to
/crafty/app/config/default-creds.txtat first boot (its built-in default is rejected as "too short"). Customers had to read the container logs to find it. Added aninitial_credentialsblock (new general felhom-controller v0.84.0 mechanism):file+format: json+username_key/password_key+ anote. The controller reads the file live from the container and shows username + password (masked, reveal/copy) on the app's page under "Kezdeti belépési adatok". Requires felhom-controller ≥ v0.84.0. - Updated
first_stepsto point at the app page for the initial login instead of "find it in the logs".
2026-06-26 — crafty-controller: Traefik https backend + scoped skip-verify (fixes 502)
- crafty-controller: the healthcheck fix un-withheld the Traefik route, exposing a pre-existing
502 — Traefik proxied
http://…:8443to Crafty's HTTPS-only self-signed backend (Crafty serves no plain-HTTP panel;:8000only redirects). Added two service labels:loadbalancer.server.scheme=https— Traefik now speaks HTTPS to the backend.loadbalancer.serverstransport=insecure-skip-verify@file— references the named serversTransport defined in the controller-managed Traefik dynamic config (felhom-controller v0.83.0), which skips verifying Crafty's per-container self-signed cert. Verification stays ON for every other backend (scoped Option B; no globalinsecureSkipVerify). The@filesuffix is the cross-provider reference from the docker provider to the file-provider transport.- Requires felhom-controller ≥ v0.83.0 (which renders the
insecure-skip-verifytransport).port=8443and the router/tls labels are unchanged.
2026-06-26 — crafty-controller healthcheck fix (curl-absent + http-vs-TLS probe)
- crafty-controller: container was permanently
unhealthy→ route withheld (routeUnpublished). Two independent healthcheck root causes, both fixed in one change:- Docker healthcheck ran
curl -fk https://localhost:8443, but thecrafty-4:4.4.8image has nocurland nowget(exec: "curl": not found, FailingStreak 150). Replaced with a dependency-free python3 TLS-socket liveness probe (/usr/bin/python3is present): completes a TLS handshake to127.0.0.1:8443(unverified context mirrors the old-k; Crafty's cert is self-signed).start_period30s → 60s for cold-boot headroom (cert gen + migrations). - Controller-side probe (
.felhom.yml healthcheck.checks) wastype: httpagainst Crafty's TLS-only 8443 →probeHTTPsent plaintext HTTP, got a TLS record →HealthProbe.Healthy=false, whichmanager.gore-applies to override Docker's verdict back tounhealthy. Changedhttp→tcp(probeTCPdial succeeds against a TLS listener). Both layers had to change together. - Live-validated on guest 9201 (
demo-felhom): synced → recreated via the update path → DockerState.Health: healthy(ExitCode 0),health_probe.healthy: true(tcp :8443, 5ms), http-vs-TLS WARNs stopped, stable green 3+ min, Traefik now publishes the route (crafty-controller@docker). - Known follow-up (separate, out of this fix's scope): the public URL still returns 502 — a
distinct pre-existing bug the un-withheld route exposed: Traefik proxies
http://…:8443to Crafty's HTTPS-only backend. Needs a Traefik HTTPS-backend + self-signedserversTransport(insecureSkipVerify) in the controller-generated Traefik config — tracked separately.
- Docker healthcheck ran
2026-06-23 — gokapi: index redirect + admin username display
- gokapi: seed
RedirectUrlrepointed from Gokapi's GitHub default →https://${SUBDOMAIN}.${DOMAIN}/admin. Gokapi's bare root/redirects toRedirectUrl; the controller's "Megnyitás" link is always the bare subdomain root, so it was landing on Gokapi's GitHub instead of the app. Now/→/admin→ login. Applied to the live demo (config.json edit + restart) and the seed (future deploys). - gokapi: added
app_info.default_creds("Felhasználó: admin · jelszó a Beállítások oldalon") so the app-info page shows the initial admin user like other apps; fixedfirst_steps(no more setup wizard).
2026-06-23 — gokapi reproducible headless setup (fixes public "maintenance mode")
- gokapi: was stuck in "maintenance mode" on the public URL since first deploy — Gokapi's one-time
/setupwizard was never completed, and (verified against the docs + the v1.9.6 binary) no Gokapi version supports env-var headless setup for admin credentials. Worse, the unconfigured/setupwas publicly reachable = an unauthenticated admin-takeover window.- Fix: the compose
entrypointnow seeds aconfig.jsonon first boot (admin user, this app's public URLhttps://${SUBDOMAIN}.${DOMAIN}/, local storage, Encryption Level 0 so it restarts without a prompt) withPassword/SaltAdmin/SaltFilescleared, then runs Gokapi's documented--deployment-passwordone-shot to set the felhom-generated admin password before the server starts serving. The admin account is claimed at first boot →/setupis never exposed. .felhom.yml: newGOKAPI_PASSWORDdeploy field (type: password,generate: password:24, shown to the customer, locked after deploy). Admin username isadmin.- Seed is pinned to Gokapi v1.9.6 (
ConfigVersion 21) — re-capture the seed if the image is bumped. - Live-validated on guest 9201: fresh remove+redeploy → headless auto-config, public login works, no maintenance page, admin claimed at first boot (browser-verified login).
- Fix: the compose
2026-06-22 — gitea healthcheck fix (unattended test campaign)
- gitea: healthcheck probe repointed
/api/v1/version→/api/healthz(docker HC + controller.felhom.ymlprobe),start_period30s → 90s.- Surfaced during the Phase-2 deploy sweep: a fresh gitea reported
unhealthybecause/api/v1/versionreturns 404 until the install wizard / INSTALL_LOCK completes, while the container was serving fine on :3000 (/api/healthz→ 200). Same class as the komga fix.
- Surfaced during the Phase-2 deploy sweep: a fresh gitea reported
2026-06-22 — komga healthcheck fix (unattended test campaign)
- komga: healthcheck probe repointed
/api/v1/actuator/health→/actuator/health.- Root cause: komga's Spring Boot actuator endpoint is served unauthenticated at
/actuator/health(HTTP 200), while everything under the/api/v1prefix is auth-gated — so the old probe got HTTP 401,curl -fexited 22, and the container reportedunhealthydespite serving normally on :25600. Diagnosed live on guest 9201 (probe matrix:/,/actuator/health,/api/v1/oauth2/providers,/loginall 200;/api/v1/actuator/health→ 401). - The
gotson/komga:1.20.0image shipscurl(verified), so the probe tool is unchanged.
- Root cause: komga's Spring Boot actuator endpoint is served unauthenticated at