Files
felhom.eu/documentation/audits/login-gate-2026-09-29/D/catpatch.py
T

216 lines
12 KiB
Python

# Applies the 2026-09-29 catalog changes (decision 46 gate + Part D) to a catalog checkout: argv[1] = repo root.
# Identical on the drill repo and the live repo, so the drill proves exactly what ships.
import sys, os
R = sys.argv[1]
T = lambda *p: os.path.join(R, "templates", *p)
def edit(path, old, new, count=1):
s = open(path).read()
n = s.count(old)
if n != count:
sys.exit(f"{path}: expected {count} of {old[:60]!r}, found {n}")
open(path, "w").write(s.replace(old, new))
def after_line(path, anchor, text):
edit(path, anchor, anchor + text)
GATE_NOTE = """
# --- The setup gate (controller >= 0.280.0, `09` §3 decision 46) ---
# The first visitor would create the admin. So a fresh install is closed to everyone but the household (a browser
# signed in to the dashboard) until the first setup is done%s
setup_gate: true
"""
# --- decision 46: the gate, on four class-4 apps -------------------------------------------------------------
probes = {
"immich": ("; immich's own status says so (measured on 9202 2026-09-29:\n# isInitialized false -> true once the admin exists).",
"setup_done_probe:\n url: http://immich-server:2283/api/server/config\n field: isInitialized\n done: \"true\"\n"),
"n8n": ("; n8n's own settings say so (measured on 9202 2026-09-29:\n# showSetupOnFirstLoad true -> false once the owner exists).",
"setup_done_probe:\n url: http://n8n:5678/rest/settings\n field: data.userManagement.showSetupOnFirstLoad\n done: \"false\"\n"),
"audiobookshelf": ("; audiobookshelf's own status says so (`/status` isInit — upstream, measured on 9202 by the\n# 2026-09-29 live proof).",
"setup_done_probe:\n url: http://audiobookshelf:80/status\n field: isInit\n done: \"true\"\n"),
"uptime-kuma": (". uptime-kuma says it only over socket.io, so the household presses \"Done, I set it up\"\n# on the app page.", ""),
}
for app, (why, probe) in probes.items():
p = T(app, ".felhom.yml")
s = open(p).read()
assert "setup_gate" not in s, app
i = s.index("\n# --- App info")
s = s[:i] + "\n" + (GATE_NOTE % why).rstrip("\n") + "\n" + probe + s[i:]
open(p, "w").write(s)
# --- Part D1: mealie ------------------------------------------------------------------------------------------
p = T("mealie", ".felhom.yml")
edit(p, """ description: "Az alkalmazás aldomainje"
# --- App info (info page content) ---""", """ description: "Az alkalmazás aldomainje"
# `09` §3 decision 45: Mealie starts with changeme@example.com / MyPassword. The box replaces that password with
# this generated one right after the install (after_install below); the app page shows it as the first password.
- env_var: ADMIN_PASSWORD
label: "Admin jelszó (changeme@example.com)"
type: password
generate: "password:24"
description: "Az első bejelentkezéshez: changeme@example.com és ez a jelszó. Utána a profilodban módosítható."
locked_after_deploy: true
# --- App info (info page content) ---""")
edit(p, " - 'Jelentkezz be: changeme@example.com / MyPassword'\n - 'Változtasd meg azonnal az email címet és jelszót'\n",
" - 'Jelentkezz be: changeme@example.com és a Beállítások oldalon látható első jelszó'\n - 'Változtasd meg az email címet a sajátodra'\n")
edit(p, " - 'Sign in: changeme@example.com / MyPassword'\n - 'Change the e-mail address and password straight away'\n",
" - 'Sign in: changeme@example.com and the first password shown on the settings page'\n - 'Change the e-mail address to your own'\n")
edit(p, """# --- Controller-side health probe ---
healthcheck:
checks:
- type: tcp
port: 9000""", """# --- After a fresh install (controller >= 0.279.0, decision 45) ---
# Mealie's OWN user repository sets the seeded user's password (what its scripts/change_password.py does, without the
# prompts). Measured on 9202 2026-09-29: afterwards MyPassword answers 401 at /api/auth/token, the new one 200.
after_install:
service: mealie
env: [ADMIN_PASSWORD]
# The password is the LAST ARGUMENT (sys.argv[1]), never pasted into the code: a quote in it cannot break or change
# the program (security review 2026-09-29).
command: ["python3", "-c", "import sys\\nfrom mealie.core.security.security import hash_password\\nfrom mealie.db.db_setup import session_context\\nfrom mealie.repos.repository_factory import AllRepositories\\nwith session_context() as s:\\n r = AllRepositories(s, group_id=None, household_id=None)\\n u = r.users.get_one('changeme@example.com', 'email')\\n r.users.update_password(u.id, hash_password(sys.argv[1]))\\n print('FELHOM_AFTER_INSTALL_OK')\\n", "${ADMIN_PASSWORD}"]
success: "FELHOM_AFTER_INSTALL_OK"
# --- Controller-side health probe ---
healthcheck:
checks:
- type: tcp
port: 9000""")
edit(p, """ - env_var: SUBDOMAIN
label: 'Subdomain'
description: 'The subdomain this app answers on'
""", """ - env_var: SUBDOMAIN
label: 'Subdomain'
description: 'The subdomain this app answers on'
- env_var: ADMIN_PASSWORD
label: 'Admin password (changeme@example.com)'
description: 'For the first sign-in: changeme@example.com and this password. Change it in your profile afterwards.'
""")
# --- Part D1: wger (+ R-712: a browser's https Origin was refused by CSRF) --------------------------------------
p = T("wger", ".felhom.yml")
edit(p, """ generate: "hex:32"
locked_after_deploy: true
# --- App info (info page content) ---""", """ generate: "hex:32"
locked_after_deploy: true
# `09` §3 decision 45: wger starts with admin / adminadmin. The box replaces that password with this generated
# one right after the install (after_install below); the app page shows it as the first password.
- env_var: ADMIN_PASSWORD
label: "Admin jelszó (admin)"
type: password
generate: "password:24"
description: "Az első bejelentkezéshez: admin és ez a jelszó. Utána a beállításokban módosítható."
locked_after_deploy: true
# --- App info (info page content) ---""")
edit(p, " - 'Jelentkezz be: admin / adminadmin'\n - 'Változtasd meg azonnal a jelszót'\n",
" - 'Jelentkezz be: admin és a Beállítások oldalon látható első jelszó'\n - 'Add meg az email címedet a beállításokban'\n")
edit(p, " - 'Sign in: admin / adminadmin'\n - 'Change the password straight away'\n",
" - 'Sign in: admin and the first password shown on the settings page'\n - 'Add your e-mail address in the settings'\n")
edit(p, "\n# --- Controller-side health probe ---\nhealthcheck:", """
# --- After a fresh install (controller >= 0.279.0, decision 45) ---
# Django's own set_password on the seeded admin. Measured on 9202 2026-09-29: afterwards adminadmin no longer signs in
# at /en/user/login, the new one does.
after_install:
service: wger
env: [ADMIN_PASSWORD]
# The password is the LAST ARGUMENT (sys.argv[1]), never pasted into the code: a quote in it cannot break or change
# the program (security review 2026-09-29). Django is set up the way manage.py does it (settings.main).
command: ["python3", "-c", "import os, sys; sys.path.insert(0, '/home/wger/src'); os.chdir('/home/wger/src'); os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main'); import django; django.setup(); from django.contrib.auth.models import User; u = User.objects.get(username='admin'); u.set_password(sys.argv[1]); u.save(); print('FELHOM_AFTER_INSTALL_OK')", "${ADMIN_PASSWORD}"]
success: "FELHOM_AFTER_INSTALL_OK"
# --- Controller-side health probe ---
healthcheck:""")
edit(p, """ - env_var: SECRET_KEY
label: 'Encryption key'
""", """ - env_var: SECRET_KEY
label: 'Encryption key'
- env_var: ADMIN_PASSWORD
label: 'Admin password (admin)'
description: 'For the first sign-in: admin and this password. Change it in the settings afterwards.'
""")
p = T("wger", "docker-compose.yml")
edit(p, " - DJANGO_DB_ENGINE=django.db.backends.sqlite3\n", """ # R-712 (measured 2026-09-29 on 9202): behind traefik wger saw the request as http and refused a browser's
# https Origin with "CSRF verification failed" — nobody could sign in from a browser.
- CSRF_TRUSTED_ORIGINS=https://${SUBDOMAIN}.${DOMAIN}
- X_FORWARDED_PROTO_HEADER_SET=True
- DJANGO_DB_ENGINE=django.db.backends.sqlite3
""")
# --- Part D2: calibre-web -----------------------------------------------------------------------------------------
p = T("calibre-web", ".felhom.yml")
edit(p, """ description: "A külső merevlemez elérési útja, ahol a Calibre könyvtár található"
locked_after_deploy: true
# --- App info (info page content) ---""", """ description: "A külső merevlemez elérési útja, ahol a Calibre könyvtár található"
locked_after_deploy: true
# `09` §3 decision 45: Calibre-Web starts with admin / admin123. The box replaces that password with this
# generated one right after the install (after_install below). Its password policy demands a special character,
# hence `:special` (controller >= 0.280.0).
- env_var: ADMIN_PASSWORD
label: "Admin jelszó (admin)"
type: password
generate: "password:24:special"
description: "Az első bejelentkezéshez: admin és ez a jelszó. Kell benne kis- és nagybetű, szám és egy különleges karakter."
locked_after_deploy: true
# --- App info (info page content) ---""")
edit(p, " - 'Jelentkezz be: admin / admin123'\n - 'Változtasd meg azonnal a jelszót'\n",
" - 'Jelentkezz be: admin és a Beállítások oldalon látható első jelszó'\n")
edit(p, " - 'Sign in: admin / admin123'\n - 'Change the password straight away'\n",
" - 'Sign in: admin and the first password shown on the settings page'\n")
edit(p, "\n# --- Controller-side health probe ---\nhealthcheck:", """
# --- After a fresh install (controller >= 0.280.0, decision 45) ---
# Calibre-Web's OWN `cps.py -s user:password`, as the app user. Measured on 9202 2026-09-29: afterwards admin123 no
# longer signs in, the new one does; a password without a special character is refused by its policy.
after_install:
service: calibre-web
user: abc
env: [ADMIN_PASSWORD]
command: ["python3", "/app/calibre-web-automated/cps.py", "-p", "/config/app.db", "-s", "admin:${ADMIN_PASSWORD}"]
success: "Password for user 'admin' changed"
# --- Controller-side health probe ---
healthcheck:""")
edit(p, """ label: 'E-book library path'
description: 'The path to the external hard drive where the Calibre library lives'
placeholder: '/mnt/felhom-drives/hdd_1'
""", """ label: 'E-book library path'
description: 'The path to the external hard drive where the Calibre library lives'
placeholder: '/mnt/felhom-drives/hdd_1'
- env_var: ADMIN_PASSWORD
label: 'Admin password (admin)'
description: 'For the first sign-in: admin and this password. It needs a lower and an upper case letter, a digit and a special character.'
""")
# --- Part D3: romm and zipline — their default_creds notes are stale (they are class 4) ---------------------------
import re
for app, cred in (("romm", "admin / admin"), ("zipline", "admin / zipline")):
p = T(app, ".felhom.yml")
s = open(p).read()
s, n = re.subn(r"(?m)^[ \t]*default_creds: ['\"]" + re.escape(cred) + r"['\"][ \t]*\n", "", s)
assert n == 2 and "default_creds" not in s, (app, n)
open(p, "w").write(s)
edit(T("romm", ".felhom.yml"), ' - "Jelentkezz be az alapértelmezett admin / admin fiókkal"\n',
' - "Az első megnyitáskor hozd létre az admin fiókodat"\n')
edit(T("romm", ".felhom.yml"), ' - "Sign in with the default admin / admin account"\n',
' - "On the first visit, create your admin account"\n')
edit(T("zipline", ".felhom.yml"), " - 'Jelentkezz be: admin / zipline'\n - 'Változtasd meg azonnal a jelszót'\n",
" - 'Az első megnyitáskor hozd létre az admin fiókodat'\n")
edit(T("zipline", ".felhom.yml"), " - 'Sign in: admin / zipline'\n - 'Change the password straight away'\n",
" - 'On the first visit, create your admin account'\n")
# --- Part D4: R-708 — grafana refuses to start without its admin password; never `admin` ---------------------------
edit(T("grafana", "docker-compose.yml"), "GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-admin}",
"GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:?the admin password is required (R-708)}")
print("catalog patched:", R)