b0f8ffb801
gates / gates (push) Successful in 26s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
216 lines
12 KiB
Python
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)
|