Gate rollout 2026-09-29 afternoon: 32/34 gated, sign-up blocks (decision 47 + CC mechanism), R-713; R-707/R-711/R-713 closed, R-714..R-716 opened; STATUS, CONTEXT, report
gates / gates (push) Successful in 27s

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-29 11:25:14 +02:00
parent fd9c77b92e
commit 1749e73e50
83 changed files with 1303 additions and 21 deletions
@@ -0,0 +1,5 @@
09:35:51 live catalog vikunja: setup_gate = None
09:35:51 deploy -> 202
09:35:57 state running | gate record None
09:36:07 the household sets vikunja up (register the first user) -> 200
09:36:07 stranger GET /api/v1/info -> 200 {"version":"v2.6.0","frontend_url":"https://p0-vik.enkisfelh
@@ -0,0 +1,4 @@
09:36:46 drill catalog synced: vikunja setup_gate = True
09:36:46 waiting 50 s = two ticks of the gate loop
09:37:40 after the catalog added setup_gate: stranger API -> 200 '{"version":"v2.6.0","frontend_' | browser -> 200 gate page False | gate file absent | record None | gate card on the app page False
09:38:23 after a controller restart: stranger API -> 200 '{"version":"v2.6.0","frontend_' | browser -> 200 gate page False | gate file absent | record None | gate card on the app page False
@@ -0,0 +1,14 @@
# READ-ONLY, 2026-09-29 ~09:23Z, after catalog 6faf432 synced: installed class-4 apps on the demo boxes (controller 0.281.0) — no gate file, no block file, no gate record, no gate/sign-up card. Part 0 on real boxes.
== hp 09:22:57
dynamic: controller.yml serverstransports.yml
adventurelog: synced template setup_gate=1 signup_block=1 | app.yaml gate record=0
docmost: synced template setup_gate=1 signup_block=0 | app.yaml gate record=0
opengist: synced template setup_gate=1 signup_block=1 | app.yaml gate record=0
romm: synced template setup_gate=1 signup_block=0 | app.yaml gate record=0
== felhom-pve 09:23:01
dynamic: controller.yml serverstransports.yml
opengist: synced template setup_gate=1 signup_block=1 | app.yaml gate record=0
demo-hp adventurelog: state running | gate record None | gate card on page False | sign-up card False
demo-hp docmost: state running | gate record None | gate card on page False | sign-up card False
demo-hp opengist: state running | gate record None | gate card on page False | sign-up card False
demo-hp romm: state running | gate record None | gate card on page False | sign-up card False
@@ -0,0 +1,3 @@
10:19:24 zipline: catalog now carries its status check; the app still says firstSetup=true
10:19:24 PART A LIVE: 'Done' pressed BEFORE the setup -> 409 {'data': None, 'error': 'Az alkalmazás szerint még nincs kész az első beállítás. Hozd létre a fiókodat, aztán próbáld újra.', 'ok': False}
10:19:27 zipline files=[setup-gate-zipline.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-zipline.enkisfelhom.hu']}
@@ -0,0 +1,8 @@
10:09:49 actualbudget AFTER, stranger browser -> 200 200 r-actualbudget.enkisfelhom.hu/ | gate page False | len 4256
10:09:49 komga AFTER, stranger browser -> 200 200 r-komga.enkisfelhom.hu/ | gate page False | len 1236
10:09:49 navidrome AFTER, stranger browser -> 200 302 r-navidrome.enkisfelhom.hu/ -> 200 r-navidrome.enkisfelhom.hu/app/ | gate page False | len 2848
10:09:50 jellyfin AFTER, stranger browser -> 200 302 r-jellyfin.enkisfelhom.hu/ -> 200 r-jellyfin.enkisfelhom.hu/web/ | gate page False | len 5331
10:09:50 emby AFTER, stranger browser -> 200 302 r-emby.enkisfelhom.hu/ -> 200 r-emby.enkisfelhom.hu/web/index.html | gate page False | len 14937
10:09:50 ghost AFTER, stranger browser -> 200 200 r-ghost.enkisfelhom.hu/ | gate page False | len 17379
10:09:50 home-assistant AFTER, stranger browser -> 200 assistant.enkisfelhom.hu/ -> 200 r-home-assistant.enkisfelhom.hu/onboarding.html | gate page False | len 3235
10:09:50 romm AFTER, stranger browser -> 200 200 r-romm.enkisfelhom.hu/ | gate page False | len 5485
@@ -0,0 +1,26 @@
10:03:32 actualbudget deploy -> 202
10:03:35 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/komga']
10:03:35 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:03:36 komga deploy -> 202
10:03:39 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/navidrome']
10:03:39 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:03:39 navidrome deploy -> 202
10:03:43 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/jellyfin']
10:03:43 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:03:43 jellyfin deploy -> 202
10:03:47 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/emby']
10:03:47 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:03:47 emby deploy -> 202
10:03:47 ghost deploy -> 202
10:03:47 home-assistant deploy -> 202
10:03:52 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/romm']
10:03:52 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:03:52 romm deploy -> 202
10:03:57 actualbudget state running | files=[setup-gate-actualbudget.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:32Z', 'hosts': ['r-actualbudget.enkisfelhom.hu']}
10:04:22 komga state running | files=[setup-gate-komga.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:36Z', 'hosts': ['r-komga.enkisfelhom.hu']}
10:04:26 navidrome state running | files=[setup-gate-navidrome.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:39Z', 'hosts': ['r-navidrome.enkisfelhom.hu']}
10:04:30 jellyfin state running | files=[setup-gate-jellyfin.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:43Z', 'hosts': ['r-jellyfin.enkisfelhom.hu']}
10:04:34 emby state running | files=[setup-gate-emby.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:47Z', 'hosts': ['r-emby.enkisfelhom.hu']}
10:06:18 ghost state running | files=[setup-gate-ghost.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:47Z', 'hosts': ['r-ghost.enkisfelhom.hu']}
10:06:21 home-assistant state running | files=[setup-gate-home-assistant.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:47Z', 'hosts': ['r-home-assistant.enkisfelhom.hu']}
10:06:24 romm state running | files=[setup-gate-romm.yml] record={'state': 'closed', 'since': '2026-09-29T08:03:52Z', 'hosts': ['r-romm.enkisfelhom.hu']}
@@ -0,0 +1,8 @@
10:06:32 actualbudget HOUSEHOLD -> 200 in 0.20s | > 302 r-actualbudget.enkisfelhom.hu/__felhom_gate/cb -> 200 r-actualbudget.enkisfelhom.hu/ | gate page False | app says 'Actual'
10:06:33 komga HOUSEHOLD -> 200 in 0.56s | __gate/start -> 302 r-komga.enkisfelhom.hu/__felhom_gate/cb -> 200 r-komga.enkisfelhom.hu/ | gate page False | app says 'Komga'
10:06:33 navidrome HOUSEHOLD -> 200 in 0.25s | __felhom_gate/cb -> 302 r-navidrome.enkisfelhom.hu/ -> 200 r-navidrome.enkisfelhom.hu/app/ | gate page False | app says 'Navidrome'
10:06:33 jellyfin HOUSEHOLD -> 200 in 0.27s | u/__felhom_gate/cb -> 302 r-jellyfin.enkisfelhom.hu/ -> 200 r-jellyfin.enkisfelhom.hu/web/ | gate page False | app says 'Jellyfin'
10:06:34 emby HOUSEHOLD -> 200 in 0.35s | u/ -> 302 r-emby.enkisfelhom.hu/web/index.html -> 200 r-emby.enkisfelhom.hu/web/index.html | gate page False | app says 'ac8466a1b4d5'
10:06:34 ghost HOUSEHOLD -> 200 in 0.65s | __gate/start -> 302 r-ghost.enkisfelhom.hu/__felhom_gate/cb -> 200 r-ghost.enkisfelhom.hu/ | gate page False | app says 'Ghost'
10:06:35 home-assistant HOUSEHOLD -> 200 in 0.25s | 02 r-home-assistant.enkisfelhom.hu/ -> 200 r-home-assistant.enkisfelhom.hu/onboarding.html | gate page False | app says 'Home Assistant'
10:06:35 romm HOUSEHOLD -> 200 in 0.20s | u/__gate/start -> 302 r-romm.enkisfelhom.hu/__felhom_gate/cb -> 200 r-romm.enkisfelhom.hu/ | gate page False | app says 'RomM'
@@ -0,0 +1,4 @@
10:09:08 actualbudget gate OPENED by probe 5s after the sync | files=[] record={'state': 'open', 'since': '2026-09-29T08:03:32Z', 'hosts': ['r-actualbudget.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:02Z', 'opened_by': 'probe'}
10:09:11 jellyfin gate OPENED by probe 8s after the sync | files=[] record={'state': 'open', 'since': '2026-09-29T08:03:43Z', 'hosts': ['r-jellyfin.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:02Z', 'opened_by': 'probe'}
10:09:14 komga gate OPENED by probe 11s after the sync | files=[] record={'state': 'open', 'since': '2026-09-29T08:03:36Z', 'hosts': ['r-komga.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:02Z', 'opened_by': 'probe'}
10:09:17 romm gate OPENED by probe 14s after the sync | files=[] record={'state': 'open', 'since': '2026-09-29T08:03:52Z', 'hosts': ['r-romm.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:02Z', 'opened_by': 'probe'}
@@ -0,0 +1,8 @@
10:09:28 emby PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:09:33 emby files=[] record={'state': 'open', 'since': '2026-09-29T08:03:47Z', 'hosts': ['r-emby.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:28Z', 'opened_by': 'household'}
10:09:34 navidrome PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:09:39 navidrome files=[] record={'state': 'open', 'since': '2026-09-29T08:03:39Z', 'hosts': ['r-navidrome.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:34Z', 'opened_by': 'household'}
10:09:39 ghost PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:09:44 ghost files=[] record={'state': 'open', 'since': '2026-09-29T08:03:47Z', 'hosts': ['r-ghost.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:39Z', 'opened_by': 'household'}
10:09:44 home-assistant PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:09:49 home-assistant files=[] record={'state': 'open', 'since': '2026-09-29T08:03:47Z', 'hosts': ['r-home-assistant.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:09:44Z', 'opened_by': 'household'}
@@ -0,0 +1,7 @@
10:07:58 actualbudget PROBE http://actualbudget:5006/account/needs-bootstrap -> {"status":"ok","data":{"bootstrapped":true,"loginMethod":"password","availableLoginMethods":[{"method":"password","active":1,"displayName":"Password"}],"multiuser":false}}
10:08:01 komga PROBE http://komga:25600/api/v1/claim -> {"isClaimed":true}
10:08:04 jellyfin PROBE http://jellyfin:8096/System/Info/Public -> {"LocalAddress":"http://172.18.0.12:8096","ServerName":"8ac2a93e4b5e","Version":"10.11.11","ProductName":"Jellyfin Server","OperatingSystem":"","Id":"05c9b4551b80435fbd2f3447eb66c88f","StartupWizardCompleted":true}
10:08:07 emby PROBE http://emby:8096/emby/System/Info/Public -> {"LocalAddresses":[],"RemoteAddresses":[],"ServerName":"ac8466a1b4d5","Version":"4.11.0.3","Id":"b9ba6aa3b18240ff8dd530a4b7f64da6"}
10:08:11 ghost PROBE http://ghost:2368/ghost/api/admin/authentication/setup/ -> {"setup":[{"status":true}]}
10:08:14 home-assistant PROBE http://home-assistant:8123/api/onboarding -> [{"step":"user","done":true},{"step":"core_config","done":false},{"step":"analytics","done":false},{"step":"integration","done":false}]
10:08:17 romm PROBE http://romm:8080/api/heartbeat -> {"SYSTEM":{"VERSION":"5.3.1","GIT_BRANCH":null,"SHOW_SETUP_WIZARD":true},"METADATA_SOURCES":{"ANY_SOURCE_ENABLED":true,"IGDB_API_ENABLED":false,"SS_API_ENABLED":false,"SS_DEV_CREDENTIALS_SET":true,"MOBY_API_ENABLED":false,"STEAMGRIDDB_API_ENABLED":false,"RA_API_ENABLED":false,"LAUNCHBOX_API_ENABLED":false,"HASHEOUS_API_ENABLED":false,"PLAYMATCH_API_ENABLED":false,"TGDB_API_ENABLED":false,"FLASHPOI
@@ -0,0 +1,18 @@
10:06:57 actualbudget PROBE http://actualbudget:5006/account/needs-bootstrap -> {"status":"ok","data":{"bootstrapped":false,"loginMethod":"password","availableLoginMethods":[],"multiuser":false}}
10:07:00 komga PROBE http://komga:25600/api/v1/claim -> {"isClaimed":false}
10:07:04 navidrome PROBE http://navidrome:4533/app/ -> <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="description"
content="Navidrome Music Server - v0.64.1 (285dc4f3)"
/>
<link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
<link rel="icon" type="image/png" sizes="192x192" h
10:07:07 jellyfin PROBE http://jellyfin:8096/System/Info/Public -> {"LocalAddress":"http://172.18.0.12:8096","ServerName":"8ac2a93e4b5e","Version":"10.11.11","ProductName":"Jellyfin Server","OperatingSystem":"","Id":"05c9b4551b80435fbd2f3447eb66c88f","StartupWizardCompleted":false}
10:07:10 emby PROBE http://emby:8096/emby/System/Info/Public -> {"LocalAddresses":[],"RemoteAddresses":[],"ServerName":"ac8466a1b4d5","Version":"4.11.0.3","Id":"b9ba6aa3b18240ff8dd530a4b7f64da6"}
10:07:14 ghost PROBE http://ghost:2368/ghost/api/admin/authentication/setup/ -> {"setup":[{"status":false}]}
10:07:17 home-assistant PROBE http://home-assistant:8123/api/onboarding -> [{"step":"user","done":false},{"step":"core_config","done":false},{"step":"analytics","done":false},{"step":"integration","done":false}]
10:07:20 romm PROBE http://romm:8080/api/heartbeat -> {"SYSTEM":{"VERSION":"5.3.1","GIT_BRANCH":null,"SHOW_SETUP_WIZARD":true},"METADATA_SOURCES":{"ANY_SOURCE_ENABLED":true,"IGDB_API_ENABLED":false,"SS_API_ENABLED":false,"SS_DEV_CREDENTIALS_SET":true,"MOBY_API_ENABLED":false,"STEAMGRIDDB_API_ENABLED":false,"RA_API_ENABLED":false,"LAUNCHBOX_API_ENABLED":false,"HASHEOUS_API_ENABLED":false,"PLAYMATCH_API_ENABLED":false,"TGDB_API_ENABLED":false,"FLASHPOI
@@ -0,0 +1,9 @@
10:07:51 actualbudget HOUSEHOLD SETUP through the gate -> 200 {"status":"ok","data":{"token":"<redacted>"}}
10:07:52 komga HOUSEHOLD SETUP through the gate -> 200 {"id":"0RRVKEAXSX4E9","email":"family@spike.hu","roles":["ADMIN","FILE_DOWNLOAD","KOBO_SYNC","KOREADER_SYNC","PAGE_STREA
10:07:53 jellyfin HOUSEHOLD SETUP through the gate -> 204 steps [204, 200, 204, 204]
10:07:53 emby HOUSEHOLD SETUP through the gate -> 204 steps [204, 200, 204]
10:07:53 navidrome HOUSEHOLD SETUP through the gate -> 200 {"id":"3RisVGNCdqxjjWlZfeIzHz","isAdmin":true,"name":"Family","subsonicSalt":"<redacted>","subsonicToken":"<redacted>"
10:07:54 ghost HOUSEHOLD SETUP through the gate -> 201 {"users":[{"id":"6abb716794aafd0001b8c3af","name":"Family","slug":"family","email":"family@spike.hu","profile_image":nul
10:07:54 home-assistant HOUSEHOLD SETUP through the gate -> 200 {"auth_code":"<redacted>"}
10:07:54 romm HOUSEHOLD SETUP through the gate -> 403 CSRF token verification failed
10:08:30 romm HOUSEHOLD SETUP through the gate -> 201 {"id":1,"username":"family","email":"family@spike.hu","enabled":true,"role":"admin","permission_group_id":null,"oauth_sc
@@ -0,0 +1,8 @@
10:10:08 actualbudget STRANGER retries POST /account/bootstrap after the setup -> 400 '{"status":"error","reason":"already-bootstrapped"}'
10:10:08 komga STRANGER retries POST /api/v1/claim after the setup -> 400 '{"timestamp":"2026-09-29T08:10:08.765+00:00","status":400,"error":"Bad'
10:10:08 jellyfin STRANGER retries POST /Startup/User after the setup -> 401 ''
10:10:08 emby STRANGER retries POST /emby/Startup/User after the setup -> 401 'Access token is invalid or expired.'
10:10:08 navidrome STRANGER retries POST /auth/createAdmin after the setup -> 403 '{"error":"Cannot create another first admin"}'
10:10:08 ghost STRANGER retries POST /ghost/api/admin/authentication/setup/ after the setup -> 403 '{"errors":[{"message":"Setup has already been completed.","context":nu'
10:10:09 home-assistant STRANGER retries POST /api/onboarding/users after the setup -> 403 '{"message":"User step already done"}'
10:10:09 romm STRANGER retries POST /api/users after the setup -> 403 '{"detail":"Forbidden"}'
@@ -0,0 +1,8 @@
10:06:31 actualbudget STRANGER browser -> 200 302 r-actualbudget.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:31 komga STRANGER browser -> 200 302 r-komga.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:31 navidrome STRANGER browser -> 200 302 r-navidrome.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:31 jellyfin STRANGER browser -> 200 302 r-jellyfin.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:31 emby STRANGER browser -> 200 302 r-emby.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:31 ghost STRANGER browser -> 200 302 r-ghost.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:31 home-assistant STRANGER browser -> 200 302 r-home-assistant.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:06:32 romm STRANGER browser -> 200 302 r-romm.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
@@ -0,0 +1,5 @@
10:21:50 zipline AFTER, stranger browser -> 200 301 r-zipline.enkisfelhom.hu/ -> 200 r-zipline.enkisfelhom.hu/dashboard | gate page False | len 6405
10:21:50 gitea AFTER, stranger browser -> 200 200 r-gitea.enkisfelhom.hu/ | gate page False | len 14539
10:21:50 docmost AFTER, stranger browser -> 200 200 r-docmost.enkisfelhom.hu/ | gate page False | len 2644
10:21:50 calcom AFTER, stranger browser -> 200 307 r-calcom.enkisfelhom.hu/ -> 200 r-calcom.enkisfelhom.hu/auth/login | gate page False | len 343294
10:21:50 tandoor AFTER, stranger browser -> 200 302 r-tandoor.enkisfelhom.hu/ -> 200 r-tandoor.enkisfelhom.hu/accounts/login/ | gate page False | len 5063
@@ -0,0 +1,16 @@
10:15:56 zipline deploy -> 202
10:15:56 gitea deploy -> 202
10:15:56 seerr deploy -> 202
10:15:56 tandoor deploy -> 202
10:15:57 calcom deploy -> 202
10:15:57 docmost deploy -> 202
10:15:57 outline deploy -> 202
10:15:57 rallly deploy -> 202
10:17:47 zipline state running | files=[setup-gate-zipline.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-zipline.enkisfelhom.hu']}
10:17:50 gitea state running | files=[setup-gate-gitea.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-gitea.enkisfelhom.hu']}
10:17:58 seerr state running | files=[setup-gate-seerr.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-seerr.enkisfelhom.hu']}
10:18:01 tandoor state running | files=[setup-gate-tandoor.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-tandoor.enkisfelhom.hu']}
10:18:04 calcom state running | files=[setup-gate-calcom.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-calcom.enkisfelhom.hu']}
10:18:07 docmost state running | files=[setup-gate-docmost.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:57Z', 'hosts': ['r-docmost.enkisfelhom.hu']}
10:18:10 outline state running | files=[setup-gate-outline.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:57Z', 'hosts': ['r-outline.enkisfelhom.hu']}
10:18:13 rallly state running | files=[setup-gate-rallly.yml] record={'state': 'closed', 'since': '2026-09-29T08:15:57Z', 'hosts': ['r-rallly.enkisfelhom.hu']}
@@ -0,0 +1,8 @@
10:18:22 zipline HOUSEHOLD -> 200 in 0.25s | _felhom_gate/cb -> 301 r-zipline.enkisfelhom.hu/ -> 200 r-zipline.enkisfelhom.hu/dashboard | gate page False | app says 'Zipline'
10:18:22 gitea HOUSEHOLD -> 200 in 0.24s | __gate/start -> 302 r-gitea.enkisfelhom.hu/__felhom_gate/cb -> 200 r-gitea.enkisfelhom.hu/ | gate page False | app says 'Installation - Gitea: Git with a cup of '
10:18:23 seerr HOUSEHOLD -> 200 in 1.25s | hom.hu/__felhom_gate/cb -> 307 r-seerr.enkisfelhom.hu/ -> 200 r-seerr.enkisfelhom.hu/setup | gate page False | app says 'Setup - Jellyseerr'
10:18:24 tandoor HOUSEHOLD -> 200 in 0.41s | u/__felhom_gate/cb -> 302 r-tandoor.enkisfelhom.hu/ -> 200 r-tandoor.enkisfelhom.hu/setup/ | gate page False | app says 'Cookbook Setup'
10:18:25 calcom HOUSEHOLD -> 200 in 1.53s | /login -> 307 r-calcom.enkisfelhom.hu/auth/setup -> 200 r-calcom.enkisfelhom.hu/auth/setup | gate page False | app says 'Setup | Cal.com'
10:18:25 docmost HOUSEHOLD -> 200 in 0.21s | te/start -> 302 r-docmost.enkisfelhom.hu/__felhom_gate/cb -> 200 r-docmost.enkisfelhom.hu/ | gate page False | app says 'Docmost'
10:18:26 outline HOUSEHOLD -> 200 in 0.24s | te/start -> 302 r-outline.enkisfelhom.hu/__felhom_gate/cb -> 200 r-outline.enkisfelhom.hu/ | gate page False | app says 'Outline'
10:18:26 rallly HOUSEHOLD -> 200 in 0.25s | gate/start -> 302 r-rallly.enkisfelhom.hu/__felhom_gate/cb -> 200 r-rallly.enkisfelhom.hu/ | gate page False | app says 'Home'
@@ -0,0 +1,5 @@
10:21:29 zipline gate OPENED by probe 20s after the sync | files=[] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-zipline.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:22Z', 'opened_by': 'probe'}
10:21:29 gitea PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:21:34 docmost PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:21:39 calcom PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:21:44 tandoor PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
@@ -0,0 +1,12 @@
10:19:58 zipline PROBE http://zipline:3000/api/setup -> {"error":"E9001: Forbidden","code":9001,"statusCode":403}
10:20:01 docmost PROBE http://docmost:3000/api/workspace/public -> <!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no" />
<title>Docmost</title>
<meta name="theme-color" conte
10:20:05 gitea PROBE http://gitea:3000/api/v1/version -> {"version":"1.27.0"}
10:20:08 gitea PROBE http://gitea:3000/api/v1/settings/api -> {"max_response_items":50,"default_paging_num":30,"default_git_trees_per_page":1000,"default_max_blob_size":10485760,"default_max_response_size":104857600}
@@ -0,0 +1,14 @@
10:18:42 zipline PROBE http://zipline:3000/api/setup -> {"firstSetup":true}
10:18:45 seerr PROBE http://seerr:5055/api/v1/settings/public -> {"initialized":false,"applicationTitle":"Jellyseerr","applicationUrl":"","hideAvailable":false,"hideBlacklisted":false,"localLogin":true,"mediaServerLogin":true,"jellyfinExternalHost":"","jellyfinForgotPasswordUrl":"","movie4kEnabled":false,"series4kEnabled":false,"discoverRegion":"","streamingRegion":"","originalLanguage":"","mediaServerType":4,"partialRequestsEnabled":true,"enableSpecialEpisodes
10:18:48 docmost PROBE http://docmost:3000/api/workspace/public -> {"message":"Workspace not found","error":"Not Found","statusCode":404}
10:18:51 gitea PROBE http://gitea:3000/api/v1/version -> Not Found. <a href="/">Go to default page</a>.
10:18:55 tandoor PROBE http://tandoor:80/api/ -> <!doctype html>
<html lang="en">
<head>
<title>Bad Request (400)</title>
</head>
<body>
<h1>Bad Request (400)</h1><p></p>
</body>
</html>
10:18:58 calcom PROBE http://calcom:3000/api/auth/setup ->
@@ -0,0 +1,5 @@
10:19:42 zipline HOUSEHOLD SETUP through the gate -> 200 {"firstSetup":true,"user":{"id":"cmumeo944000201niiki0m8ei","username":"family","createdAt":"2026-09-29T08:19:42.243Z","
10:19:42 docmost HOUSEHOLD SETUP through the gate -> 200 {"data":{"id":"01a0ec3f-8b7e-7c62-915f-31cb204c90a7","name":"Family","description":null,"logo":null,"hostname":null,"cus
10:19:43 calcom HOUSEHOLD SETUP through the gate -> 200 {"message":"First admin user created successfully."}
10:19:48 gitea HOUSEHOLD SETUP through the gate -> 200 after POST: 200 /
10:19:48 tandoor HOUSEHOLD SETUP through the gate -> 200 after POST: 302 /setup/ -> 200 /accounts/login/
@@ -0,0 +1,13 @@
zipline POST /api/auth/register -> 400 {"error":"E1037: User registration is disabled","code":1037,"statusCode":400}
gitea GET /user/sign_up -> 200 <!DOCTYPE html> <html lang="en-US" data-theme="gitea-auto"> <head> <meta http-equiv="Content-Security-Policy"
docmost POST /api/auth/register -> 404 {"message":"Cannot POST /api/auth/register","error":"Not Found","statusCode":404}
docmost POST /api/auth/signup -> 404 {"message":"Cannot POST /api/auth/signup","error":"Not Found","statusCode":404}
calcom GET /signup -> 200 <!DOCTYPE html><html class="notranslate" translate="no" lang="en" dir="ltr" data-nextjs-router="app"><head non
calcom POST /api/auth/signup -> 201 {"message":"Created user"}
tandoor GET /accounts/signup/ -> 200 <!DOCTYPE html> <html> <head> <title>Sign Up Closed</title> <meta charset="utf-8"> <meta nam
Register Account
name="retype"
name="user_name"
calcom GET /auth/signup -> 308
calcom GET /signup -> 200
calcom GET /api/auth/signup -> 405
@@ -0,0 +1,8 @@
10:18:20 zipline STRANGER browser -> 200 302 r-zipline.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:20 gitea STRANGER browser -> 200 302 r-gitea.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:21 seerr STRANGER browser -> 200 302 r-seerr.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:21 tandoor STRANGER browser -> 200 302 r-tandoor.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:21 calcom STRANGER browser -> 200 302 r-calcom.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:21 docmost STRANGER browser -> 200 302 r-docmost.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:21 outline STRANGER browser -> 200 302 r-outline.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:18:21 rallly STRANGER browser -> 200 302 r-rallly.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
@@ -0,0 +1,4 @@
zipline FRESH install, before the setup: "features":{"oauthRegistration":false
zipline FRESH install, before the setup: "userRegistration":false}
zipline FRESH install, before the setup: "firstSetup":true
10:33:01 zipline (fresh): 'Done' pressed before the setup -> 409 {'data': None, 'error': 'Az alkalmazás szerint még nincs kész az első beállítás. Hozd létre a fiókodat, aztán próbáld új
@@ -0,0 +1,15 @@
10:34:01 adventurelog deploy -> 202
10:34:02 homebox deploy -> 202
10:34:02 papra deploy -> 202
10:34:02 plant-it deploy -> 409 {'ok': False, 'error': 'Ez az alkalmazás jelenleg nem telepíthető.'}
10:34:02 sparkyfitness deploy -> 202
10:34:02 vikunja deploy -> 202
10:34:02 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['SUBDOMAIN_DB']
10:34:03 wanderer deploy -> 202
10:35:47 adventurelog state unhealthy | files=[setup-gate-adventurelog.yml] record={'state': 'closed', 'since': '2026-09-29T08:34:01Z', 'hosts': ['r-adventurelog.enkisfelhom.hu']}
10:35:50 homebox state running | files=[setup-gate-homebox.yml] record={'state': 'closed', 'since': '2026-09-29T08:34:02Z', 'hosts': ['r-homebox.enkisfelhom.hu']}
10:35:53 papra state running | files=[setup-gate-papra.yml] record={'state': 'closed', 'since': '2026-09-29T08:34:02Z', 'hosts': ['r-papra.enkisfelhom.hu']}
10:45:59 plant-it state not_deployed | files=[] record=None
10:46:02 sparkyfitness state running | files=[setup-gate-sparkyfitness.yml] record={'state': 'closed', 'since': '2026-09-29T08:34:02Z', 'hosts': ['r-sparkyfitness.enkisfelhom.hu']}
10:46:05 vikunja state running | files=[setup-gate-vikunja.yml] record={'state': 'closed', 'since': '2026-09-29T08:34:02Z', 'hosts': ['r-vikunja.enkisfelhom.hu']}
10:46:08 wanderer state unhealthy | files=[setup-gate-wanderer.yml] record={'state': 'closed', 'since': '2026-09-29T08:34:03Z', 'hosts': ['hike-db.enkisfelhom.hu', 'r-wanderer.enkisfelhom.hu']}
@@ -0,0 +1,5 @@
adventurelog http://adventurelog:80/auth/is-registration-disabled/ -> {"is_disabled":false,"message":"Registration is disabled. Please contact the administrator if you need an account."}
homebox http://homebox:7745/api/v1/status -> {"health":true,"versions":null,"title":"Homebox","message":"Track, Manage, and Organize your Things","build":{"version":"v0.26.2","commit":"e01dd737238a3fa7e1a6454b37de6c6fc88c86e4","buildTime":""},"latest":{"version":"v0.26.2","date":"2026-06-14 01:57:51 +000
papra http://papra:1221/api/config -> {"config":{"version":"26.6.2","gitCommitSha":"cf0deb70cf7954853058b65d07b4cdebc84b916a","gitCommitDate":"2026-09-01T18:24:46+02:00","auth":{"isEmailVerificationRequired":false,"isPasswordResetEnabled":true,"isRegistrationEnabled":true,"showLegalLinksOnAuthPage
sparkyfitness http://sparkyfitness-server:3010/api/auth/settings ->
vikunja http://vikunja:3456/api/v1/info -> {"version":"v2.6.0","frontend_url":"https://r-vikunja.enkisfelhom.hu/","motd":"","link_sharing_enabled":true,"max_file_size":"20MB","max_items_per_page":50,"available_migrators":["vikunja-file","ticktick","wekan","csv","planka"],"task_attachments_enabled":true
@@ -0,0 +1,7 @@
10:46:10 adventurelog HOUSEHOLD -> 200 in 0.29s | > 302 r-adventurelog.enkisfelhom.hu/__felhom_gate/cb -> 200 r-adventurelog.enkisfelhom.hu/ | gate page False | app says 'AdventureLog'
10:46:10 homebox HOUSEHOLD -> 200 in 0.20s | te/start -> 302 r-homebox.enkisfelhom.hu/__felhom_gate/cb -> 200 r-homebox.enkisfelhom.hu/ | gate page False | app says '<!DOCTYPE html><html><head><meta charset'
10:46:10 papra HOUSEHOLD -> 200 in 0.20s | __gate/start -> 302 r-papra.enkisfelhom.hu/__felhom_gate/cb -> 200 r-papra.enkisfelhom.hu/ | gate page False | app says 'Papra - Document archiving and sharing p'
10:46:10 plant-it HOUSEHOLD -> 404 in 0.05s | 404 r-plant-it.enkisfelhom.hu/ | gate page False | app says '404 page not found '
10:46:10 sparkyfitness HOUSEHOLD -> 200 in 0.20s | 302 r-sparkyfitness.enkisfelhom.hu/__felhom_gate/cb -> 200 r-sparkyfitness.enkisfelhom.hu/ | gate page False | app says 'SparkyFitness'
10:46:10 vikunja HOUSEHOLD -> 200 in 0.20s | te/start -> 302 r-vikunja.enkisfelhom.hu/__felhom_gate/cb -> 200 r-vikunja.enkisfelhom.hu/ | gate page False | app says 'Vikunja'
10:46:10 wanderer HOUSEHOLD -> 404 in 0.05s | 404 r-wanderer.enkisfelhom.hu/ | gate page False | app says '404 page not found '
@@ -0,0 +1,5 @@
10:48:30 adventurelog PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:48:36 homebox PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:48:41 papra PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:48:46 sparkyfitness PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
10:48:52 vikunja PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
@@ -0,0 +1,24 @@
10:50:04 [X] stop -> 200 {'ok': True, 'message': 'Stack adventurelog stop completed'}
10:50:36 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'adventurelog', 'volumes_removed': ['adventurelog_adventurelog_media', 'adventurelog_adventurelog_postgres_data'], 'hdd_paths_r
10:50:44 [X] after remove: deployed=False leftovers='/opt/docker/stacks/adventurelog'
10:50:45 [X] stop -> 200 {'ok': True, 'message': 'Stack homebox stop completed'}
10:51:16 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'homebox', 'volumes_removed': ['homebox_homebox_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alk
10:51:25 [X] after remove: deployed=False leftovers='/opt/docker/stacks/homebox'
10:51:26 [X] stop -> 200 {'ok': True, 'message': 'Stack papra stop completed'}
10:51:57 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'papra', 'volumes_removed': ['papra_papra_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazá
10:52:06 [X] after remove: deployed=False leftovers='/opt/docker/stacks/papra'
10:52:07 [X] stop -> 200 {'ok': True, 'message': 'Stack sparkyfitness stop completed'}
10:52:39 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'sparkyfitness', 'volumes_removed': ['sparkyfitness_sparkyfitness_backup', 'sparkyfitness_sparkyfitness_db_data', 'sparkyfitnes
10:52:47 [X] after remove: deployed=False leftovers='/opt/docker/stacks/sparkyfitness'
10:52:47 [X] stop -> 200 {'ok': True, 'message': 'Stack vikunja stop completed'}
10:53:19 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'vikunja', 'volumes_removed': ['vikunja_vikunja_data', 'vikunja_vikunja_db'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [
10:53:28 [X] after remove: deployed=False leftovers='/opt/docker/stacks/vikunja'
10:53:29 [X] stop -> 200 {'ok': True, 'message': 'Stack wanderer stop completed'}
10:54:00 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'wanderer', 'volumes_removed': ['wanderer_wanderer_data', 'wanderer_wanderer_meili_data', 'wanderer_wanderer_plugins', 'wandere
10:54:09 [X] after remove: deployed=False leftovers='/opt/docker/stacks/wanderer'
10:54:09 [X] stop -> 200 {'ok': True, 'message': 'Stack gitea stop completed'}
10:54:41 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'gitea', 'volumes_removed': ['gitea_gitea_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazá
10:54:49 [X] after remove: deployed=False leftovers='/opt/docker/stacks/gitea'
10:55:00 [X] stop -> 200 {'ok': True, 'message': 'Stack calcom stop completed'}
10:55:32 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'calcom', 'volumes_removed': ['calcom_calcom_postgres_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': '
10:55:41 [X] after remove: deployed=False leftovers='/opt/docker/stacks/calcom'
@@ -0,0 +1,10 @@
10:47:43 adventurelog HOUSEHOLD SETUP through the gate -> 403 <!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta na
10:47:43 homebox HOUSEHOLD SETUP through the gate -> 204
10:47:43 papra HOUSEHOLD SETUP through the gate -> 200 {"token":"<redacted>","user":{"name":"Family","email":"family@spike.hu","emailVerified":false,"ima
10:47:43 sparkyfitness HOUSEHOLD SETUP through the gate -> 404
10:47:44 vikunja HOUSEHOLD SETUP through the gate -> 200 {"id":1,"name":"","username":"family","created":"2026-09-29T08:47:44Z","updated":"2026-09-29T08:47:44.100197843Z"}
@@ -0,0 +1,5 @@
10:48:57 adventurelog STRANGER sign-up -> 200 closed-answer=False '{"type":"redirect","status":302,"location":"/"}'
10:48:57 homebox STRANGER sign-up -> 204 closed-answer=False ''
10:48:58 papra STRANGER sign-up -> 200 closed-answer=False '{"token":"<redacted>","user":{"name":"'
10:48:58 sparkyfitness STRANGER sign-up -> 200 closed-answer=False '{"token":"<redacted>","user":{"name":"'
10:48:58 vikunja STRANGER sign-up -> 200 closed-answer=False '{"id":2,"name":"","username":"stranger6b31","created":"2026-'
@@ -0,0 +1,7 @@
10:46:08 adventurelog STRANGER browser -> 200 302 r-adventurelog.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:46:08 homebox STRANGER browser -> 200 302 r-homebox.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:46:09 papra STRANGER browser -> 200 302 r-papra.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:46:09 plant-it STRANGER browser -> 404 404 r-plant-it.enkisfelhom.hu/ gate-page=False | API POST -> 404 '404 page not found\n'
10:46:09 sparkyfitness STRANGER browser -> 200 302 r-sparkyfitness.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:46:09 vikunja STRANGER browser -> 200 302 r-vikunja.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:46:09 wanderer STRANGER browser -> 404 404 r-wanderer.enkisfelhom.hu/ gate-page=False | API POST -> 404 '404 page not found\n'
@@ -0,0 +1,7 @@
11:05:22 opengist AFTER, stranger browser -> 200 302 r-opengist.enkisfelhom.hu/ -> 200 r-opengist.enkisfelhom.hu/-/all | gate page False | len 15769
11:05:23 wishlist AFTER, stranger browser -> 200 307 r-wishlist.enkisfelhom.hu/ -> 200 r-wishlist.enkisfelhom.hu/login | gate page False | len 9593
11:05:23 termix AFTER, stranger browser -> 200 200 r-termix.enkisfelhom.hu/ | gate page False | len 2981
11:05:23 gramps-web AFTER, stranger browser -> 200 302 r-gramps-web.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start | gate page True | len 1824
11:05:23 radarr AFTER, stranger browser -> 200 302 r-radarr.enkisfelhom.hu/ -> 200 r-radarr.enkisfelhom.hu/login | gate page False | len 10020
11:05:23 sonarr AFTER, stranger browser -> 200 302 r-sonarr.enkisfelhom.hu/ -> 200 r-sonarr.enkisfelhom.hu/login | gate page False | len 9654
11:05:23 recipe-importer AFTER, stranger browser -> 200 recipe-importer.enkisfelhom.hu/ -> 200 r-recipe-importer.enkisfelhom.hu/settings | gate page False | len 14057
@@ -0,0 +1,18 @@
10:56:29 opengist deploy -> 202
10:56:29 wishlist deploy -> 202
10:56:30 termix deploy -> 202
10:56:30 gramps-web deploy -> 202
10:56:34 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/radarr']
10:56:34 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:56:34 radarr deploy -> 202
10:56:38 [1] made the drive paths this app requires: ['/mnt/felhom-drives/scratch_hdd/userdata/sonarr']
10:56:38 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['HDD_PATH']
10:56:38 sonarr deploy -> 202
10:56:38 recipe-importer deploy -> 202
10:56:42 opengist state running | files=[setup-gate-opengist.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:29Z', 'hosts': ['r-opengist.enkisfelhom.hu']}
10:56:56 wishlist state running | files=[setup-gate-wishlist.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:29Z', 'hosts': ['r-wishlist.enkisfelhom.hu']}
10:57:21 termix state running | files=[setup-gate-termix.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:30Z', 'hosts': ['r-termix.enkisfelhom.hu']}
10:58:40 gramps-web state running | files=[setup-gate-gramps-web.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:30Z', 'hosts': ['r-gramps-web.enkisfelhom.hu']}
10:58:43 radarr state running | files=[setup-gate-radarr.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:34Z', 'hosts': ['r-radarr.enkisfelhom.hu']}
10:58:47 sonarr state running | files=[setup-gate-sonarr.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:38Z', 'hosts': ['r-sonarr.enkisfelhom.hu']}
10:58:49 recipe-importer state running | files=[setup-gate-recipe-importer.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:38Z', 'hosts': ['r-recipe-importer.enkisfelhom.hu']}
@@ -0,0 +1,7 @@
10:59:04 opengist HOUSEHOLD -> 200 in 0.26s | /__felhom_gate/cb -> 302 r-opengist.enkisfelhom.hu/ -> 200 r-opengist.enkisfelhom.hu/-/all | gate page False | app says 'All gists - Opengist'
10:59:05 wishlist HOUSEHOLD -> 200 in 0.60s | m.hu/ -> 302 r-wishlist.enkisfelhom.hu/login -> 200 r-wishlist.enkisfelhom.hu/setup-wizard | gate page False | app says '<!doctype html> <html lang="en" dir="ltr'
10:59:05 termix HOUSEHOLD -> 200 in 0.20s | gate/start -> 302 r-termix.enkisfelhom.hu/__felhom_gate/cb -> 200 r-termix.enkisfelhom.hu/ | gate page False | app says 'Termix'
10:59:06 gramps-web HOUSEHOLD -> 200 in 0.52s | rt -> 302 r-gramps-web.enkisfelhom.hu/__felhom_gate/cb -> 200 r-gramps-web.enkisfelhom.hu/ | gate page False | app says 'Gramps Web'
10:59:06 radarr HOUSEHOLD -> 200 in 0.27s | gate/start -> 302 r-radarr.enkisfelhom.hu/__felhom_gate/cb -> 200 r-radarr.enkisfelhom.hu/ | gate page False | app says 'Radarr'
10:59:06 sonarr HOUSEHOLD -> 200 in 0.30s | gate/start -> 302 r-sonarr.enkisfelhom.hu/__felhom_gate/cb -> 200 r-sonarr.enkisfelhom.hu/ | gate page False | app says 'Sonarr'
10:59:06 recipe-importer HOUSEHOLD -> 200 in 0.26s | -> 302 r-recipe-importer.enkisfelhom.hu/ -> 200 r-recipe-importer.enkisfelhom.hu/settings | gate page False | app says 'Beállítások — Recept Importáló'
@@ -0,0 +1,11 @@
11:00:43 termix gate OPENED by probe 10s after the sync
11:04:38 gramps-web still closed files=[setup-gate-gramps-web.yml] record={'state': 'closed', 'since': '2026-09-29T08:56:30Z', 'hosts': ['r-gramps-web.enkisfelhom.hu']}
11:04:38 opengist PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
11:04:43 wishlist PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
11:04:49 radarr PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
11:04:54 sonarr PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
11:04:59 recipe-importer PRESS 'Done' -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
# gramps-web probe candidate DROPPED: after the setup the status answers HTTP 405 (the box reads only 200 answers, fail closed) — button instead
11:05:17 gramps-web PRESS 'Done' -> 409 {'data': None, 'error': 'Az alkalmazás szerint még nincs kész az első beállítás. Hozd létre a fiókodat, aztán próbáld új
11:05:42 gramps-web PRESS 'Done' (no status check any more) -> 200 {'data': {'opened': True}, 'error': '', 'ok': True}
11:05:48 gramps-web files=[] record={'state': 'open', 'since': '2026-09-29T08:56:30Z', 'hosts': ['r-gramps-web.enkisfelhom.hu'], 'opened_at': '2026-09-29T09:05:42Z', 'opened_by': 'household'}
@@ -0,0 +1,5 @@
termix http://termix:8080/users/setup-required -> {"setup_required":false}
termix http://termix:8080/users/registration-allowed -> {"allowed":true}
gramps-web http://gramps-web:5000/api/token/create_owner/ -> {"error": {"code": 405, "message": "Users already exist"}}
radarr http://radarr:7878/initialize.json ->
sonarr http://sonarr:8989/initialize.json ->
@@ -0,0 +1,8 @@
gramps-web http://gramps-web:5000/api/metadata/ -> {"message":"Missing JWT in headers or query_string (Missing Authorization Header; Missing 'jwt' query paramater)"}
termix http://termix:8080/users/registration-allowed -> {"allowed":true}
termix http://termix:8080/users/setup-required -> {"setup_required":true}
wishlist http://wishlist:3000/api/setup -> <!doctype html><html lang="en" dir="ltr" data-theme="wishlist"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <script src="https://code.iconify.design/iconify-icon/2.2.0/iconif
radarr http://radarr:7878/initialize.json -> { "apiRoot": "/api/v3", "apiKey":"<redacted>", "release": "6.4.4.10685-master", "version": "6.4.4.10685", "instanceName": "Radarr", "theme": "auto", "branch": "master", "analytics": true, "userHash": "159cbc1
sonarr http://sonarr:8989/initialize.json -> { "apiRoot": "/api/v3", "apiKey":"<redacted>", "release": "4.0.20.3014-main", "version": "4.0.20.3014", "instanceName": "Sonarr", "theme": "auto", "branch": "main", "analytics": true, "userHash": "a37955e8",
recipe-importer http://recipe-importer:8000/api/status -> <!doctype html><html lang=en><title>404 Not Found</title><h1>Not Found</h1><p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
opengist http://opengist:6157/-/register -> <!DOCTYPE html><html lang="en" class="h-full " data-theme="auto"><head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="https://r-opengist.enkisfelhom.hu" />
@@ -0,0 +1,9 @@
11:07:09 [X] stop -> 200 {'ok': True, 'message': 'Stack opengist stop completed'}
11:07:41 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'opengist', 'volumes_removed': ['opengist_opengist_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az
11:07:49 [X] after remove: deployed=False leftovers='/opt/docker/stacks/opengist'
11:07:50 [X] stop -> 200 {'ok': True, 'message': 'Stack wishlist stop completed'}
11:08:22 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'wishlist', 'volumes_removed': ['wishlist_wishlist_data', 'wishlist_wishlist_uploads'], 'hdd_paths_removed': [], 'hdd_paths_pre
11:08:30 [X] after remove: deployed=False leftovers='/opt/docker/stacks/wishlist'
11:08:30 [X] stop -> 200 {'ok': True, 'message': 'Stack termix stop completed'}
11:09:02 [X] remove (with drive data) -> 200 {'ok': True, 'data': {'removed': 'termix', 'volumes_removed': ['termix_termix_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalm
11:09:11 [X] after remove: deployed=False leftovers='/opt/docker/stacks/termix'
@@ -0,0 +1,14 @@
10:59:47 termix HOUSEHOLD SETUP through the gate -> 200 {"message":"User created","is_admin":true,"toast":{"type":"success","message":"User created: family"}}
10:59:47 opengist HOUSEHOLD SETUP through the gate -> 200 after POST: 302 /-/register -> 200 /
10:59:48 radarr HOUSEHOLD SETUP through the gate -> 202 {
"bindAddress": "*",
"port": 7878,
10:59:48 sonarr HOUSEHOLD SETUP through the gate -> 202 {
"bindAddress": "*",
"port": 8989,
10:59:48 gramps-web HOUSEHOLD SETUP through the gate -> 422 {"error":{"code":422,"message":"json: name: Unknown field.","messages":{"json":{"name":["Unknown field."]}}}}
10:59:49 wishlist HOUSEHOLD SETUP through the gate -> 200 {"type":"success","status":200,"data":"[{\"success\":1},true]"}
11:00:00 gramps-web HOUSEHOLD SETUP through the gate -> 201
@@ -0,0 +1,4 @@
11:06:00 opengist STRANGER sign-up -> 200 closed-answer=False 'landed on /'
11:06:01 wishlist STRANGER sign-up -> 200 closed-answer=False '{"type":"success","status":200,"data":"[{\\"success\\":1},true'
11:06:01 termix STRANGER sign-up -> 200 closed-answer=False '{"message":"User created","is_admin":false,"toast":{"type":"'
11:06:02 gramps-web STRANGER sign-up -> 500 closed-answer=False '<html>\n <head>\n <title>Internal Server Error</title>\n <'
@@ -0,0 +1,7 @@
10:59:03 opengist STRANGER browser -> 200 302 r-opengist.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:59:03 wishlist STRANGER browser -> 200 302 r-wishlist.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:59:03 termix STRANGER browser -> 200 302 r-termix.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:59:03 gramps-web STRANGER browser -> 200 302 r-gramps-web.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:59:04 radarr STRANGER browser -> 200 302 r-radarr.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:59:04 sonarr STRANGER browser -> 200 302 r-sonarr.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
10:59:04 recipe-importer STRANGER browser -> 200 302 r-recipe-importer.enkisfelhom.hu/ -> 200 felhom.enkisfelhom.hu/__gate/start gate-page=True | API POST -> 401 '{"error":"this app is waiting for its first setup"}'
@@ -0,0 +1,13 @@
# R-713: claper's after_install no longer pastes the password into Elixir code; it decodes ${ADMIN_PASSWORD|base64}.
import sys, os
p = os.path.join(sys.argv[1], "templates", "claper", ".felhom.yml")
s = open(p).read()
old = """ - 'u = Claper.Accounts.get_user_by_email("admin@claper.co"); r = if u, do: Claper.Accounts.update_user_password(u, "claper", %{password: "${ADMIN_PASSWORD}", password_confirmation: "${ADMIN_PASSWORD}"}), else"""
new = """ - 'pw = Base.decode64!("${ADMIN_PASSWORD|base64}"); u = Claper.Accounts.get_user_by_email("admin@claper.co"); r = if u, do: Claper.Accounts.update_user_password(u, "claper", %{password: pw, password_confirmation: pw}), else"""
assert s.count(old) == 1
s = s.replace(old, new)
old_c = "# Measured on 9202 2026-09-28: afterwards `admin@claper.co / claper` no longer authenticates.\n"
assert s.count(old_c) == 1
s = s.replace(old_c, old_c + "# R-713 (controller >= 0.281.0): the password reaches the Elixir code base64-encoded (${ADMIN_PASSWORD|base64}) and is\n# decoded there — a quote or #{ in a household-typed password can no longer end the string or run code.\n")
open(p, "w").write(s)
print("claper patched")
@@ -0,0 +1,2 @@
# Which sign-up endpoints exist (read from the containers on the docker network — no gate, no request that creates anything).
for u in "adventurelog http://adventurelog:80/auth/is-registration-disabled/" "homebox http://homebox:7745/api/v1/status" "papra http://papra:1221/api/config" "sparkyfitness http://sparkyfitness-server:3010/api/auth/settings" "vikunja http://vikunja:3456/api/v1/info"; do set -- $u; echo "$1 $2 -> $(docker exec felhom-controller curl -s -m 5 $2 | head -c 260)"; done
@@ -0,0 +1 @@
for u in "gramps-web http://gramps-web:5000/api/metadata/" "termix http://termix:8080/users/registration-allowed" "termix http://termix:8080/users/setup-required" "wishlist http://wishlist:3000/api/setup" "radarr http://radarr:7878/initialize.json" "sonarr http://sonarr:8989/initialize.json" "recipe-importer http://recipe-importer:8000/api/status" "opengist http://opengist:6157/-/register"; do set -- $u; echo "$1 $2 -> $(docker exec felhom-controller curl -s -m 5 $2 | tr -d '\n' | head -c 240)"; done
@@ -0,0 +1 @@
for u in "termix http://termix:8080/users/setup-required" "termix http://termix:8080/users/registration-allowed" "gramps-web http://gramps-web:5000/api/token/create_owner/" "radarr http://radarr:7878/initialize.json" "sonarr http://sonarr:8989/initialize.json"; do set -- $u; echo "$1 $2 -> $(docker exec felhom-controller curl -s -m 5 $2 | tr -d '\n' | head -c 200)"; done
@@ -0,0 +1,30 @@
# Adds setup_gate (+ optional probe / signup_block / add_people) to catalog templates. argv[1] = repo root, argv[2] = JSON spec file.
# spec: {app: {"probe": {"url","field","done","why"} | null, "signup_block": "<matcher>" | null,
# "add_people": {"hu": "...", "en": "..."} | null, "why": "<one line>"}}
import json, os, re, sys
R, spec = sys.argv[1], json.load(open(sys.argv[2]))
for app, sp in spec.items():
p = os.path.join(R, "templates", app, ".felhom.yml")
s = open(p).read()
if "\nsetup_gate:" in s:
# replace the whole managed block
s = re.sub(r"\n# --- The setup gate \(controller.*?(?=\n# --- App info|\napp_info:)", "", s, flags=re.S)
block = ["", "# --- The setup gate (controller >= 0.281.0, `09` §3 decisions 46-47) ---",
"# " + sp.get("why", "The first visitor would create the admin; a fresh install is closed to everyone but the household until the first setup is done."),
"setup_gate: true"]
pr = sp.get("probe")
if pr:
block += ["# " + pr["why"], "setup_done_probe:", f" url: {pr['url']}", f" field: {pr['field']}", f" done: \"{pr['done']}\""]
if sp.get("signup_block"):
block += ["# Decision 47: the app's own sign-up address is closed once the gate opens (" + sp.get("signup_why", "measured on 9202 2026-09-29") + ").",
"signup_block: \"" + sp["signup_block"].replace('"', '\\"') + "\""]
i = s.index("\n# --- App info") if "\n# --- App info" in s else s.index("\napp_info:")
s = s[:i] + "\n".join(block) + "\n" + s[i:]
ap = sp.get("add_people")
if ap:
# Hungarian: into app_info (after docs_url or default_creds or tagline line); English: into i18n.en.app_info
s = re.sub(r"(\napp_info:\n(?: [^\n]*\n)*? tagline: [^\n]*\n)", lambda m: m.group(1) + " add_people: " + json.dumps(ap["hu"], ensure_ascii=False) + "\n", s, count=1)
s = re.sub(r"(\n en:\n(?: [^\n]*\n)*? app_info:\n tagline: [^\n]*\n)", lambda m: m.group(1) + " add_people: " + json.dumps(ap["en"], ensure_ascii=False) + "\n", s, count=1)
assert s.count("add_people:") == 2, app
open(p, "w").write(s)
print("patched", len(spec), "apps in", R)
@@ -0,0 +1,121 @@
# Rollout harness (2026-09-29 afternoon, 9202, controller 0.281.0, drill catalog). Evidence, not product.
# python3 ro.py deploy app... fresh installs through the product; the gate file + record right after
# python3 ro.py stranger app... a stranger: API-style request and a browser request
# python3 ro.py household app... the household (dashboard session) opens the app through the gate
# python3 ro.py probe app URL read a candidate status URL from inside the box (the controller's own network)
# python3 ro.py state app... gate record, gate file, sign-up block file
# python3 ro.py press app the household's "Done" press (the product's endpoint)
# python3 ro.py after app... a stranger after the gate opened
# python3 ro.py remove app...
# Household test passwords are generated per run and kept in ro_creds.json (scratch, 0600, deleted at teardown).
import json, os, secrets, sys, time
sys.path.insert(0, '/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/pg-calcom-claper-2026-09-28/tools')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import walk as w
from browser import Browser, hopstr
D = "enkisfelhom.hu"
PW = open(os.path.join(w.SC, ".ctlpw")).read().strip()
JAR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ro_jar.json")
CREDS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ro_creds.json")
def sub(app):
return "r-" + app
def host(app):
return f"{sub(app)}.{D}"
def household():
b = Browser("household")
if os.path.exists(JAR):
b.jar = json.load(open(JAR))
if "felhom." + D not in b.jar:
b.login_dashboard(D, PW)
return b
def save(b):
old = os.umask(0o077)
json.dump(b.jar, open(JAR, "w"))
os.umask(old)
def creds(app):
c = json.load(open(CREDS)) if os.path.exists(CREDS) else {}
if app not in c:
c[app] = {"user": "family", "email": "family@spike.hu", "pw": "Hh" + secrets.token_hex(10) + "7"}
old = os.umask(0o077)
json.dump(c, open(CREDS, "w"))
os.umask(old)
return c[app]
def gstate(app):
f = w.guest(f"for f in setup-gate-{app}.yml signup-block-{app}.yml; do test -f /opt/docker/stacks/traefik/dynamic/$f && echo -n \"$f \"; done; echo").strip()
rec = (w.stack(app).get("app_config") or {}).get("setup_gate")
return f"files=[{f}] record={rec}"
def main():
cmd, args = sys.argv[1], sys.argv[2:]
w.login()
if cmd == "deploy":
for app in args:
v = w.deploy_values(app, sub(app))
code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": v, "kept_data": "fresh"})
w.say(app, "deploy ->", code, "" if code == "202" else str(d)[:200])
for app in args:
st = {}
for _ in range(120):
st = w.stack(app)
if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") in ("running", "unhealthy", "degraded"):
break
time.sleep(5)
w.say(app, "state", st.get("state"), "|", gstate(app))
elif cmd == "stranger":
for app in args:
s = Browser("stranger")
st, body, hops = s.req(f"https://{host(app)}/")
gp = "Jelentkezz be a Felhom" in body or "waiting for its first setup" in body
st2, body2, _ = s.req(f"https://{host(app)}/api/x", "POST", body={"a": 1}, accept="application/json")
w.say(app, f"STRANGER browser -> {st} {hopstr(hops)} gate-page={gp} | API POST -> {st2} {body2[:52]!r}")
elif cmd == "household":
b = household()
for app in args:
t0 = time.time()
st, body, hops = b.req(f"https://{host(app)}/")
gp = "Jelentkezz be a Felhom" in body or "waiting for its first setup" in body
title = body[body.find("<title>") + 7: body.find("</title>")][:40] if "<title>" in body else body[:40].replace("\n", " ")
w.say(app, f"HOUSEHOLD -> {st} in {time.time() - t0:.2f}s | {hopstr(hops)[-90:]} | gate page {gp} | app says {title!r}")
save(b)
elif cmd == "probe":
app, url = args[0], args[1]
out = w.guest(f"docker exec felhom-controller curl -s -m 8 {url} | head -c 400")
w.say(app, "PROBE", url, "->", out.strip()[:400])
elif cmd == "state":
for app in args:
w.say(app, gstate(app))
elif cmd == "press":
app = args[0]
code, d = w.ctl("POST", f"/apps/{app}/setup-gate/open", {})
w.say(app, "PRESS 'Done' ->", code, str(d)[:120])
time.sleep(2)
w.say(app, gstate(app))
elif cmd == "after":
for app in args:
s = Browser("stranger")
st, body, hops = s.req(f"https://{host(app)}/")
gp = "Jelentkezz be a Felhom" in body or "waiting for its first setup" in body
w.say(app, f"AFTER, stranger browser -> {st} {hopstr(hops)[-80:]} | gate page {gp} | len {len(body)}")
elif cmd == "remove":
for app in args:
w.remove(app)
else:
sys.exit("unknown command")
if __name__ == "__main__":
main()
@@ -0,0 +1,51 @@
# Part C live: the sign-up block on apps whose gate is open. argv: phase app...
# check — block file present; a stranger's sign-up is refused (page + API); the rest of the app still answers;
# the app page shows the sign-up card with the how-to and the window button
# window — the household opens sign-up for 15 minutes; a "family member" signs up; the page says until when
# closed — after the window: the block is back, a stranger is refused again
import json, secrets, subprocess, sys, time
sys.path.insert(0, '.')
import ro
from ro import w, host
from browser import Browser
SIGNUP = { # the stranger's / family member's sign-up call, per app
"gitea": ("GET", "/user/sign_up", None),
"calcom": ("POST", "/api/auth/signup", lambda n: {"username": n, "email": n + "@x.hu", "password": "Xx12345678z!A"}),
}
def try_signup(app, who):
m, p, body = SIGNUP[app]
s = Browser(who)
b = body(who + secrets.token_hex(3)) if body else None
st, text, _ = s.req(f"https://{host(app)}{p}", m, body=b, accept="application/json" if m == "POST" else "text/html")
closed = "sign-up is closed" in text or "nem lehet regisztr" in text
return st, closed, text[:70].replace("\n", " ")
w.login()
phase, apps = sys.argv[1], sys.argv[2:]
for app in apps:
if phase == "check":
w.say(app, ro.gstate(app))
st, closed, t = try_signup(app, "stranger")
s = Browser("stranger")
st2, body2, _ = s.req(f"https://{host(app)}/")
w.say(app, f"STRANGER sign-up -> {st} closed-answer={closed} | the app's home page -> {st2} len {len(body2)}")
s3 = Browser("stranger-browser")
st3, body3, _ = s3.req(f"https://{host(app)}{SIGNUP[app][1]}")
w.say(app, f"STRANGER browser on the sign-up address -> {st3} page says closed (hu-ascii 'nem lehet regisztr'): {'nem lehet regisztr' in body3}")
page = w.page(f"/apps/{app}")
w.say(app, f"APP PAGE: sign-up card {'id=\"signup-card\"' in page} | how-to {'id=\"add-people\"' in page} | window button {'/signup-window' in page}")
elif phase == "window":
code, d = w.ctl("POST", f"/apps/{app}/signup-window", {})
w.say(app, "HOUSEHOLD opens sign-up for 15 minutes ->", code, str(d)[:90])
time.sleep(3)
st, closed, t = try_signup(app, "family")
w.say(app, f"a family member signs up inside the window -> {st} closed-answer={closed} {t!r} | {ro.gstate(app)}")
page = w.page(f"/apps/{app}")
w.say(app, "APP PAGE during the window: says open until:", "nyitva, eddig" in page, "| window button hidden:", "/signup-window" not in page)
elif phase == "closed":
st, closed, t = try_signup(app, "stranger")
w.say(app, f"AFTER the window: stranger sign-up -> {st} closed-answer={closed} | {ro.gstate(app)}")
@@ -0,0 +1,190 @@
# The household's first setup of each app, THROUGH the gate (its browser holds the gate cookies from `ro.py household`).
# Each app's own first-run API, as its first-setup screen calls it. Passwords from ro_creds.json; never printed.
import json, sys, time
sys.path.insert(0, '.')
import ro
from ro import w, host
def J(b, app, method, path, body=None, headers=None):
st, text, _ = b.req(f"https://{host(app)}{path}", method, body=body, accept="application/json", headers=headers)
return st, text
def actualbudget(b, c):
return J(b, "actualbudget", "POST", "/account/bootstrap", {"password": c["pw"]})
def komga(b, c):
return J(b, "komga", "POST", "/api/v1/claim", headers={"X-Komga-Email": c["email"], "X-Komga-Password": c["pw"]})
def jellyfin(b, c):
out = []
out.append(J(b, "jellyfin", "POST", "/Startup/Configuration", {"UICulture": "en-US", "MetadataCountryCode": "HU", "PreferredMetadataLanguage": "hu"})[0])
out.append(J(b, "jellyfin", "GET", "/Startup/User")[0])
out.append(J(b, "jellyfin", "POST", "/Startup/User", {"Name": c["user"], "Password": c["pw"]})[0])
st, t = J(b, "jellyfin", "POST", "/Startup/Complete")
out.append(st)
return st, f"steps {out}"
def emby(b, c):
out = [J(b, "emby", "POST", "/emby/Startup/Configuration", {"UICulture": "en-US", "MetadataCountryCode": "HU", "PreferredMetadataLanguage": "hu"})[0],
J(b, "emby", "POST", "/emby/Startup/User", {"Name": c["user"], "Password": c["pw"]})[0]]
st, t = J(b, "emby", "POST", "/emby/Startup/Complete")
out.append(st)
return st, f"steps {out}"
def navidrome(b, c):
return J(b, "navidrome", "POST", "/auth/createAdmin", {"username": c["user"], "password": c["pw"]})
def ghost(b, c):
return J(b, "ghost", "POST", "/ghost/api/admin/authentication/setup/",
{"setup": [{"name": "Family", "email": c["email"], "password": c["pw"] + "Zz9", "blogTitle": "Family"}]},
headers={"Origin": f"https://{host('ghost')}"})
def home_assistant(b, c):
return J(b, "home-assistant", "POST", "/api/onboarding/users",
{"client_id": f"https://{host('home-assistant')}/", "name": "Family", "username": c["user"], "password": c["pw"], "language": "en"})
def romm(b, c):
J(b, "romm", "GET", "/api/heartbeat") # sets romm's csrftoken cookie, as its setup screen's first call does
tok = b.jar.get(host("romm"), {}).get("romm_csrftoken") or b.jar.get(host("romm"), {}).get("csrftoken", "")
return J(b, "romm", "POST", "/api/users", {"username": c["user"], "password": c["pw"], "email": c["email"], "role": "admin"},
headers={"X-CSRFToken": tok})
def zipline(b, c):
return J(b, "zipline", "POST", "/api/setup", {"username": c["user"], "password": c["pw"]})
def docmost(b, c):
return J(b, "docmost", "POST", "/api/auth/setup", {"name": "Family", "email": c["email"], "password": c["pw"], "workspaceName": "Family"})
def calcom(b, c):
return J(b, "calcom", "POST", "/api/auth/setup", {"username": c["user"], "email_address": c["email"], "full_name": "Family", "password": c["pw"] + "Aa1!"})
def _form(b, app, path, fields, csrf_name):
import re, urllib.parse
st, html, _ = b.req(f"https://{host(app)}{path}")
m = re.search(r'name="' + csrf_name + r'" value="([^"]+)"', html)
fields[csrf_name] = m.group(1) if m else ""
body = urllib.parse.urlencode(fields)
st, text, hops = b.req(f"https://{host(app)}{path}", "POST", body=body,
headers={"Content-Type": "application/x-www-form-urlencoded", "Referer": f"https://{host(app)}{path}", "Origin": f"https://{host(app)}"})
return st, f"after POST: {' -> '.join(str(h[0]) + ' ' + h[2] for h in hops)}"
def gitea(b, c):
import re
st, html, _ = b.req(f"https://{host('gitea')}/")
fields = dict(re.findall(r'<input[^>]*name="([^"]+)"[^>]*value="([^"]*)"', html))
for sel in re.findall(r'<select[^>]*name="([^"]+)"', html):
fields.setdefault(sel, "")
fields.update({"db_type": fields.get("db_type") or "sqlite3", "admin_name": "family", "admin_email": c["email"],
"admin_passwd": c["pw"], "admin_confirm_passwd": c["pw"]})
import urllib.parse
st, text, hops = b.req(f"https://{host('gitea')}/", "POST", body=urllib.parse.urlencode(fields),
headers={"Content-Type": "application/x-www-form-urlencoded"})
return st, f"after POST: {' -> '.join(str(h[0]) + ' ' + h[2] for h in hops)}"
def tandoor(b, c):
return _form(b, "tandoor", "/setup/", {"name": c["user"], "password": c["pw"], "password_confirm": c["pw"]}, "csrfmiddlewaretoken")
def homebox(b, c):
return J(b, "homebox", "POST", "/api/v1/users/register", {"name": "Family", "email": c["email"], "password": c["pw"]})
def papra(b, c):
return J(b, "papra", "POST", "/api/auth/sign-up/email", {"email": c["email"], "password": c["pw"], "name": "Family"},
headers={"Origin": f"https://{host('papra')}"})
def sparkyfitness(b, c):
return J(b, "sparkyfitness", "POST", "/api/auth/register", {"email": c["email"], "password": c["pw"], "full_name": "Family"})
def vikunja(b, c):
return J(b, "vikunja", "POST", "/api/v1/register", {"username": c["user"], "email": c["email"], "password": c["pw"]})
def adventurelog(b, c):
J(b, "adventurelog", "GET", "/auth/browser/v1/config")
tok = b.jar.get(host("adventurelog"), {}).get("csrftoken", "")
return J(b, "adventurelog", "POST", "/auth/browser/v1/auth/signup", {"username": c["user"], "email": c["email"], "password": c["pw"]},
headers={"X-CSRFToken": tok, "Referer": f"https://{host('adventurelog')}/", "Origin": f"https://{host('adventurelog')}"})
def termix(b, c):
return J(b, "termix", "POST", "/users/create", {"username": c["user"], "password": c["pw"]})
def opengist(b, c):
return _form(b, "opengist", "/-/register", {"username": c["user"], "password": c["pw"]}, "_csrf")
def _arr(b, app, c):
import re
st, html, _ = b.req(f"https://{host(app)}/initialize.json", accept="application/json")
key = json.loads(html).get("apiKey", "")
h = {"X-Api-Key": key}
st, cfg = J(b, app, "GET", "/api/v3/config/host", headers=h)
cfg = json.loads(cfg)
cfg.update({"authenticationMethod": "forms", "authenticationRequired": "enabled", "username": c["user"],
"password": c["pw"], "passwordConfirmation": c["pw"]})
st, t = J(b, app, "PUT", "/api/v3/config/host", cfg, headers=h)
return st, t[:40].replace(key, "<key>")
def radarr(b, c):
return _arr(b, "radarr", c)
def sonarr(b, c):
return _arr(b, "sonarr", c)
def gramps_web(b, c):
st, t = J(b, "gramps-web", "GET", "/api/token/create_owner/")
tok = json.loads(t).get("access_token", "") if st == 200 else ""
st, t = J(b, "gramps-web", "POST", "/api/users/" + c["user"] + "/create_owner/", {"password": c["pw"], "email": c["email"], "full_name": "Family"},
headers={"Authorization": "Bearer " + tok})
return st, t
def wishlist(b, c):
import urllib.parse
body = urllib.parse.urlencode({"name": "Family", "username": c["user"], "email": c["email"], "password": c["pw"], "confirmPassword": c["pw"]})
st, t, _ = b.req(f"https://{host('wishlist')}/signup", "POST", body=body, accept="application/json",
headers={"Content-Type": "application/x-www-form-urlencoded", "Origin": f"https://{host('wishlist')}", "x-sveltekit-action": "true"})
return st, t
FN = {"actualbudget": actualbudget, "komga": komga, "jellyfin": jellyfin, "emby": emby, "navidrome": navidrome,
"ghost": ghost, "home-assistant": home_assistant, "romm": romm,
"zipline": zipline, "docmost": docmost, "calcom": calcom, "gitea": gitea, "tandoor": tandoor,
"homebox": homebox, "papra": papra, "sparkyfitness": sparkyfitness, "vikunja": vikunja, "adventurelog": adventurelog,
"termix": termix, "opengist": opengist, "radarr": radarr, "sonarr": sonarr, "gramps-web": gramps_web, "wishlist": wishlist}
if __name__ == "__main__":
w.login()
b = ro.household()
for app in sys.argv[1:]:
c = ro.creds(app)
try:
st, text = FN[app](b, c)
except Exception as e:
st, text = "ERR", str(e)
for v in (c["pw"],):
text = str(text).replace(v, "<pw>")
w.say(app, "HOUSEHOLD SETUP through the gate ->", st, text[:120])
ro.save(b)
@@ -0,0 +1,26 @@
# After the gate opened: a STRANGER retries each app's own first-admin / sign-up call (no cookies). Refused = the
# app itself closes it; 2xx = open sign-up (decision 47 needs a block).
import sys, secrets
sys.path.insert(0, '.')
import ro
from ro import w, host
from browser import Browser
T = {
"actualbudget": ("POST", "/account/bootstrap", {"password": "x" + secrets.token_hex(6)}, None),
"komga": ("POST", "/api/v1/claim", None, {"X-Komga-Email": "s@x.hu", "X-Komga-Password": "x" + secrets.token_hex(6)}),
"jellyfin": ("POST", "/Startup/User", {"Name": "stranger", "Password": "x" + secrets.token_hex(6)}, None),
"emby": ("POST", "/emby/Startup/User", {"Name": "stranger", "Password": "x" + secrets.token_hex(6)}, None),
"navidrome": ("POST", "/auth/createAdmin", {"username": "stranger", "password": "x" + secrets.token_hex(6)}, None),
"ghost": ("POST", "/ghost/api/admin/authentication/setup/", {"setup": [{"name": "S", "email": "s@x.hu", "password": "Xx" + secrets.token_hex(8), "blogTitle": "S"}]}, {"Origin": "https://r-ghost.enkisfelhom.hu"}),
"home-assistant": ("POST", "/api/onboarding/users", {"client_id": "https://r-home-assistant.enkisfelhom.hu/", "name": "S", "username": "stranger", "password": "x" + secrets.token_hex(6), "language": "en"}, None),
"romm": ("POST", "/api/users", {"username": "stranger", "password": "x" + secrets.token_hex(6), "email": "s@x.hu", "role": "viewer"}, None),
}
w.login()
for app in sys.argv[1:]:
m, p, body, hd = T[app]
s = Browser("stranger")
if app == "romm":
s.req(f"https://{host(app)}/api/heartbeat", accept="application/json")
hd = {"X-CSRFToken": s.jar.get(host(app), {}).get("romm_csrftoken", "")}
st, text, _ = s.req(f"https://{host(app)}{p}", m, body=body, accept="application/json", headers=hd)
w.say(app, f"STRANGER retries {m} {p} after the setup -> {st} {text[:70]!r}")
@@ -0,0 +1,36 @@
# A stranger (no cookies) tries each app's own sign-up after the gate opened.
import sys, secrets, urllib.parse
sys.path.insert(0, '.')
import ro
from ro import w, host
from browser import Browser
def call(app, n):
s = Browser("stranger"); pw = "Xx" + secrets.token_hex(8) + "9"; em = n + "@x.hu"; o = {"Origin": f"https://{host(app)}"}
if app == "homebox": return s.req(f"https://{host(app)}/api/v1/users/register", "POST", body={"name": n, "email": em, "password": pw}, accept="application/json")
if app == "papra": return s.req(f"https://{host(app)}/api/auth/sign-up/email", "POST", body={"email": em, "password": pw, "name": n}, accept="application/json", headers=o)
if app == "sparkyfitness": return s.req(f"https://{host(app)}/api/auth/sign-up/email", "POST", body={"email": em, "password": pw, "name": n}, accept="application/json", headers=o)
if app == "vikunja": return s.req(f"https://{host(app)}/api/v1/register", "POST", body={"username": n, "email": em, "password": pw}, accept="application/json")
if app == "adventurelog":
b = urllib.parse.urlencode({"username": n, "email": em, "password1": pw, "password2": pw, "first_name": "S", "last_name": "S"})
return s.req(f"https://{host(app)}/signup", "POST", body=b, accept="application/json", headers=dict(o, **{"Content-Type": "application/x-www-form-urlencoded", "x-sveltekit-action": "true"}))
def call2(app, n):
s = Browser("stranger"); pw = "Xx" + secrets.token_hex(8) + "9"; em = n + "@x.hu"; o = {"Origin": f"https://{host(app)}"}
if app == "termix": return s.req(f"https://{host(app)}/users/create", "POST", body={"username": n, "password": pw}, accept="application/json")
if app == "gramps-web": return s.req(f"https://{host(app)}/api/users/{n}/register/", "POST", body={"email": em, "password": pw, "full_name": "S"}, accept="application/json")
if app == "wishlist":
b = urllib.parse.urlencode({"name": "S", "username": n, "email": em, "password": pw, "confirmPassword": pw})
return s.req(f"https://{host(app)}/signup", "POST", body=b, accept="application/json", headers=dict(o, **{"Content-Type": "application/x-www-form-urlencoded", "x-sveltekit-action": "true"}))
if app == "opengist":
import re
st, html, _ = s.req(f"https://{host(app)}/-/register")
m = re.search(r'name="_csrf" value="([^"]+)"', html)
if not m:
return st, html[:200], None
b = urllib.parse.urlencode({"username": n, "password": pw, "_csrf": m.group(1)})
st, t, h = s.req(f"https://{host(app)}/-/register", "POST", body=b, headers={"Content-Type": "application/x-www-form-urlencoded", "Origin": o["Origin"]})
return st, (t[:60] if st != 200 else ("landed on " + h[-1][2])), h
return call(app, n)
w.login()
for app in sys.argv[2:]:
st, t, _ = call2(app, sys.argv[1] + secrets.token_hex(2))
w.say(app, f"{sys.argv[1].upper()} sign-up -> {st} closed-answer={'sign-up is closed' in t or 'nem lehet regisztr' in t} {t[:60]!r}")
@@ -0,0 +1,149 @@
{
"actualbudget": {
"probe": {
"url": "http://actualbudget:5006/account/needs-bootstrap",
"field": "data.bootstrapped",
"done": "true",
"why": "measured on 9202 2026-09-29: data.bootstrapped false -> true once the server password is set."
}
},
"adventurelog": {
"signup_block": "Path(`/signup`) || PathPrefix(`/auth/browser/v1/auth/signup`) || PathPrefix(`/_allauth/browser/v1/auth/signup`) || PathPrefix(`/accounts/signup`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és a családtagod a saját címével regisztrál.",
"en": "Open sign-up for 15 minutes, and your family member signs up with their own address."
}
},
"calcom": {
"signup_block": "Path(`/signup`) || PathPrefix(`/auth/signup`) || PathPrefix(`/api/auth/signup`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's POST /api/auth/signup created an account (201) after the setup",
"add_people": {
"hu": "Beállítások → Adminisztráció → Felhasználók → Új felhasználó.",
"en": "Settings → Admin → Users → Add user."
}
},
"docmost": {},
"emby": {},
"ghost": {},
"gitea": {
"signup_block": "PathPrefix(`/user/sign_up`)",
"signup_why": "measured on 9202 2026-09-29: /user/sign_up served the registration form after the setup",
"add_people": {
"hu": "Webhely adminisztráció → Felhasználói fiókok → Új felhasználói fiók létrehozása.",
"en": "Site Administration → User Accounts → Create User Account."
}
},
"gramps-web": {
"signup_block": "PathRegexp(`^/api/users/[^/]+/register/`)",
"signup_why": "measured on 9202 2026-09-29: the self-registration address answered (500 here); closed to be safe",
"add_people": {
"hu": "Adminként: Beállítások → Felhasználók kezelése → Új felhasználó.",
"en": "As the admin: Settings → Manage users → New user."
}
},
"home-assistant": {},
"homebox": {
"signup_block": "PathPrefix(`/api/v1/users/register`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és küldd el a családtagodnak a Homebox csoport meghívó linkjét (Beállítások → Csoport → Meghívó).",
"en": "Open sign-up for 15 minutes and send your family member Homebox's group invite link (Settings → Group → Invite)."
}
},
"jellyfin": {
"probe": {
"url": "http://jellyfin:8096/System/Info/Public",
"field": "StartupWizardCompleted",
"done": "true",
"why": "measured on 9202 2026-09-29: StartupWizardCompleted false -> true after the startup wizard."
}
},
"komga": {
"probe": {
"url": "http://komga:25600/api/v1/claim",
"field": "isClaimed",
"done": "true",
"why": "measured on 9202 2026-09-29: isClaimed false -> true once the admin claimed it."
}
},
"navidrome": {},
"opengist": {
"signup_block": "PathPrefix(`/-/register`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és a családtagod regisztrál.",
"en": "Open sign-up for 15 minutes, and your family member signs up."
}
},
"outline": {},
"papra": {
"signup_block": "PathPrefix(`/api/auth/sign-up`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre; a családtagod regisztrál, aztán meghívod a szervezetedbe (Szervezet → Tagok → Meghívás).",
"en": "Open sign-up for 15 minutes; your family member signs up, then you invite them into your organization (Organization → Members → Invite)."
}
},
"plant-it": {},
"radarr": {},
"rallly": {},
"recipe-importer": {},
"romm": {
"probe": {
"url": "http://romm:8080/api/heartbeat",
"field": "SYSTEM.SHOW_SETUP_WIZARD",
"done": "false",
"why": "measured on 9202 2026-09-29: SYSTEM.SHOW_SETUP_WIZARD true -> false once the first user exists."
}
},
"seerr": {},
"sonarr": {},
"sparkyfitness": {
"signup_block": "PathPrefix(`/api/auth/sign-up`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és a családtagod regisztrál.",
"en": "Open sign-up for 15 minutes, and your family member signs up."
}
},
"tandoor": {},
"termix": {
"probe": {
"url": "http://termix:8080/users/setup-required",
"field": "setup_required",
"done": "false",
"why": "measured on 9202 2026-09-29: setup_required true -> false once the first user exists."
},
"signup_block": "PathPrefix(`/users/create`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és a családtagod regisztrál.",
"en": "Open sign-up for 15 minutes, and your family member signs up."
}
},
"vikunja": {
"signup_block": "PathPrefix(`/api/v1/register`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és a családtagod regisztrál; utána megoszthatod vele a projektjeidet.",
"en": "Open sign-up for 15 minutes, and your family member signs up; then share your projects with them."
}
},
"wishlist": {
"signup_block": "Path(`/signup`)",
"signup_why": "measured on 9202 2026-09-29: a stranger's sign-up succeeded after the setup",
"add_people": {
"hu": "Nyisd meg a regisztrációt 15 percre, és a családtagod regisztrál (a Wishlist meghívó linkje is ebben az időben működik).",
"en": "Open sign-up for 15 minutes, and your family member signs up (Wishlist's invite link works in that time too)."
}
},
"zipline": {
"probe": {
"url": "http://zipline:3000/api/server/public",
"field": "firstSetup",
"done": "false",
"why": "measured on 9202 2026-09-29: firstSetup true -> false once the first user exists (/api/setup itself answers 403 after the setup, so it cannot be the check)."
}
}
}
@@ -0,0 +1,10 @@
D=enkisfelhom.hu
c() { printf '%s %s %s -> ' "$1" "$2" "$3"; curl -sk -o /tmp/su.b -w '%{http_code}' -H "Host: r-$1.$D" -H 'Content-Type: application/json' -H 'Accept: application/json' -X "$2" ${4:+-d "$4"} https://127.0.0.1$3; echo " $(head -c 110 /tmp/su.b | tr '\n' ' ')"; }
c zipline POST /api/auth/register '{"username":"stranger","password":"Xx12345678z"}'
c gitea GET /user/sign_up
c docmost POST /api/auth/register '{"name":"S","email":"s@x.hu","password":"Xx12345678z"}'
c docmost POST /api/auth/signup '{"name":"S","email":"s@x.hu","password":"Xx12345678z"}'
c calcom GET /signup
c calcom POST /api/auth/signup '{"username":"stranger","email":"s@x.hu","password":"Xx12345678z!A"}'
c tandoor GET /accounts/signup/
rm -f /tmp/su.b
@@ -0,0 +1 @@
curl -sk -H "Host: r-gitea.enkisfelhom.hu" https://127.0.0.1/user/sign_up | grep -oiE "disabled[^<]{0,60}|name=\"user_name\"|name=\"retype\"|Register Account" | sort -u; for p in /auth/signup /signup /api/auth/signup; do echo "calcom GET $p -> $(curl -sk -o /dev/null -w %{http_code} -H "Host: r-calcom.enkisfelhom.hu" https://127.0.0.1$p)"; done
@@ -0,0 +1,5 @@
10:49:49 vikunja household sign-in -> 200
10:49:49 homebox household sign-in -> 200
10:49:50 papra household sign-in -> 200
10:49:50 vikunja: household opens sign-up for 15 min -> 200 {'data': {'open_until': '2026-09-29T09:04:50Z'}, 'error': '', 'ok': True}
10:49:53 vikunja FAMILY sign-up -> 200 closed-answer=False '{"id":3,"name":"","username":"familyf072","created":"2026-09'
@@ -0,0 +1,5 @@
10:49:36 adventurelog STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
10:49:36 homebox STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
10:49:36 papra STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
10:49:36 sparkyfitness STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
10:49:36 vikunja STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
@@ -0,0 +1,6 @@
11:06:56 opengist home page after the block -> 200 15765
11:06:56 wishlist home page after the block -> 200 9593
11:06:56 termix home page after the block -> 200 2981
11:06:56 gramps-web home page after the block -> 200 1456
11:06:56 radarr home page after the block -> 200 10020
11:06:56 sonarr home page after the block -> 200 9654
@@ -0,0 +1,4 @@
11:06:55 opengist STRANGER sign-up -> 403 closed-answer=False '\n<!DOCTYPE html>\n<html lang="hu">\n<head>\n <meta charset="'
11:06:55 wishlist STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
11:06:55 termix STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
11:06:56 gramps-web STRANGER sign-up -> 403 closed-answer=True '{"error":"sign-up is closed on this app; its admin adds new '
@@ -0,0 +1,8 @@
10:23:30 gitea files=[signup-block-gitea.yml] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-gitea.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:29Z', 'opened_by': 'household'}
10:23:30 gitea STRANGER sign-up -> 403 closed-answer=True | the app's home page -> 200 len 14539
10:23:30 gitea STRANGER browser on the sign-up address -> 403 page says closed (hu-ascii 'nem lehet regisztr'): True
10:23:30 gitea APP PAGE: sign-up card True | how-to True | window button True
10:23:33 calcom files=[signup-block-calcom.yml] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-calcom.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:39Z', 'opened_by': 'household'}
10:23:34 calcom STRANGER sign-up -> 403 closed-answer=True | the app's home page -> 200 len 343294
10:23:34 calcom STRANGER browser on the sign-up address -> 403 page says closed (hu-ascii 'nem lehet regisztr'): True
10:23:34 calcom APP PAGE: sign-up card True | how-to True | window button True
@@ -0,0 +1,2 @@
10:43:25 gitea AFTER the window: stranger sign-up -> 403 closed-answer=True | files=[signup-block-gitea.yml] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-gitea.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:29Z', 'opened_by': 'household', 'signup_open_until': '2026-09-29T08:38:43Z'}
10:43:28 calcom AFTER the window: stranger sign-up -> 403 closed-answer=True | files=[signup-block-calcom.yml] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-calcom.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:39Z', 'opened_by': 'household', 'signup_open_until': '2026-09-29T08:38:49Z'}
@@ -0,0 +1,6 @@
10:23:43 gitea HOUSEHOLD opens sign-up for 15 minutes -> 200 {'data': {'open_until': '2026-09-29T08:38:43Z'}, 'error': '', 'ok': True}
10:23:49 gitea a family member signs up inside the window -> 200 closed-answer=False '<!DOCTYPE html> <html lang="en-US" data-theme="gitea-auto"> <head> \t<m' | files=[] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-gitea.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:29Z', 'opened_by': 'household', 'signup_open_until': '2026-09-29T08:38:43Z'}
10:23:49 gitea APP PAGE during the window: says open until: True | window button hidden: True
10:23:49 calcom HOUSEHOLD opens sign-up for 15 minutes -> 200 {'data': {'open_until': '2026-09-29T08:38:49Z'}, 'error': '', 'ok': True}
10:23:56 calcom a family member signs up inside the window -> 201 closed-answer=False '{"message":"Created user"}' | files=[] record={'state': 'open', 'since': '2026-09-29T08:15:56Z', 'hosts': ['r-calcom.enkisfelhom.hu'], 'opened_at': '2026-09-29T08:21:39Z', 'opened_by': 'household', 'signup_open_until': '2026-09-29T08:38:49Z'}
10:23:56 calcom APP PAGE during the window: says open until: True | window button hidden: True
@@ -0,0 +1,4 @@
## 2026-09-29T09:07:17Z floor 0.280.0 -> 0.281.0 (min_agent 0.131.0 from the v0.281.0 header)
HTTP/1.1 303 See Other
Location: /configuration?flash=floor_set
name="min_controller_version" value="0.281.0"
@@ -0,0 +1 @@
for p in / /register /login /all; do echo "$p -> $(curl -sk -o /dev/null -w "%{http_code} %{redirect_url}" -H "Host: s-og.enkisfelhom.hu" https://127.0.0.1$p)"; done; docker logs --tail 5 opengist 2>&1 | cut -c1-160; grep -n "rule=" /opt/docker/stacks/opengist/docker-compose.yml
@@ -0,0 +1,5 @@
echo "== wishlist env + signup settings"
docker exec wishlist sh -c 'env | grep -iE "SIGN|REGIST|ORIGIN" | sed "s/=.*/=<set>/"; ls /usr/src/app 2>/dev/null | head; grep -rlo "enableSignup\|signup" /usr/src/app/build/server 2>/dev/null | head -3'
docker exec wishlist sh -c 'grep -rhoE "\"?(enableSignup|signup\.enabled|allowSignup|SIGNUP)[^,;]{0,60}" /usr/src/app/build 2>/dev/null | sort -u | head -8'
echo "== vikunja registration env + user CLI"
docker exec vikunja sh -c '/app/vikunja/vikunja user --help 2>&1 | head -12; env | grep -i REGIST | sed "s/=.*/=<set>/"' 2>&1 | head -16
@@ -0,0 +1,3 @@
echo "== vikunja CLI"; docker exec vikunja /app/vikunja/vikunja user --help 2>&1 | head -14
echo "== routes: sign-up endpoints answer (stranger, no cookie)"
for u in "s-og /register" "s-og /login" "s-wl /signup" "s-wl /api/signup" "p0-vik /api/v1/register" "p0-vik /register"; do set -- $u; echo "$1$2 GET -> $(curl -sk -o /dev/null -w '%{http_code}' -H "Host: $1.enkisfelhom.hu" https://127.0.0.1$2)"; done
@@ -0,0 +1,4 @@
10:56:29 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['ADMIN_PASSWORD']
10:56:29 claper deploy with a typed password holding a quote and #{ -> 202
10:56:59 claper after_install record after 30s: ok=True detail='None'
claper default 'claper' -> ANS=false | typed (quote + #{) -> ANS=true | wrong -> ANS=false
@@ -0,0 +1,4 @@
# claper's own auth in its running node (rpc): default vs typed vs wrong. The typed value travels base64-encoded.
IFS= read -r P
chk() { B=$(printf %s "$1" | base64 -w0); docker exec -e ELIXIR_ERL_OPTIONS=+fnu claper /app/bin/claper rpc "IO.puts(\"ANS=#{Claper.Accounts.get_user_by_email_and_password(\"admin@claper.co\", Base.decode64!(\"$B\")) != nil}\")" 2>&1 | grep -o 'ANS=[a-z]*'; }
echo "claper default 'claper' -> $(chk claper) | typed (quote + #{) -> $(chk "$P") | wrong -> $(chk wrongxyz123)"
@@ -0,0 +1,21 @@
# R-713 live: claper installed with a household-typed password holding " and #{ ; the box's after_install must set it
# (base64 into the Elixir code) — then the default fails and the typed one works. The password stays in memory/stdin.
import json, os, secrets, subprocess, sys, time
sys.path.insert(0, '.')
import ro
from ro import w
w.login()
pw = 'Fam"ily#{x}-' + secrets.token_hex(6)
v = w.deploy_values("claper", "r-claper"); v["ADMIN_PASSWORD"] = pw
code, d = w.ctl("POST", "/api/stacks/claper/deploy", {"values": v, "kept_data": "fresh"})
w.say("claper deploy with a typed password holding a quote and #{ ->", code)
t0 = time.time()
while time.time() - t0 < 900:
rec = (w.stack("claper").get("app_config") or {}).get("after_install")
if rec:
w.say(f"claper after_install record after {time.time()-t0:.0f}s: ok={rec.get('ok')} detail={str(rec.get('detail'))[:90]!r}"); break
time.sleep(10)
script = open("claper_login.sh").read()
subprocess.run(["ssh", "hp", "cat > /root/cl.sh && pct push 9202 /root/cl.sh /root/cl.sh && rm /root/cl.sh"], input=script, text=True, check=True, capture_output=True)
r = subprocess.run(["ssh", "hp", "pct exec 9202 -- bash /root/cl.sh; pct exec 9202 -- rm -f /root/cl.sh"], input=pw + "\n", text=True, capture_output=True, timeout=300)
print(r.stdout.replace(pw, "<typed>"), end="")
@@ -0,0 +1,9 @@
# RP17 — Part A: the press asks the probe first
# mutation in internal/web/setup_gate.go:
# - '\tif has, done, got, perr := s.stackMgr.SetupGateProbe(found.Name); has && (perr != nil || !done) {'
# + '\tif has, done, got, perr := s.stackMgr.SetupGateProbe(found.Name); false && has && (perr != nil || !done) { // RED-PROOF RP17'
# go test -run ^TestSetupGateButton_RefusedWhileTheAppSaysNotDone$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGateButton_RefusedWhileTheAppSaysNotDone
signup_block_test.go:39: press before the setup: 200 {"data":{"opened":true},"error":"","ok":true}
--- FAIL: TestSetupGateButton_RefusedWhileTheAppSaysNotDone (0.07s)
@@ -0,0 +1,9 @@
# RP18 — Part A: an unreadable status refuses (fail closed)
# mutation in internal/web/setup_gate.go:
# - 'has && (perr != nil || !done) {'
# + 'has && (perr == nil && !done) { // RED-PROOF RP18'
# go test -run ^TestSetupGateButton_RefusedWhileTheAppSaysNotDone$ ./internal/web
# verdict: RED (assertion)
=== RUN TestSetupGateButton_RefusedWhileTheAppSaysNotDone
signup_block_test.go:46: an unreadable status: 200 — want 409 (fail closed)
--- FAIL: TestSetupGateButton_RefusedWhileTheAppSaysNotDone (0.07s)
@@ -0,0 +1,9 @@
# RP19 — Part C: the block goes up before the gate comes down
# mutation in internal/stacks/setup_gate.go:
# - '\tif block != "" {\n\t\tif err := m.writeSignupBlock('
# + '\tif false && block != "" { // RED-PROOF RP19\n\t\tif err := m.writeSignupBlock('
# go test -run ^TestSignupBlock_TheGateOpensOnlyWithTheBlockUp$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSignupBlock_TheGateOpensOnlyWithTheBlockUp
signup_block_test.go:25: no sign-up block after the gate opened
--- FAIL: TestSignupBlock_TheGateOpensOnlyWithTheBlockUp (0.00s)
@@ -0,0 +1,9 @@
# RP20 — Part C: an unwritable block keeps the gate closed
# mutation in internal/stacks/setup_gate.go:
# - '\t\t\treturn fmt.Errorf("setup gate %s: the sign-up block could not be written, so the gate stays closed: %w", name, err)'
# + '\t\t\t_ = err // RED-PROOF RP20'
# go test -run ^TestSignupBlock_UnwritableBlockKeepsTheGateClosed$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSignupBlock_UnwritableBlockKeepsTheGateClosed
signup_block_test.go:52: the gate opened although the sign-up block could not be written
--- FAIL: TestSignupBlock_UnwritableBlockKeepsTheGateClosed (0.00s)
@@ -0,0 +1,9 @@
# RP21 — Part C: the window closes again
# mutation in internal/stacks/signup_block.go:
# - '\treturn err == nil && now.Before(t)\n'
# + '\treturn err == nil || now.Before(t) // RED-PROOF RP21\n'
# go test -run ^TestSignupBlock_TheWindowOpensAndCloses$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSignupBlock_TheWindowOpensAndCloses
signup_block_test.go:87: the window passed but the block did not come back
--- FAIL: TestSignupBlock_TheWindowOpensAndCloses (0.00s)
@@ -0,0 +1,9 @@
# RP22 — Part 0 / C: never a block on an app this box did not gate
# mutation in internal/stacks/signup_block.go:
# - '\t\tif !st.Deployed || g == nil || g.State != SetupGateOpen || strings.TrimSpace(st.Meta.SignupBlock) == "" || g.signupWindowOpen(now) {\n\t\t\tcontinue\n\t\t}\n\t\twants[n] = want{hosts: append([]string(nil), g.Hosts...), fragment: st.Meta.SignupBlock}'
# + '\t\tif !st.Deployed || strings.TrimSpace(st.Meta.SignupBlock) == "" || g.signupWindowOpen(now) { // RED-PROOF RP22\n\t\t\tcontinue\n\t\t}\n\t\thosts := []string{"gapp.example.hu"}\n\t\tif g != nil {\n\t\t\thosts = g.Hosts\n\t\t}\n\t\twants[n] = want{hosts: hosts, fragment: st.Meta.SignupBlock}'
# go test -run ^TestSignupBlock_NeverOnAnAppThisBoxDidNotGate$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSignupBlock_NeverOnAnAppThisBoxDidNotGate
signup_block_test.go:107: a sign-up block appeared on an app this box never gated
--- FAIL: TestSignupBlock_NeverOnAnAppThisBoxDidNotGate (0.00s)
@@ -0,0 +1,9 @@
# RP23 — Part 0: a catalog setup_gate never closes an installed app
# mutation in internal/stacks/setup_gate.go:
# - '\t\tif !st.Deployed || st.AppConfig == nil || !st.AppConfig.SetupGate.Closed() {\n\t\t\tcontinue\n\t\t}\n\t\trec := *st.AppConfig.SetupGate'
# + '\t\tif !st.Deployed || st.AppConfig == nil || !(st.AppConfig.SetupGate.Closed() || st.Meta.SetupGate) { // RED-PROOF RP23\n\t\t\tcontinue\n\t\t}\n\t\trec := SetupGateRecord{Hosts: []string{"gapp.example.hu"}}'
# go test -run ^TestSignupBlock_NeverOnAnAppThisBoxDidNotGate$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestSignupBlock_NeverOnAnAppThisBoxDidNotGate
signup_block_test.go:110: a catalog setup_gate closed an app that was already installed
--- FAIL: TestSignupBlock_NeverOnAnAppThisBoxDidNotGate (0.00s)
@@ -0,0 +1,9 @@
# RP24 — Part D / R-713: a value read as code is refused
# mutation in internal/stacks/after_install.go:
# - '\t\tcodeShaped := !argumentShaped(a)'
# + '\t\tcodeShaped := false && !argumentShaped(a) // RED-PROOF RP24'
# go test -run ^TestR713_AValueThatWouldBeReadAsCodeIsRefused$ ./internal/stacks
# verdict: RED (assertion)
=== RUN TestR713_AValueThatWouldBeReadAsCodeIsRefused
after_install_test.go:107: a quote and #{ went into Elixir code: <nil>
--- FAIL: TestR713_AValueThatWouldBeReadAsCodeIsRefused (0.00s)