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
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:
@@ -16,6 +16,14 @@
|
||||
> and holds nothing of its own; this file does hold its own content, namely the standing rulings below.
|
||||
|
||||
|
||||
> **2026-09-29 afternoon — operator ruling decision 47 (R-711 option A) + one CC-unattended decision (operator may
|
||||
> reverse):** sign-up is closed by a box-side block of the app's own sign-up address once the gate opens, with a
|
||||
> household 15-minute window (`internal/stacks/signup_block.go`, `.felhom.yml` `signup_block:` + `app_info.add_people`)
|
||||
> — per-app switches were measured unusable. Controller **v0.281.0** (the press asks the probe first; sign-up block;
|
||||
> R-713 `${NAME|base64}`), floor 0.281.0. Catalog `6faf432`: 32 of 34 class-4 apps gated (9 by probe), 11 sign-up
|
||||
> blocks, claper base64. Not gated: wanderer (R-714). Open: R-715 (probe shapes), R-716 (installed apps keep open
|
||||
> sign-up — operator). Report: `REPORT-gate-rollout-2026-09-29.md`.
|
||||
|
||||
> **2026-09-29 — operator rulings:** (1) CC changed the admin passwords of demo-hp's installed bookstack and calibre-web
|
||||
> (stored in the operator's credentials file as `DEMO_HP_BOOKSTACK_*` / `DEMO_HP_CALIBRE_*`, values never in a repo);
|
||||
> (2) decision 46 — the setup gate is SPIKED first. **The spike PASSED** (`audits/login-gate-2026-09-29/B/B-VERDICT.md`);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# REPORT — 2026-09-29 afternoon: the setup gate on the other 30 apps; "Done" asks the app first; open sign-up closed after the first admin; claper's password never in code
|
||||
|
||||
Architecture read first: `09` §3 decisions 45–47, `01-topology-and-trust.md` §5, `audits/login-gate-2026-09-29/B/B-VERDICT.md`,
|
||||
`app-catalog-felhom.eu/FIRST-ADMIN.md`, rows R-707, R-711, R-713. Controller **v0.281.0** (one release). Floor 0.281.0;
|
||||
both demo boxes run it. Catalog `6faf432`. Evidence: `documentation/audits/gate-rollout-2026-09-29/`
|
||||
(0 = Part 0, A, B = per-app, C = sign-up, D = claper, redproofs).
|
||||
|
||||
## The Parts
|
||||
|
||||
| Part | Step | State | Note |
|
||||
|---|---|---|---|
|
||||
| — | decision 47 recorded first | done | `09` §3 + `01` §5 before any work |
|
||||
| 0 | installed app never gated by a catalog change | done — **holds** | 9202: vikunja installed ungated and set up, then the drill catalog added `setup_gate: true`, synced, two loop ticks, a controller restart: still answered strangers, no gate file, no record (`0/P0-1`, `P0-2`). Code: the gate is set only in `DeployStack`. Pinned by `TestSignupBlock_NeverOnAnAppThisBoxDidNotGate` (RP23). After the live push, the demo boxes' installed class-4 apps (adventurelog, docmost, opengist, romm; opengist) got no gate and no block (`0/P0-3`) |
|
||||
| A1 | "Done" asks the probe first | done | refuses while the app says not done and when it cannot be read (409 + the sentence). Live: zipline pressed before its setup → 409, twice (`A/`, `B/B2-zipline-probe-before.txt`) |
|
||||
| A2 | confirm for an app without a probe | done | in-page `felhomConfirm` with the sentence (native confirm is banned by a gate) |
|
||||
| A3 | tests + red-proofs | done | RP17, RP18 |
|
||||
| B | the gate on the other 30 | done — **28 gated (25 fully proven, 3 opening not provable here), 2 not gated** | per-app table below |
|
||||
| C1 | spike: how sign-up can be closed | done, **mechanism changed** | opengist, wishlist: only in their own admin settings; vikunja: an env, but then no way to add a user but its CLI. Chosen: a box-side block of the app's own sign-up address + a household 15-minute window (CC-unattended decision, `09` §3 decision 47) |
|
||||
| C2 | per app | done | 11 apps let a stranger sign up after the setup → blocked and refused; 11 more refuse by themselves; window proven (gitea, calcom, vikunja) and closes again (gitea, calcom) |
|
||||
| C3 | apps that cannot close sign-up | **1: wanderer** | not gated at all (R-714) — STATUS asks |
|
||||
| D | R-713 | done | code-bound values refused; `${NAME|base64}`; claper proven live with a typed password holding `"` and `#{` (default refused, typed signs in). RP24 |
|
||||
|
||||
**Recommendation not followed, one line why (standing rule 4):** the ruling says "only the admin adds people, from the
|
||||
app's own user page"; for apps with no such page (opengist, vikunja, termix, sparkyfitness, adventurelog) the page
|
||||
says to open sign-up for 15 minutes instead — the only way a family member can join those apps at all.
|
||||
|
||||
### Part B / C — one row per app (9202, controller 0.281.0, drill catalog)
|
||||
|
||||
| app | gate proven (stranger refused · household reached setup · opened · answered after) | opened by | sign-up after the setup |
|
||||
|---|---|---|---|
|
||||
| actualbudget | yes | probe `data.bootstrapped` (M) | refused by the app |
|
||||
| komga | yes | probe `isClaimed` (M) | refused by the app |
|
||||
| jellyfin | yes | probe `StartupWizardCompleted` (M) | refused by the app |
|
||||
| romm | yes | probe `SYSTEM.SHOW_SETUP_WIZARD` (M) | refused by the app |
|
||||
| zipline | yes | probe `/api/server/public firstSetup` (M) | refused by the app (`userRegistration: false`) |
|
||||
| termix | yes | probe `setup_required` (M) | **was open → blocked** |
|
||||
| emby | yes | button | refused by the app |
|
||||
| navidrome | yes | button (no JSON status) | refused by the app |
|
||||
| ghost | yes | button (status is a list — R-715) | refused by the app |
|
||||
| home-assistant | yes | button (status is a list — R-715) | refused by the app |
|
||||
| gitea | yes | button | **was open → blocked** |
|
||||
| docmost | yes | button | no public sign-up |
|
||||
| calcom | yes | button | **was open (a stranger's account was created) → blocked** |
|
||||
| tandoor | yes | button | "Sign Up Closed" by the app |
|
||||
| gramps-web | yes | button (its status answers 405 — R-715) | answered 500 → blocked anyway |
|
||||
| adventurelog | yes | button | **was open → blocked** |
|
||||
| homebox | yes | button | **was open → blocked** |
|
||||
| papra | yes | button | **was open → blocked** |
|
||||
| sparkyfitness | yes | button | **was open → blocked** |
|
||||
| vikunja | yes | button | **was open → blocked**; window let a family member in |
|
||||
| opengist | yes | button | **was open → blocked** |
|
||||
| wishlist | yes | button | **was open → blocked** |
|
||||
| radarr, sonarr | yes | button | single user; their API key was public BEFORE the setup — the gate hides it |
|
||||
| recipe-importer | yes | button | our own app, open until a password is set — the confirm says so |
|
||||
| seerr | stranger refused, household reached setup | **opening not proven** (needs a media server) | not measured |
|
||||
| outline | stranger refused, household reached setup | **opening not proven** (needs e-mail / SSO) | not measured |
|
||||
| rallly | stranger refused, household reached setup | **opening not proven** (e-mail) | not measured |
|
||||
| wanderer | **not gated** | — | open (R-714) |
|
||||
| plant-it | not installable (`lifecycle: abandoned`); the template carries the gate | — | — |
|
||||
|
||||
## Claims in the brief that turned out wrong (or right), named
|
||||
|
||||
- **"A catalog change never gates an installed app"** — **right** (measured on 9202 and read on both demo boxes).
|
||||
- **"14 of the 34 have a probe"** — **wrong**: 9 have a probe that works (3 from the morning, 6 today); 3 more have a
|
||||
status the box cannot read (ghost, home-assistant — lists; gramps-web — 405, R-715); zipline's upstream route was
|
||||
wrong (`/api/setup` answers 403 after the setup; `/api/server/public` works).
|
||||
- **"The first admin is the first registered user on opengist and wishlist"** — **right** (measured: the household's
|
||||
sign-up became the admin; after it, a stranger could still sign up).
|
||||
- **"Each app in R-711's list can close sign-up"** — **wrong** as a per-app switch: opengist and wishlist keep it only
|
||||
in their admin settings, vikunja only as a start-up env; and wanderer cannot be closed at all today. The box-side
|
||||
block closes all but wanderer.
|
||||
- **"An env switch needs a restart"** — **right** for vikunja (read at start); not used — the block needs no restart.
|
||||
- **"Register 346 rows"** — was **350** at the start of this session (the morning session ended at 350).
|
||||
|
||||
## Also found
|
||||
|
||||
- A probe that never flips blocks the household's press (fail closed; measured on gramps-web) → R-715.
|
||||
- calcom created a stranger's account after the setup (measured) — closed.
|
||||
- Apps installed before today keep their open sign-up (demo boxes only) → R-716, needs an operator word.
|
||||
|
||||
## Rows
|
||||
|
||||
Closed: R-707, R-711, R-713. Opened: R-714 (wanderer), R-715 (probe shapes), R-716 (installed apps' sign-up, operator).
|
||||
**Register 350 → 353 rows.**
|
||||
|
||||
## Teardown
|
||||
|
||||
Machines: 9202 — every test app removed through the product (the scratch drive keeps some app folders, R-442's
|
||||
refusal as before); no gate or block file left; back on the live catalog; the drill catalog reset to live `main`.
|
||||
Demo boxes — read only (plus the floor). Host: nothing. Hub: floor 0.281.0. ep0: untouched.
|
||||
@@ -1,26 +1,25 @@
|
||||
# STATUS — what works, what's broken, what's next
|
||||
|
||||
**Updated 2026-09-29 morning. Both demo boxes run controller 0.280.0 and host agent 0.137.0. Hub 0.125.0. New installs get golden 0.276.0 with agent 0.137.0.**
|
||||
**Updated 2026-09-29 afternoon. Both demo boxes run controller 0.281.0 and host agent 0.137.0. Hub 0.125.0. New installs get golden 0.276.0 with agent 0.137.0.**
|
||||
|
||||
**Decisions today** (yours, recorded): I changed the HP box's two demo passwords. The "gate" was tested first, and built only because the test passed.
|
||||
**Decisions today.** Yours: after an app's first admin exists, strangers can no longer sign up. Mine (you may reverse it): most apps have no switch the box can flip to close sign-up. So the box blocks just the app's sign-up address after the setup. The household can open it for 15 minutes from the app page, so a family member can join.
|
||||
|
||||
**What I did, and it worked.**
|
||||
- **The HP box's bookstack and calibre-web have new admin passwords.** They are in your credentials file, under names starting `DEMO_HP_`. The old shared passwords no longer work. Their pages no longer warn.
|
||||
- **The gate test passed.** A new app whose first visitor would become its admin is now closed to strangers until you set it up. A stranger sees a page that says "This app is waiting for its first setup. Sign in to the Felhom dashboard." You, signed in to the dashboard, go straight in. It adds about 2 ms.
|
||||
- **The gate opens by itself** when the app says it has an admin (immich, n8n, audiobookshelf: within 30 seconds of the setup). For an app that cannot say it, you press "Kész, beállítottam" on the app page (uptime-kuma).
|
||||
- **After it opens, nothing of it is left.** immich's phone-app login worked unchanged.
|
||||
- **Every app that started with a known password now gets its own random one**: mealie, wger and calibre-web joined claper and bookstack.
|
||||
- **Small fixes:** wger's login did not work from any browser (fixed). romm and zipline no longer show a login that does not exist. grafana refuses to start without its password. Installed apps' passwords are no longer inside the settings page's code; the eye button fetches them.
|
||||
- **New button on the app page:** "Megváltoztattam" / "I changed it", under a known default login.
|
||||
- **The gate is on 32 of the 34 apps where the first visitor would become the admin.** On a new install a stranger gets only the gate page. You, signed in to the dashboard, go straight to the setup. I tested every one on the scratch box.
|
||||
- **9 apps open the gate by themselves** when their setup is done. The rest open with the "Done" button.
|
||||
- **The "Done" button now asks the app first.** If the app says "not done", the button refuses. I tested it on zipline before its setup.
|
||||
- **Strangers can no longer sign up after the setup.** 11 apps let anyone make an account; the box now blocks that. 11 more refuse strangers by themselves. The 15-minute window works and closes again by itself.
|
||||
- **claper's password step is safe** for passwords with unusual characters. I tested one with a quote mark in it.
|
||||
- **Apps already installed were not closed.** I checked this first, and again on both demo boxes after the update.
|
||||
|
||||
**What is not done.**
|
||||
- **30 apps still let the first visitor create the admin.** Each needs the gate switched on and tested. That is the next sessions' work, as you chose.
|
||||
- **About a dozen apps still allow open sign-up after the setup.** The gate does not change that. Written down.
|
||||
- **I did not test a restore of a gated app live.** It needs a whole-box backup, which test runs must not do. The code keeps the gate's state through a restore, and a test checks it.
|
||||
- **wanderer is not closed.** Its web part calls its own database through the public address, and a gate would block that call. So a stranger can still create its admin and sign up. It needs a small design change.
|
||||
- **seerr, outline and rallly** are closed to strangers. I could not test the opening, because it needs a media server or e-mail.
|
||||
- **3 apps have a "setup done" signal the box cannot read yet** (ghost, home-assistant, gramps-web). They use the button for now.
|
||||
|
||||
**Costs you should know** (no decision needed): while an app is still closed, its phone app cannot reach it, and it stops answering if the controller is down. "Kész, beállítottam" trusts you: pressed too early, the app opens before you set it up.
|
||||
|
||||
**Rows.** 4 opened, 4 closed. The list went from 346 to 350 rows.
|
||||
**Rows.** 3 opened, 3 closed, plus 1 for you. The list went from 350 to 353 rows.
|
||||
|
||||
**What needs you.**
|
||||
1. **D4, the image copies, the Peti leftovers:** unchanged. If you do nothing, nothing changes.
|
||||
1. **wanderer:** (A) keep it in the catalog until I fix it; its page now says plainly that anyone who finds the address can make an account (I recommend A; the fix is small). (B) Hide it from new installs until fixed. If you do nothing: A.
|
||||
2. **Apps installed before today on the demo boxes** (adventurelog, opengist) still allow open sign-up. (A) Add a one-time "close sign-up now" button for installed apps. (B) Leave them; only the demo boxes have such installs. If you do nothing: B.
|
||||
3. **D4, the image copies, the Peti leftovers:** unchanged. If you do nothing, nothing changes.
|
||||
|
||||
@@ -136,7 +136,9 @@ the dashboard's own `/__gate/start`). The controller is in an app's request path
|
||||
open, the gate's traefik router is removed and the app is reached exactly as without it. The gate decides who creates
|
||||
the first admin. **Who may sign up afterwards** is `09` §3 decision 47 (operator ruling 2026-09-29): once the first admin
|
||||
exists, open sign-up is closed; only the admin adds people, from the app's own user page. An app that cannot close it
|
||||
says so on its page.
|
||||
says so on its page. Mechanism (controller ≥ 0.281.0): the box keeps a small traefik router on the app's own sign-up
|
||||
address once the gate opens, answered "sign-up is closed" by the controller; the household opens it for 15 minutes
|
||||
from the app page to let a family member in. The controller is in THAT address's path only.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -515,7 +515,18 @@ R-636's louder repeated alarm.
|
||||
the household's first admin exists, a stranger can no longer make an account; only the admin adds people, from the
|
||||
app's own user page, and the app page says how. Where an app cannot close sign-up, it stays in the catalog and its
|
||||
page says plainly that anyone who finds the address can make an account; STATUS asks the operator about it.
|
||||
Mechanism and per-app proof: *(filled in by the 2026-09-29 afternoon session)*.
|
||||
**Mechanism — decided by CC unattended 2026-09-29, operator may reverse.** *How does the box close sign-up in apps
|
||||
that keep the switch only in their own admin settings?* Options: (a) per-app switches — an env read at start plus a
|
||||
restart, or the app's admin API; (b) a box-side block of the app's own sign-up address once the gate opens, with a
|
||||
household window to let a family member in. Costs: (a) measured impossible for most — opengist and wishlist keep
|
||||
it only in their admin settings (no env, no CLI), vikunja has an env but then no way to add a user except its CLI;
|
||||
(b) one more traefik router per app, and a family member joins through a 15-minute window instead of an in-app
|
||||
invite. **Chose (b)**: it works the same for every app, needs no credentials and no restart, and leaves installed
|
||||
apps untouched (only an app whose gate this box opened gets a block). Built in controller v0.281.0
|
||||
(`internal/stacks/signup_block.go`): the block goes up before the gate comes down; a failed write keeps the gate
|
||||
closed. **Proven on 9202:** 11 apps let a stranger sign up after the setup and refused it with the block; 11 more
|
||||
refuse by themselves; the window let a family member in and closed again. wanderer cannot be gated yet (R-714).
|
||||
Evidence `audits/gate-rollout-2026-09-29/`.
|
||||
Same day, operator: CC changes the admin passwords of demo-hp's installed bookstack and calibre-web and stores them
|
||||
in the operator's credentials file (not in any repo).
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -818,13 +818,16 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
|
||||
| **R-704** | **[P3-LOW] The box's crash-loop stop (decision 28) outlives the app: after remove and reinstall, the new install is still held.** Measured 2026-09-28 on 9202: calcom crash-looped at 08:22 and 08:28 (`unhealthy_stop`, `crash_loop`, trip 2, recorded 08:28:59Z); it was then REMOVED through the product twice and installed fresh twice (09:14:51Z the last). At 09:45 the new, healthy install's Update was refused `409 held` with the crash-loop sentence („…újra és újra összeomlott…"), and `GET /api/stacks/calcom` carried the old `hold_reason` while `state=running`. Start lifted it (`the unhealthy stop is LIFTED by Start`). So a household that removes a crash-looping app and installs it again (the obvious fix) finds its updates refused for a crash of a previous install. Not measured: whether the nightly update leg also skips it; whether other holds (restore hold) behave the same. **Fix direction:** the remove clears the app's box-set holds, as `DeleteAppBackupPrefs` clears its backup preferences (R-474). Evidence: `audits/pg-calcom-claper-2026-09-28/box/calcom/hold.txt`, `…/box/calcom/move.txt`. **-- 2026-09-28 later: SECOND and worse instance, then FIXED in controller v0.278.0.** demo-hp's fresh nextcloud (installed 10:13) carried an UPDATE hold from a nextcloud of 2026-09-13 (set before v0.242.0 made removals clear update holds; nothing ever swept it). At the manual off-site run (15:17) the backup leg logged `Skipping volume dump for nextcloud — the app is HELD stopped`, captured no unit, and pushed a snapshot that `carried NO database dump and NO volume tar` — a freshly installed app silently NOT backed up. **Fix:** a removal also clears the crash-loop stop (`settings.ClearUpdateHold`), and a new install (plain or "use my kept data") drops a leftover update/crash-loop hold of an app that is not installed (`Router.dropLeftoverHold`); restore holds (R-379) untouched. Red-proofed RP4–RP6 (`audits/kept-offsite-2026-09-28/redproofs/`). Floor 0.278.0. **STILL OPEN: live proof of the install-time drop** (a box with a leftover hold on an uninstalled app). | **WATCHING — P2; owner: CC (install-time drop, live)** |
|
||||
| **R-705** | **[P3-LOW] There is no way to run the night's chain now — only its pieces.** Asked by the operator 2026-09-28 (to finish a proof in the day). What exists (read from source, controller v0.278.0): the backup page's off-site run-now (`POST /backup/offbox/run`) runs the dump leg first (the R-44 pre-phase: DB dumps, volume dumps with brief app stops, unit capture) and then the push — used live on demo-hp 2026-09-28 15:17, 3m57s; the debug API has `backup/dbdump`, `backup/crossdrive` (Tier 2), `backup/integrity`, `backup/offsite-proof`. **Missing:** the automatic update leg (`RunUpdateLeg`, chained only to the scheduled off-site job) and the whole-guest backup (the agent's, on its own 24 h / 7 d cadence) have no manual trigger; the only way to run the chain in order is to move the backup window (`POST /backups/window`), which takes W..W+2h at least. **Needs:** a debug action "run tonight's chain now" (dump → Tier 2 → off-site → update leg, in order, one at a time), and an agent-side "whole-guest backup now" for demo boxes. Not built. **-- 2026-09-28 evening: the controller half BUILT (v0.279.0):** debug `POST /api/debug/backup/night-chain` runs dump → Tier 2 → off-site → update leg in order, one at a time, refused while anything else runs; the leg gets its normal length from its own start. Proven on 9202: 44 s, a second press 409, the leg deadline 22:00. **Still open: the whole-guest backup (agent side) has no manual trigger.** | **OPEN — P3, agent half only; owner: CC** |
|
||||
| **R-706** | **[P3-LOW] Removing an app "with its backups" leaves its off-site verification copy on the drive.** Measured 2026-09-28 on demo-hp: after a full off-site restore of nextcloud (which leaves the downloaded copy in `backups/offsite-restore/nextcloud`, ~1 GB, by design, for the household to inspect), `POST /api/stacks/nextcloud/remove` with `remove_backups: true` removed the unit and listed `backup_paths_removed` WITHOUT the verification copy; it stayed until the restore page's own delete (`POST /backup/offbox/verify-copy/delete`, 302 `scratch_deleted`). A household that removes an app to free space keeps 1 GB it cannot see on the app list. **Fix direction:** the removal with backups also deletes the app's verification copy (the same `DeleteOffsiteRestoreCopy`). Evidence: `audits/kept-offsite-2026-09-28/E/E9-teardown.txt`. **-- 2026-09-28 evening: FIXED in controller v0.279.0** — a removal with its backups also deletes the verification copy and lists it among the removed paths (`TestR706_…`, red-proofed RP7). Not yet seen live (needs a full off-site restore then a removal). | **WATCHING — P3; owner: CC (live)** |
|
||||
| **R-707** | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). | **OPEN — P2; owner: CC; 30 of 37 left** |
|
||||
| **R-707** | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). **2026-09-29 (controller v0.280.0, catalog `d0e7e2e`):** every class-3 app fixed — mealie, wger, calibre-web by `after_install` (calibre-web with the new `password:24:special`), proven on 9202 fresh installs (`audits/login-gate-2026-09-29/D/`); the setup gate (decision 46, spike PASSED) built and live on immich, n8n, audiobookshelf (probes measured) and uptime-kuma (button) (`…/C/`); romm's and zipline's stale notes removed. **Left: 30 class-4 apps** — gate each (probe measured on 9202 where one exists — 11 upstream candidates listed in `…/B/B-VERDICT.md` §3; the button otherwise). **2026-09-29 afternoon (controller v0.281.0, catalog `6faf432`):** 28 more class-4 apps gated — 32 of 34 — each proven on 9202 (`audits/gate-rollout-2026-09-29/`B): stranger → gate page / 401, household reached the first-setup screen, the gate opened (9 by a measured probe, the rest by the press), the app answered after. seerr, outline, rallly: gated, their opening needs a media server / e-mail (not proven). **Left:** wanderer (R-714); plant-it is not installable. | **CLOSED — 2026-09-29 (the rest → R-714)** |
|
||||
| **R-708** | **[P3-LOW] grafana falls back to password `admin` when its admin field is empty.** `templates/grafana/docker-compose.yml:18` `GF_SECURITY_ADMIN_PASSWORD=${…:-admin}` (read 2026-09-28, the audit). Today the field is generated and required, so it is never empty on a normal install — but an edit, an import or a restore that drops the value would publish grafana with `admin / admin`. **Fix direction:** no default in the compose (`${GF_SECURITY_ADMIN_PASSWORD:?}` refuses to start instead). **Fixed 2026-09-29** (catalog `d0e7e2e`): `${GF_SECURITY_ADMIN_PASSWORD:?…}` — `docker compose config` with it empty or unset exits 1 naming R-708, set → 0 (`audits/login-gate-2026-09-29/D/D4-grafana-r708.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-709** | **[P3-LOW] The deploy page writes the generated admin passwords of installed apps into its HTML.** `internal/web/templates/deploy.html` renders a `type: password` field's decrypted value into a disabled `<input value=…>` (read 2026-09-28; used by the proofs of R-702/R-707 to read the first password as the household sees it). `type: secret` fields got a fetch-on-demand reveal in R-254; `type: password` fields did not. The page needs a login, so this is exposure to a logged-in session's HTML (browser cache, a shared screen, a saved page), not to strangers. **Fix direction:** the R-254 reveal for password fields too. **Fixed in controller v0.280.0:** an installed app's password field renders empty with a reveal eye (`/stacks/<n>/auto-field/reveal` now serves `type: password` of an installed app, never a restore-generated one). Red-proofs RP14/RP15; live on 9202: mealie, wger, calibre-web — the revealed value is NOT in the settings page HTML (`…/D/D6-r709-live.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-710** | **[P2-MEDIUM] An app installed before its template gained an `after_install:` is never warned about its default login.** MEASURED 2026-09-29 on demo-hp: bookstack's page carried no known-login sentence although its default `admin@admin.com / password` still logged in (the app was installed before the catalog added bookstack's `after_install` on 2026-09-28; the command never runs for an installed app). `internal/web/known_login.go` reads an ABSENT `after_install` record as "not run yet" for ever. Evidence `audits/login-gate-2026-09-29/A/A2-page-warning-after.txt`. Also: the page has no way to learn of a password the household changed by hand (the brief's Part A4). **Fix direction:** absent record + installed longer than the command's window = in effect; a household "I changed it" press recorded in `app.yaml`. **Fixed in controller v0.280.0** (RP13 red-proof): an absent record is "not run yet" only for 30 minutes after the install; the card has „Megváltoztattam" / "I changed it" (`app.yaml` `default_login`). **Live on demo-hp 2026-09-29:** after the delivery bookstack's page warned again (the positive control), then the press on both apps removed the sentence (`audits/login-gate-2026-09-29/A/A3-demo-hp-changed-it.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-711** | **[P2-MEDIUM] About a dozen class-4 apps keep open sign-up after their first admin exists — the setup gate (decision 46) does not close that.** FOUND 2026-09-29 by the gate spike (`audits/login-gate-2026-09-29/B/B-VERDICT.md` F3). The gate decides who becomes the admin; once it opens, a stranger can still make an ordinary account on adventurelog, homebox, papra, plant-it, sparkyfitness, vikunja, wanderer, rallly, opengist, wishlist, termix, docmost (READ from `app-catalog-felhom.eu/FIRST-ADMIN.md`, not measured). **Fix direction:** per app, route (a) — disable sign-up after the first user (env or the app's own setting), measured on 9202. | **OPEN — P2; owner: CC** |
|
||||
| **R-711** | **[P2-MEDIUM] About a dozen class-4 apps keep open sign-up after their first admin exists — the setup gate (decision 46) does not close that.** FOUND 2026-09-29 by the gate spike (`audits/login-gate-2026-09-29/B/B-VERDICT.md` F3). The gate decides who becomes the admin; once it opens, a stranger can still make an ordinary account on adventurelog, homebox, papra, plant-it, sparkyfitness, vikunja, wanderer, rallly, opengist, wishlist, termix, docmost (READ from `app-catalog-felhom.eu/FIRST-ADMIN.md`, not measured). **Fix direction:** per app, route (a) — disable sign-up after the first user (env or the app's own setting), measured on 9202. **Built and proven (decision 47, controller v0.281.0, catalog `6faf432`):** a `signup_block:` per app, written when the gate opens (before the gate comes down), answered "sign-up is closed"; the household's 15-minute window. Measured on 9202 (`audits/gate-rollout-2026-09-29/`B, C): 11 apps let a stranger sign up after the setup (gitea, calcom, adventurelog, homebox, papra, sparkyfitness, vikunja, opengist, wishlist, termix; gramps-web 500) — all refused with the block, the apps still answered, the window let a family member in and closed again; 11 more refuse a stranger by themselves. wanderer → R-714. | **CLOSED — 2026-09-29** |
|
||||
| **R-712** | **[P2-MEDIUM] wger refused every browser sign-in behind traefik: "CSRF verification failed".** MEASURED 2026-09-29 on 9202 (live catalog wger 2.6): a POST to `/en/user/login` with the browser's `Origin: https://…` answered 403 — Django saw the request as http (no trusted proxy header) and no `CSRF_TRUSTED_ORIGINS`. Found while proving R-707's wger route. **Fixed** (catalog `d0e7e2e`): `CSRF_TRUSTED_ORIGINS=https://${SUBDOMAIN}.${DOMAIN}` + `X_FORWARDED_PROTO_HEADER_SET=True`; proven on a fresh install: the generated password signs in (302) with the https Origin (`audits/login-gate-2026-09-29/D/D2-live.txt`). | **CLOSED — 2026-09-29** |
|
||||
| **R-713** | **[P3-LOW] claper's `after_install` pastes the household's password into Elixir code, and the controller does not refuse a value that would break such code.** FOUND 2026-09-29 by a background security review of the drill commit (mealie/wger had the same shape and were changed to pass the password as `sys.argv[1]`). claper's `bin/claper rpc '… "${ADMIN_PASSWORD}" …'` has no argv: a household-typed password with `"` or `#{` breaks the command (recorded as failed; the page then warns) or changes the Elixir it runs — inside the household's own claper container, as that app. The generated value (letters + digits) is safe. **Fix direction:** (1) controller: `expandAfterInstall` refuses a value holding a quote, a backslash, `$`, `{`, `}`, a backtick or a newline — or a declared per-field encoding; (2) claper: read the value some other way (a file the command reads, or `System.get_env` from a one-shot env). | **OPEN — P3; owner: CC** |
|
||||
| **R-713** | **[P3-LOW] claper's `after_install` pastes the household's password into Elixir code, and the controller does not refuse a value that would break such code.** FOUND 2026-09-29 by a background security review of the drill commit (mealie/wger had the same shape and were changed to pass the password as `sys.argv[1]`). claper's `bin/claper rpc '… "${ADMIN_PASSWORD}" …'` has no argv: a household-typed password with `"` or `#{` breaks the command (recorded as failed; the page then warns) or changes the Elixir it runs — inside the household's own claper container, as that app. The generated value (letters + digits) is safe. **Fix direction:** (1) controller: `expandAfterInstall` refuses a value holding a quote, a backslash, `$`, `{`, `}`, a backtick or a newline — or a declared per-field encoding; (2) claper: read the value some other way (a file the command reads, or `System.get_env` from a one-shot env). **Fixed in controller v0.281.0** (RP24): a code-bound value holding a quote, backslash, `$`, `{`, `}`, backtick or line break is refused; `${NAME|base64}` is new. claper (catalog `6faf432`) decodes `Base.decode64!("${ADMIN_PASSWORD|base64}")`; proven live with a typed password holding `"` and `#{`: default refused, typed signs in (`audits/gate-rollout-2026-09-29/`D). | **CLOSED — 2026-09-29** |
|
||||
| **R-714** | **[P2-MEDIUM] wanderer cannot be gated: its web part calls its own database host through the public name.** MEASURED 2026-09-29 on 9202: `PUBLIC_POCKETBASE_URL=https://${SUBDOMAIN_DB}.${DOMAIN}` is fetched by the web server itself; a gate on that host would refuse the web part (no gate cookie) and the household could not finish the setup. (On 9202 the name points to another box, so the app answered 500 either way.) Meanwhile wanderer keeps open sign-up (`PUBLIC_DISABLE_SIGNUP=false`) and PocketBase's own first-run screen on the second host. **Fix direction:** point the web part at PocketBase on the docker network (if wanderer separates the internal and public URL), then gate both hosts; or gate only the web host and close PocketBase's `/_/` installer with a block. | **OPEN — P2; owner: CC** |
|
||||
| **R-715** | **[P3-LOW] The setup gate's probe reads only an HTTP-200 JSON object, so three apps with a real status get the button.** MEASURED 2026-09-29 on 9202: ghost (`{"setup":[{"status":…}]}` — a list), home-assistant (`/api/onboarding` — a top-level list), gramps-web (405 after the setup). And a probe that never flips BLOCKS the household's press (fail closed — measured on gramps-web while its check was still in the catalog): a wrong probe in a template would keep an app closed to everyone but the household until the catalog is fixed. **Fix direction:** list indexes in `field`, an optional `status:` to match, and a catalog gate that refuses a probe without a before/after measurement in its comment. | **OPEN — P3; owner: CC** |
|
||||
| **R-716** | **[P3-LOW] Apps installed before controller 0.281.0 keep their open sign-up — decision 47 closes it only on apps whose gate the box opened.** READ 2026-09-29 on the demo boxes after catalog `6faf432` synced: demo-hp's adventurelog and opengist, demo-felhom's opengist carry `signup_block:` in their synced template and no gate record, so no block (`audits/gate-rollout-2026-09-29/0/P0-3-demo-boxes-after-push.txt`). This is Part 0's rule working as designed (a catalog change never touches an installed app). **Needs an operator word** before anything changes on an installed app: a one-time "close sign-up now" press on the app page for an installed app, or leave them. Only the demo boxes have such installs today. | **WAITING-ON-OPERATOR — P3; owner: operator** |
|
||||
|
||||
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
|
||||
One row per dated check. The R-number must have a row above. Dates are UTC.
|
||||
|
||||
Reference in New Issue
Block a user