Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 631e759864 | |||
| 5c36d4c110 | |||
| 96e15c15ba | |||
| babf596c89 | |||
| e713f6aeff | |||
| b94411b4b8 | |||
| 6999c15f0f | |||
| 7e1c6df3a0 | |||
| 9d6f3b4637 | |||
| 836d64efa3 | |||
| 11fa13ee38 | |||
| ea8e67f341 | |||
| bc9a871391 | |||
| 59834feadf | |||
| f1e1c8a60c | |||
| b17e9e993c | |||
| 920c1aaf84 | |||
| 8b0a4413cd | |||
| ffacccee89 | |||
| f51dfe2046 |
@@ -10,3 +10,27 @@ The full report is `app-catalog-felhom.eu/REPORT.md`. This file is the felhom.eu
|
||||
- **Rows opened:** R-1085, R-1086, R-1089, R-1090, R-1091, R-1092. R-1088 (another session's row) extended. None closed.
|
||||
- **Code:** `scripts/read_credential.py` + its test — the same-file guard (R-1088), red-proved.
|
||||
- **STATUS:** one „works" line, D11 and D12, and the „read the Plex and Sonarr guide" line.
|
||||
|
||||
## Morning of 2026-10-16 — the owed rendering check
|
||||
|
||||
- The operator restored `~/.config/credentials` from backup. CC checked the key names only (17 keys); no value read.
|
||||
- **Rendering check ran, 07:20 UTC, through `https://felhom.enkisfelhom.hu` (demo-hp 9201, controller 0.317.0):**
|
||||
login 302 + session; `/apps/plex/guide` and `/apps/sonarr/guide`, Hungarian and `?lang=en`. Each page: 4
|
||||
`data-guide-section`, 1 FAQ block with 6 entries, `data-guide-checked` equal to the catalog. Every rendered string
|
||||
(33 Plex, 35 Sonarr, per language) equals the catalog text on `main` (`ec864b5`); a changed line in memory was seen
|
||||
to fail the compare. Evidence: `documentation/audits/app-guides-2026-10-15/render-check-2026-10-16.txt`.
|
||||
- Read only: nothing installed, removed, restarted or set; logged out after. Teardown: provisioned nothing.
|
||||
- **R-1088 updated, not closed:** restore and check done; the hub token rotation is still the operator's.
|
||||
- STATUS: the „read the guides" line now says the robot check passed; the R-1088 line now asks for the rotation only.
|
||||
|
||||
## 2026-10-16 — the operator's answers applied
|
||||
|
||||
- **Token rotated (operator).** CC checked the token in the credentials file works on the hub: `/hosts` and
|
||||
`/configuration` 200 with it, 302 without it. **R-1088 CLOSED.**
|
||||
- **D11 → stop advising the change.** Catalog `5cc9e0e`: qBittorrent's first step now says keep the generated password;
|
||||
qBittorrent and Transmission field descriptions no longer say „change it later". **R-1086 CLOSED.**
|
||||
- **D12 → reword.** Catalog `5cc9e0e`: Cal.com names Apple Calendar or CalDAV, not Google/Outlook; BentoPDF says the
|
||||
browser does the work and the file stays on the device. **R-1091 stays open** for its other items; (1) and (2) are
|
||||
marked done.
|
||||
- Register: 178 → 176 rows; 0 opened, 2 closed (moved to `CLOSED-ITEMS.md`). STATUS: D11, D12 and the token line removed.
|
||||
- Gates: catalog `--fast` and 12 test files green; felhom.eu `repo_gates.py --fast` green before the push.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# REPORT — app page tuning, round 2 (2026-10-16)
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. The full technical report is felhom-controller `REPORT.md`
|
||||
(v0.319.0). Evidence: `documentation/audits/app-page-tuning-2026-10-16/` (P0, before/, browser/, release/, tools/,
|
||||
redproof.txt). Design read and updated: `architecture/02-controller-module-map.md` „The app page's layout".
|
||||
|
||||
## 1. Why the Calibre-Web password was empty (Part 0)
|
||||
|
||||
Calibre-Web on demo-hp was installed on 2026-08-21. Its template got the password field on 2026-09-29. The box adds new
|
||||
generated values to installed apps, but never a password. So the box never had one. The user name the box shows is the
|
||||
real one: a session renamed Calibre-Web's admin to it on 2026-10-01. Nothing was removed. No customer box has this state
|
||||
(the hub lists only demo-hp and demo-felhom; demo-felhom has no Calibre-Web now — checked on the box).
|
||||
|
||||
## 2. What changed on the page
|
||||
|
||||
The header, the tab bar and every section are panels (style A). The open tab is filled and underlined. One
|
||||
„Belépés az alkalmazásba" panel shows the name and the password side by side. A value the box does not have is a
|
||||
sentence, not an eye. „Cím és kulcsok" is rows: no Domain input, no storage list. The install page uses the same panels;
|
||||
its fields did not change. Checked in a real (headless) Chrome through `https://felhom.enkisfelhom.hu`.
|
||||
|
||||
Login pairs: calibre-web, kimai, lubelogger, nextcloud, paperless-ngx, radicale. 14 other apps show the password alone.
|
||||
|
||||
## 3. Release
|
||||
|
||||
Controller **0.319.0**: Tester 1, demo-hp and demo-felhom took it through their own floor at 10:34 UTC; box walk checks
|
||||
green on all three; global floor 0.319.0 / MinAgent **0.158.1** (first saved with 0.157.0 by my mistake, corrected 65
|
||||
seconds later; no box was affected — see the controller report §6). Per-customer floors cleared.
|
||||
|
||||
## 4. Register
|
||||
|
||||
Rows before 174, after 175; opened 1 (R-1095: `/apps/{slug}/install` for an installed app), closed 0.
|
||||
|
||||
## 5. Other session duties
|
||||
|
||||
- Instruction files: no edit.
|
||||
- System poster: no fact in `felhom-system-poster.facts.md` changed.
|
||||
- `unproven.py --summary`: NOT WALKED 35 of 55 — no number moved.
|
||||
- Commits: no Co-Authored-By line (the brief's rule).
|
||||
@@ -0,0 +1,82 @@
|
||||
# REPORT — apps burndown: RomM saves, the false „shared password" line, FileBrowser, the Data tab, the install address, the build guards, the Pi key (2026-10-16)
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. Operator brief „apps burndown", daytime, operator present.
|
||||
Design read first: `architecture/02-controller-module-map.md` „The app page" (now with the v0.321.0 notes),
|
||||
`07-backup-architecture.md` (what an app backup holds — a fact added), `09-update-architecture.md` §0.
|
||||
Evidence: `documentation/audits/burndown-apps-2026-10-16/`.
|
||||
|
||||
**The operator's word before the start:** no RomM saves need to be kept or copied on any box. So Part 1 has no one-time
|
||||
copy, and no stop for the N100.
|
||||
|
||||
## 1. RomM saves (R-1097) — done
|
||||
|
||||
- **Measured first (demo-hp 9201):** RomM writes saves to `/romm/assets`; the image keeps `/romm` as an anonymous
|
||||
volume; the compose mounted nothing there. Every RomM backup on the drive held only `romm_config`, `romm_db_data`,
|
||||
`romm_redis_data` — no saves. In the code, the app backup takes only the named volumes the compose declares.
|
||||
`P1/p1-measure.txt`.
|
||||
- **Fix (catalog `326f846`):** a named volume `romm_assets:/romm/assets`, in the compose and both older step files. A
|
||||
named volume is the catalog's normal place for app data (07 §6.2), every app backup takes it, and the restore brings it
|
||||
back. New catalog gate `image-volumes` (a measured list of image data paths that must be mounted).
|
||||
- **Survey:** no other running app on demo-hp keeps data in an anonymous volume (PrivateBin's are temp folders).
|
||||
`P1/anon-volumes-demo-hp.txt`.
|
||||
- **Proof, through the product's endpoints** (`tools/romm_saves.py`, `P1/romm-saves.txt`): sync → restart (the new
|
||||
volume mounted) → a 32 KiB save file written into `/romm/assets` with `docker exec` → the by-day backup run (night
|
||||
chain, 5 min 32 s; its update leg did nothing) wrote `romm_romm_assets.tar` → RomM removed, keeping backups (the volume
|
||||
went) → restored from the backups page (77 s) → **the save back, byte-identical (sha256)**. The dummy file removed after.
|
||||
- **Boxes:** the hub shows RomM only on demo-hp (the N100 had it once, on 2026-10-13). `P1/hub-romm-boxes.txt`.
|
||||
|
||||
## 2. Parts 2–7
|
||||
|
||||
| Part | Result |
|
||||
|---|---|
|
||||
| 2 — the false „shared password" line (R-1102, D10 = A) | **Done.** `default_creds` removed (hu + en) from the eight; copy freeze re-captured with the reason; a catalog test, red-proved. Live on demo-hp: the line is on none of their app pages or install pages (`live/d10-check.txt`). The user name „admin" stays in the first steps of grafana, gokapi, nzbget, transmission. |
|
||||
| 3.1 — FileBrowser's stale password (R-1101) | **Done** (controller 0.321.0). The „change it" line is gone for FileBrowser; the page checks the stored password with FileBrowser first and, refused, says so and offers the new-password press. Only FileBrowser's own 401/403 counts. |
|
||||
| 3.2 — FileBrowser's own banners | **Done.** `server.disableUpdateCheck` (`backend/common/settings/structs.go:51`, read at `backend/cmd/root.go:66`) and `frontend.disablePWAInstall` (`structs.go:294`, sent at `backend/http/static.go:300`) at the pinned tag, set in the rendered config. In the browser, logged in: no „update is available", no „Install app", `disablePWAInstall: true`, `updateAvailable: ""`. Control: the 2026-10-16 screenshot `logins-folders-2026-10-16/browser/cold-romm-2-after-login.png` shows the banner before. |
|
||||
| 4 — the Data tab's backup part | **Done.** One section, rows, plain words; the value column in the UI font; the export eye is the text „Megjelenítés"/„Elrejtés" with `aria-label`. Changed keys: `tier2_config.a_2_mentes_egy_masik`, `.csak_db_konfiguracio`, `.jelenleg_csak_a_belso_ssd`, `.nincs_elerheto_off_drive_cel`, `.nincs_masik_adatmeghajto_automatikus_cel`, `.rendszerlemez.vedett`, `note.tier2.no_space_ssd`, `note.tier2.reason_no_second`. No eye icon exists in the dashboard's icon set, and adding one would change every page's fixture, so the button is the text the other reveal buttons use. |
|
||||
| 5 — the install address (R-1095) | **Done** (302 to the settings tab, GET only). **The template clean-up is NOT done:** the installed-only branches of `app_fields.html` are pinned by the R-254/R-709 guard tests, which assert a reveal control on that path; removing them means retiring those tests — filed R-1103. |
|
||||
| 6 — the build guards (R-1098) | **Done.** `build-hub.sh` 1.25.0 refuses an existing tag and an unanswered registry (6 cases, red-proved). The agent's `release-agent.sh` had the check but let an unreachable registry through; now only a clean 404 proceeds (5 cases, red-proved). Script changes only: **no hub image, no hub deploy, no stop needed.** |
|
||||
| 7 — the Raspberry Pi key (R-1067) | **Not done, as the brief says:** Tester-2 still reports controller **0.292.0** (newest report 2026-10-04). Its name is in the row. `P7-pi-fleet.txt`. |
|
||||
|
||||
## 3. Browser check (headless Chrome through `https://felhom.enkisfelhom.hu`) — `browser/`
|
||||
|
||||
`tools/cdp_burndown.py`. Calibre-Web's Data tab: one backup section, rows nightly/second/target, no off-drive/PBS, value
|
||||
font Plus Jakarta Sans, the export toggle with its label. FileBrowser's page: the reveal, no „change it" line.
|
||||
`/apps/calibre-web/install` landed on `/apps/calibre-web/settings`. FileBrowser itself: no banners. No console error; no
|
||||
page wider than the window.
|
||||
|
||||
## 4. Versions and commits
|
||||
|
||||
- **felhom-controller** `c02c9d8` → `7e791d9` → docs. `0.321.0-rc.1` (demo-hp), release **0.321.0**, MinAgent 0.158.1.
|
||||
Tester 1, demo-hp and the N100 took it through per-box floors; nothing unhealthy, restarting, exited or created; the
|
||||
three dashboards 200; https-only 35 names OK; each box's FileBrowser config carries both switches. **Global floor
|
||||
0.321.0 / MinAgent 0.158.1**, the per-box floors cleared (`release/`).
|
||||
- **app-catalog-felhom.eu** `5630979` → `326f846` (RomM volume, gate `image-volumes`, D10) → docs.
|
||||
- **felhom-agent** `3835224` → `dd8b2ac` (script and test only; no release).
|
||||
- **felhom.eu** `11fa13ee` → this commit (`build-hub.sh` 1.25.0, its test, the register, design notes, STATUS, evidence).
|
||||
- **Fixtures replaced (13):** `app_data_deployed`, `app_data_export`, `app_data_files_media`, `app_data_not_hdd`,
|
||||
`_not_hdd_absent`, `_not_hdd_protected`, `_not_hdd_same_disk`, `_notarget_disabled`, `_target_auto`, `_target_ssd`,
|
||||
`app_settings_deployed`, `app_settings_operator_creds`, `app_settings_infra_filebrowser`. **New (1):**
|
||||
`app_settings_infra_filebrowser_stale`. Every other fixture, the install page's included, byte-identical.
|
||||
- **Red-proofs:** `redproof/` (controller 6 + 1 redo, catalog 2 gates + 1 test, hub build, agent).
|
||||
|
||||
## 5. Rows
|
||||
|
||||
**Before 179, after 175. Closed 5:** R-1097, R-1102, R-1101, R-1095, R-1098. **Opened 1:** R-1103. R-1067 stays
|
||||
BLOCKED, now naming Tester-2.
|
||||
|
||||
**System poster:** no listed fact changed — none of the three was needed.
|
||||
|
||||
## 6. Observations
|
||||
|
||||
1. The install form keeps branches no page serves any more. FILED: R-1103
|
||||
2. Reading the hub's database by copying its three files one by one gave a STALE floor (0.320.0, 30 s after 0.321.0 was set); one tar of the three files and the hub's own page both read 0.321.0. NOT-A-FINDING: an evidence technique, not a product defect; the memory recipe is corrected to one tar.
|
||||
3. The by-day backup run (night chain) also runs the update leg and the off-site push. NOT-A-FINDING: it is the sanctioned trigger, documented as „the night chain, by day"; here the update leg did nothing (kimai held).
|
||||
4. No live control exists for the „known, shared password" line any more: the eight apps were the only ones that showed it. NOT-A-FINDING: `known_login_test.go` is the mechanism control and passes.
|
||||
|
||||
## 7. Teardown (three layers)
|
||||
|
||||
- **Machine:** demo-hp 9201 — RomM removed and restored (it runs, healthy, with its new saves volume); the dummy save
|
||||
file removed. FileBrowser recreated once on each box by the config change (the password survives: its database is in
|
||||
its volume). Nothing else changed. The N100: only the normal release update.
|
||||
- **Host:** nothing.
|
||||
- **Hub:** the three per-box floors set and cleared; the global floor raised to 0.321.0 (the release).
|
||||
@@ -0,0 +1,115 @@
|
||||
# REPORT — burndown 2: Data tab leftovers, plain http closed, the hub-key door removed, Go 1.27.2, the dead install-form branches (2026-10-16)
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. Operator brief „burndown 2", operator present.
|
||||
Design read first: `architecture/01-topology-and-trust.md` §4 and §10 (now records R-970), `04-control-plane-authorization.md`,
|
||||
`09-update-architecture.md` §0 and the release rule. Evidence: `documentation/audits/burndown2-2026-10-16/`.
|
||||
|
||||
## 0. A decision I took (inside the brief; reversible)
|
||||
|
||||
**The controller runtime stays on Debian 12 (12.13, all fixes) — not Debian 13.** The brief said „the current Debian
|
||||
stable". Debian 13 changes restic, the off-site backup engine, from 0.14.0 to 0.18.0. The off-site code relies on
|
||||
measured 0.14.0 behaviour (a missing source path is only a warning, the progress output, the clean-up windows,
|
||||
restore without a lock). Changing it silently changes how customer backups behave. The Go toolchain and every fixable
|
||||
OS package are updated; the Debian 13 move is its own row with the re-proof (R-1105).
|
||||
|
||||
## 1. Each part
|
||||
|
||||
| Part | Result |
|
||||
|---|---|
|
||||
| 1 — Data tab leftovers | **Done** (controller 0.322.0). The export password field: `autocomplete="new-password"`, name `fab_encryption_key`. The 2nd copy's status rows are gone; the form is the one place (its no-target reason moved into it); the warning stays. Browser (headless Chrome through the public address): the field renders with the attribute and stays empty; no status rows; one target label. **Headless Chrome has no saved passwords, so the autofill itself is not proven — the attribute is.** `P1/` |
|
||||
| 2 — plain http (R-1033, P1) | **Closed.** The hub's newest reports: demo-hp, Tester 1, demo-felhom on ≥ 0.321.0. `https_only_check.py`: 35 public names → 301 on all three. **Tester 2** (`sajatfelhom.hu`, 0.292.0, off since 2026-10-04) is not checked: both http and https answer Cloudflare 530 (tunnel down). R-1104 checks it when it returns. `P2/` |
|
||||
| 3 — the box settings route (R-970, P2) | **Closed.** See §3. |
|
||||
| 4 — Go and base images (R-972, P2) | **Controller released, hub deployed on your yes, agent committed (no release).** See §4. |
|
||||
| 5 — dead install-form branches (R-1103, P4) | **Closed.** The installed-only branches of `app_fields.html` removed. Retired tests and where their rule is now: `TestDeployPage_DeployedApp_DoesNotCarryTheSecret` → `TestAppPage_LoginPairPanel` + `TestR255_NoPlantedSecretReachesAnyPage`; `TestR709_AnInstalledAppsPasswordIsNotInThePage` → the same two (its install-form half stays as `TestR709_TheInstallFormKeepsItsGenerator`); `TestR694_TheDeployPageDoesNotOfferAGeneratedLogin` → `TestR694_TheSettingsTabDoesNotOfferAGeneratedLogin` (new, red-proved); the parity cases `deploy_installed` and `deploy_installed_restored`. |
|
||||
|
||||
## 2. https check per box (`P2/https-only.txt`)
|
||||
|
||||
tester-1 13 names, demo-hp 14, demo-felhom 8 — each `http://` → 301 to the same `https://` address; the negative
|
||||
control convicted. Tester 2: not checked (530, off).
|
||||
|
||||
## 3. The removed route (R-970)
|
||||
|
||||
- **What:** the routes that read and replaced the box's settings file, and the hub-key access to the self-update and
|
||||
geo routes (the dashboard keeps using those with a session). The matching CSRF exemption for the hub key is gone.
|
||||
- **Nothing called them:** no caller in the hub (it never connects into a box; its geo switch goes to Cloudflare
|
||||
directly), the agent, the controller's own pages, or any script in the five repos. The hub README listed „Push/Pull
|
||||
Config", „Show Diff" and „Trigger Update" — no code exists for them; the four rows are removed.
|
||||
`02-controller-module-map.md` marked config-apply as needing rework, not as a planned use.
|
||||
- **Proof:** the route table is now one function (`buildMux`), and `TestR970_HubKeyOpensNoDoor` drives that real table
|
||||
with real servers: every method on every former door, with the box's key and no session, is refused, and a planted
|
||||
settings file is neither served nor changed. Plus `TestR970_ConfigRoutesAreGone` and
|
||||
`TestR970_HubKeyIsNoCSRFExemption`. Each red-proved (`redproof/`). **Live on demo-hp:** every former door answers 401
|
||||
with the box's own key (read inside the box, never printed); `/api/health` 200 (`P3/live-door-check.txt`).
|
||||
|
||||
## 4. Go and base images (R-972)
|
||||
|
||||
| | before | after |
|
||||
|---|---|---|
|
||||
| controller binary | Go 1.24.13, x/crypto v0.31.0 | **Go 1.27.2, x/crypto v0.58.0** |
|
||||
| controller runtime | `debian:bookworm-slim` | **`debian:12.13-slim@sha256:67b3…` + `apt-get upgrade`** (restic stays 0.14.0) |
|
||||
| controller builder | `golang:1.24-bookworm` | `golang:1.27.2-trixie@sha256:4766…` |
|
||||
| hub | Go 1.24.13, x/crypto v0.31.0, `alpine:3.21` | **Go 1.27.2, x/crypto v0.58.0, `alpine:3.24.2@sha256:294b…`** (builder `golang:1.27.2-alpine3.24@sha256:f92b…`) |
|
||||
| agent | Go 1.26.0 (binary 0.158.1), x/crypto v0.52.0 | `go.mod` Go 1.27.2, x/crypto v0.58.0 — committed, **not released** |
|
||||
|
||||
Scanners (the review's own tools: trivy 0.74.0, govulncheck v1.1.4):
|
||||
|
||||
| image | critical (fixable) | high (fixable) | govulncheck on the binary |
|
||||
|---|---|---|---|
|
||||
| controller 0.321.0 (before) | 16 (12) | 200 (64) | 62 |
|
||||
| controller 0.322.0-rc.3 = the release's commit | **4 (0)** | **140 (4)** | **1** |
|
||||
| hub 0.155.0 (before) | 0 (0) | 36 (36) | 62 |
|
||||
| hub 0.156.0 | **0 (0)** | **0 (0)** | **1** |
|
||||
|
||||
The 4 remaining fixable highs are in Docker's own `docker-compose` plugin (its Go), not ours. The one govulncheck note
|
||||
is `x/crypto/openpgp`, a package neither program links (`go mod why`), reported at module level. Full suites green on
|
||||
Go 1.27.2 in all three repos (the i18n parity suite included).
|
||||
|
||||
**Restic, measured on demo-hp:** an off-site backup with restic 0.18.0 (rc.1, Debian 13) — „backup OK", 172 snapshots;
|
||||
then with 0.14.0 (rc.2) on the same store — „backup OK", 186 snapshots. The store stays readable both ways; the other
|
||||
0.14.0 assumptions are not measured on 0.18 → R-1105.
|
||||
|
||||
## 5. Versions, commits, fixtures, rows
|
||||
|
||||
- **felhom-controller** `8fe46a1` → `f037be7` (R-970, Data tab, R-1103) → `fc77fcb` (Go 1.27.2) → `52619e8`
|
||||
(Debian 12.13) → `e9d0d72` (CHANGELOG, `apt-get upgrade`) → docs. Test images rc.1 (Debian 13), rc.2, rc.3 (demo-hp
|
||||
only); release **0.322.0**, MinAgent 0.158.1.
|
||||
- **felhom.eu** `836d64ef` → `9d6f3b46` (hub 0.156.0) → this commit. **Hub 0.156.0 deployed** on the operator's yes
|
||||
(`6999c15f`, manifest tag only): see §5a.
|
||||
- **felhom-agent** `dd8b2ac` → `37b99d7` (Go 1.27.2, not released).
|
||||
- **Fixtures re-captured (8):** `app_data_deployed`, `app_data_export`, `app_data_notarget_disabled`,
|
||||
`app_data_target_auto`, `app_data_target_ssd`, `deploy_new`, `deploy_new_blocked`, `deploy_new_memory_blocked`
|
||||
(the last three: whitespace only, plus one test-data option production never renders). **Removed (2):**
|
||||
`deploy_installed`, `deploy_installed_restored`. Every other fixture byte-identical.
|
||||
- **Rows: before 175, after 174. Closed 3** (R-1033, R-970, R-1103). **Opened 2** (R-1104, R-1105). R-972 NARROWED.
|
||||
|
||||
**The release (`release/`):** Tester 1, demo-hp and demo-felhom took 0.322.0 through per-box floors (all three on it at
|
||||
19:01). Nothing unhealthy, restarting, exited or created; the three dashboards 200; https-only 35 names OK; restic 0.14.0
|
||||
in each controller. **Global floor 0.322.0 / MinAgent 0.158.1** at 19:04:50 (`303` → `flash=floor_set`); the three
|
||||
per-box floors cleared. Read back on two channels: the hub's own `/configuration` page, and one `tar` of `hub.db` +
|
||||
`-wal` + `-shm` (integrity ok, newest report 12 min old): `min_controller_version` 0.322.0, `…declared_min_agent`
|
||||
`0.322.0=0.158.1`, the three per-box floors empty.
|
||||
|
||||
## 5a. The hub deploy (operator's yes, 2026-10-16)
|
||||
|
||||
`manifests/hub.yaml` tag 0.155.0 → 0.156.0 (`6999c15f`). ArgoCD hard refresh: only `Deployment/hub` out of sync; only it
|
||||
was synced. Synced/Healthy at the pushed revision; the pod runs `felhom-hub:0.156.0`, 0 restarts; its log says
|
||||
`felhom-hub 0.156.0 starting`, no error line. **Positive observables:** reports from demo-felhom, demo-hp and Tester 1 at
|
||||
19:27 and 19:31 (the hub's log); the hub's `/configuration` page (HTTP 200) shows 0.322.0 and 0.158.1
|
||||
(`release/hub-page-floor.txt`); a database copy with its WAL after the start (`release/hub-0.156.0-after-deploy.txt`).
|
||||
**CI:** every commit of this task green by job. The controller docs commit failed first (job 1959): its CI read the
|
||||
register before the felhom.eu push that added R-1105; re-run after that push, green.
|
||||
|
||||
## 6. Observations
|
||||
|
||||
1. Tester 2 is not checked for plain http while it is off. FILED: R-1104
|
||||
2. Debian 13 would move the off-site engine to restic 0.18.0 with assumptions measured on 0.14.0. FILED: R-1105
|
||||
3. govulncheck names `x/crypto/openpgp` in both new binaries. NOT-A-FINDING: neither program links it (`go mod why`: not needed); the report is module-level because the binaries are stripped.
|
||||
4. The parity case `deploy_new_memory_blocked` set „the current drive is missing" on a NOT-installed app, which the handler never does. NOT-A-FINDING: test data only; the re-captured fixture now shows what production renders.
|
||||
5. The Samba image (48 fixable high in the review) was not rebuilt in this task. NOT-A-FINDING: it is already named in R-972, which stays NARROWED for it.
|
||||
|
||||
## 7. Teardown (three layers)
|
||||
|
||||
- **Machine:** demo-hp 9201 ran rc.1, rc.2, rc.3, then the release by the floor; two by-day backup runs (off-site
|
||||
included). The door-check script ran inside the controller container and was removed; the key never left the box.
|
||||
- **Host:** nothing.
|
||||
- **Hub:** per-box floors set and cleared; the global floor raised (the release). Hub 0.156.0 deployed (only `Deployment/hub` synced).
|
||||
@@ -0,0 +1,126 @@
|
||||
# REPORT — the controller on Debian 13 with a pinned restic, re-proven; the Samba image rebuilt (2026-10-16)
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. Operator brief „the controller on Debian 13 with a pinned restic,
|
||||
re-proven; the Samba image rebuilt". Design read first: `architecture/07-backup-architecture.md` (Tier 3, §6.3, §6.7 the
|
||||
failure classifier, §3 the restore table; now §6.8), `06-offsite-connectivity.md`, `03-host-agent.md` (escrow),
|
||||
`_recovery-inventory-2026-07-28.md`. Evidence: `documentation/audits/debian13-restic-2026-10-16/`.
|
||||
|
||||
## 0. Decisions I took (inside the brief; reversible)
|
||||
|
||||
1. **I removed seven packages, not two.** The brief named the two database clients. I read every program call in the
|
||||
controller: it never runs `sqlite3`, `fdisk`, `e2fsprogs`, `parted` (disk work is the agent's since v0.37.0) or
|
||||
`sshpass` (its key install was replaced by the hub's registrar in v0.289.0) either. On demo-hp every kept tool works,
|
||||
every removed one is gone, and the log has no „executable not found". To undo: put the names back in the apt list.
|
||||
2. **I kept restic 0.18.1, as the brief says, although it adds scanner findings** (§4). The choice of 0.19.x is yours
|
||||
(R-1107).
|
||||
|
||||
## 1. The assumption table (Part 1) and each row on restic 0.18.1
|
||||
|
||||
Measured on scratch stores with both official binaries side by side (`part1/`), then pinned as test fixtures of REAL
|
||||
outputs (`felhom-controller` `internal/backup/testdata/restic/`, `TestR1105_*`, red-proved by six mutations).
|
||||
|
||||
| restic call · where | What the code assumes | 0.18.1 |
|
||||
|---|---|---|
|
||||
| `backup` (+ missing path) · `offbox.go`, `offbox_capture.go` | a missing source path: a warning, exit 0; an unreadable file: exit 3 | same |
|
||||
| `backup --json` · `offbox_progress.go` | status lines: `percent_done`, totals, `current_files` | same names (live bar moved on demo-hp) |
|
||||
| `snapshots --json`, `snapshots latest --tag` · `offbox.go`, `offbox_inventory.go`, `offbox_restore.go`, `offbox_window.go` | one JSON array, nothing else in the combined output (also with an empty cache) | same (new fields only) |
|
||||
| `stats <id>`, `stats --mode raw-data` · `offbox_restore.go`, `offbox.go` | `total_size` | same |
|
||||
| `forget … --dry-run --json` · `offbox_window.go` | the plan of the 7/4/6 policy, `--group-by host,tags` | **CHANGED, safe direction:** 0.17+ also keeps each group's oldest snapshot while monthly slots are unused — fewer removals, never more (41-day set 60 vs 62; 230-day set identical, both time zones). The guard's verdict holds. Pinned. No code change needed. |
|
||||
| `restore … --include <unit> --no-lock` · `offbox_proof.go` | writes nothing to the store | same (restores from a read-only store) |
|
||||
| `restore` (plain) · `offbox_restore.go`, `offbox.go`, `shares_restore.go` | the layout under the target | same. The old comment „restore takes no lock" was wrong for both versions; corrected |
|
||||
| `cat config`, `init` · `offbox.go` probe | „wrong password…" = orphaned; „unable to open config file" = no repo; „already exists" = init race is success | same |
|
||||
| `unlock`, `unlock --remove-all` · `resticStep` | lock text „repository is already locked"; stale-only unlock keeps a foreign lock | same (both lock forms) |
|
||||
| `check` · `offbox_integrity.go` | a damaged pack reads as damage, a healthy run does not | same verdict (other wording) |
|
||||
| the §6.7 classifier | refused port, refused key, unreachable host, dead rclone ssh → `transport` | same |
|
||||
| agent: escrow, recovery, DR recipe | — | **the agent runs no restic**; it only stores and returns the password |
|
||||
|
||||
Exit codes changed (10, 11, 12); the controller reads none.
|
||||
|
||||
## 2. The version and the pin
|
||||
|
||||
- **restic 0.18.1** (newest 0.18 patch). The hosts run **no restic** (demo-hp, demo-felhom, Tester 1: not installed); every
|
||||
guest ran Debian's 0.14.0. So „one version for guest and host" means the guest only.
|
||||
- **The pin:** a `restic` build stage downloads the official release file; restic's `SHA256SUMS` signature is checked
|
||||
with the vendored release key (fingerprint CF8F 18F2 8445 7597 3F79 D4E1 91A6 868B D3F7 A907, cross-checked against
|
||||
restic's own install doc); the file must match the published sum AND the sum pinned in the Dockerfile. A wrong sum
|
||||
fails the build (proved). `TestR1105_ResticIsPinnedOutsideTheOS`, red-proved by four mutations.
|
||||
|
||||
## 3. Both directions and the drill (demo-hp 9201, rc.1, the dashboard's own posts)
|
||||
|
||||
- **0.18.1 restores a 0.14.0 snapshot:** the product's off-site restore of bookstack → a tree byte-identical to a 0.14.0
|
||||
restore of the same snapshot. **0.14.0 restores a 0.18.1 snapshot:** byte-identical too. Both on demo-hp's REAL store.
|
||||
- **0.14.0 after a 0.18.1 prune:** proven on a scratch store (502 snapshots; `check --read-data` clean, both restores).
|
||||
On the real store it waits for demo-hp's next weekly clean-up window (due after 2026-10-18 14:16 UTC). In STATUS.
|
||||
- **The drill:** Rallly (database) and Grimmory (books folder): a marker row and a marker file; the off-site run
|
||||
(dumps + push, 3m33s, 186 → 202 snapshots); removed with data and backups; reinstalled (nothing there — the negative
|
||||
control); full restore from the off-site copy. **The row and the file came back** (file sha256 identical). **But both
|
||||
apps were then unusable — not because of restic:** the restore puts back the OLD database volume with the old
|
||||
password, and the reinstall made a new one. Rallly crash-looped on auth while the restore said done; Grimmory was held
|
||||
stopped. Filed R-1106 (R-127's class, on the reinstall path).
|
||||
|
||||
## 4. Debian 13 and the scanner
|
||||
|
||||
Kept: git, rsync, util-linux, openssh-client, curl, ca-certificates, gnupg, docker CLI + compose plugin. Removed: the
|
||||
seven in §0. trivy 0.74.0 (critical / high, fixable in brackets):
|
||||
|
||||
| image | critical | high |
|
||||
|---|---|---|
|
||||
| controller 0.322.0 (Debian 12) | 4 (0) | 140 (4) |
|
||||
| controller 0.323.0 (Debian 13) | **3 (2)** | **122 (52)** |
|
||||
| — of which the OS | 1 (0) | 70 (0) |
|
||||
| — of which restic 0.18.1 (Go 1.25.1, old modules) | 2 (2) | 48 (48) |
|
||||
| felhom-samba 1.1.0 | 0 | 48 (48) |
|
||||
| felhom-samba 1.2.0 | **0** | **0** |
|
||||
|
||||
The OS part fell by half. The official restic file is built with an old Go, so the fixable count rose. restic 0.19.1's
|
||||
official file: 0 / 19. R-1107.
|
||||
|
||||
## 5. Samba (R-972)
|
||||
|
||||
felhom-samba 1.2.0: Alpine 3.24.2 (digest-pinned), same packages, Samba 4.23.8. `build-samba-image.sh` now refuses a tag
|
||||
already in the registry, a registry it cannot ask, and a dirty or unpushed tree (6 cases; 4 fail on the old script).
|
||||
Checked on demo-hp first on 1.1.0, then after the switch to 1.2.0, with the SAME household password (the passdb volume
|
||||
survived): smbclient from DooPlex wrote, listed and read back a file; a wrong password was refused; nmbd answered the
|
||||
name; mDNS answered `FELHOMHP.local`; WS-Discovery answered; smbd, nmbd, wsdd, avahi, dbus under tini; `testparm` clean.
|
||||
Found on the way: a share added before the first password is not exported (R-1108, pre-existing, narrow).
|
||||
|
||||
## 6. Release
|
||||
|
||||
Built **0.323.0** from `8587198` (code identical to rc.1 `fbcc3cf`; only CHANGELOG and README differ), MinAgent 0.158.1.
|
||||
Per-box floors at 19:23:46 UTC (`303` → `floor_set`, all three); **Tester 1, demo-hp and demo-felhom on 0.323.0 at
|
||||
19:25 UTC**, each through the normal update path (`Post-update startup: update successful`). Box walk: no container
|
||||
unhealthy, restarting, exited or created (24 / 9 / 17); restic 0.18.1 and Debian 13 in each controller; the three
|
||||
dashboards 200; https-only 35 names OK with both controls (`release/`).
|
||||
|
||||
**The global floor is NOT raised tonight** (the brief). It waits until each test box has made one real night's off-site
|
||||
backup on 0.323.0 (they run at about 02:15 UTC). The exact check and the floor command are in `STATUS.md` („What we
|
||||
work on"). The real-store check „0.14.0 reads after a 0.18.1 prune" waits for demo-hp's clean-up window (after
|
||||
2026-10-18 14:16 UTC).
|
||||
|
||||
**CI:** green by job for every commit of this task — controller `fbcc3cf` (1963), `8587198` (1965), `00a0d91` (1967); felhom.eu `e713f6ae` (1964), `babf596c` (1966).
|
||||
|
||||
## 7. Rows
|
||||
|
||||
**Before 174, after 177. Opened 3** (R-1106, R-1107, R-1108). **Closed 0.** R-1105 and R-972 NARROWED (R-1105 waits
|
||||
on the night check; R-972's Samba part is done and rides the floor). `unproven.py`: NOT WALKED 35 of 55, unchanged.
|
||||
|
||||
## 8. Observations
|
||||
|
||||
1. The forget plan of restic 0.17+ keeps each group's oldest snapshot while monthly slots are unused. NOT-A-FINDING: it only ever keeps more, the guard's verdict holds; pinned by `TestR1105_TheForgetPlanOnlyEverKeepsMore` and written into `07` §6.8.
|
||||
2. A reinstalled database app restored from the off-site copy is unusable (old volume, new password). FILED: R-1106
|
||||
3. The official restic 0.18.1 binary adds 50 fixable scanner findings. FILED: R-1107
|
||||
4. A share added before the first household password is not exported. FILED: R-1108
|
||||
5. A remove „with drive data" keeps `userdata/media/grimmory`. NOT-A-FINDING: by design (`07` §6.5, decision 67 — the household's folders stay).
|
||||
6. A restore hold outlives the remove and a fresh install. NOT-A-FINDING: by design (R-379 — only the operator clears it; `ClearUpdateHold`'s comment). The fresh install over it runs; the hold sentence stays on its page until cleared — part of R-1106's chain.
|
||||
7. The `-clear-restore-hold` tool's change is overwritten if the controller is not restarted at once. NOT-A-FINDING: the tool prints „NOW RESTART THE CONTROLLER"; done that way it worked.
|
||||
8. A plain `restic restore` writes a lock (both versions); an old code comment said it did not. NOT-A-FINDING: the comment is corrected; the nightly proof uses `--no-lock` and was never affected.
|
||||
9. `build.sh --push` also pushes `:latest` for an rc tag. NOT-A-FINDING: nothing pulls `:latest` (the update button reads X.Y.Z tags; boxes run a named tag).
|
||||
|
||||
## 9. Teardown (three layers)
|
||||
|
||||
- **Machine (demo-hp 9201):** Rallly and Grimmory removed through the product (no container, volume, unit or folder
|
||||
left; the Grimmory restore hold cleared with the operator tool and a restart). The check copies are gone. Sharing
|
||||
turned off, both test shares deleted, their empty folders removed; the passdb volume and `user_set` stay, as the
|
||||
product leaves them. The helper scripts and scratch restores in the guest removed. 9201 runs 0.323.0.
|
||||
- **Host:** nothing.
|
||||
- **Hub:** per-box floors (see §6).
|
||||
@@ -0,0 +1,128 @@
|
||||
# REPORT — every app says where its login is · FileBrowser shows its password again · a „Fájlok" button (2026-10-16)
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. Task: „logins and folders" (operator brief, daytime).
|
||||
Design read first: `architecture/02-controller-module-map.md` „The app page" (now has „Logins and folders", `[DESIGN]`),
|
||||
`15-media-library.md` §2 and §11, `CLOSED-ITEMS.md` R-513 and R-75, `app-catalog-felhom.eu/FIRST-ADMIN.md`.
|
||||
Evidence: `documentation/audits/logins-folders-2026-10-16/` (P0, P1b, live, browser, tools).
|
||||
|
||||
## 0. Decisions I took (inside the brief; each reversible)
|
||||
|
||||
1. **Media folders get no backup sentence.** The brief said „the class-driven backup sentence where a class exists". The
|
||||
only class there is `excluded`, and its sentence says „this folder is temporary and not backed up". That is false for
|
||||
the family's films. The class means „not this app's data". So: no sentence (the rule `consequenceFor` already follows
|
||||
when it cannot be sure).
|
||||
2. **An infra app has no data tab.** Export, move and the per-app backup are not the household's for a part of the box.
|
||||
`/apps/filebrowser/data` answers 302 to the page.
|
||||
3. **A sixth wording inside answer „none".** MeTube and Glance have no login of their own, but the family gate asks for
|
||||
the family name. „No sign-in needed" is false there. With `family_gate`, „none" says: you sign in with your family
|
||||
name and password. No new catalog value.
|
||||
4. **The reset uses FileBrowser's own CLI, not its environment variable** (the variable re-applies at every start).
|
||||
5. **traefik, cloudflared and samba get no login panel** — nobody signs in to them.
|
||||
|
||||
## 1. Part 0 — why FileBrowser showed „Telepítés" and lost its password
|
||||
|
||||
- FileBrowser is part of the box. It has no `app.yaml`, so the box marks it „not deployed" (and „protected").
|
||||
- Every page counted `deployed OR protected` as „on the box". The new app page (v0.315.0, commit `e66d466`) counted
|
||||
`deployed` only. So it showed the not-installed view: an Install button, no tabs, and no login. The password data was
|
||||
still there, but only the settings tab shows it, and a not-installed app has no settings tab.
|
||||
- Worse, found here: the Install button led to a deploy that had **no guard** for a protected stack. A press could have
|
||||
installed the infra stack as an app. Now refused twice (the deploy API and the deploy itself).
|
||||
- **State on both boxes:** `operator` on demo-hp (since 2026-09-15) and on the N100 (since 2026-09-16). Nobody knows
|
||||
either password.
|
||||
- **Sources:** each box has one drive source (`NVME 1TB` on demo-hp, `Külső tárhely (hdd_1)` on the N100), `Beolvasás`
|
||||
and `Megőrzött adatok`. The drive source is the drive's `userdata/`, so every `media/<folder>` is reachable.
|
||||
|
||||
## 2. What a household sees now (controller 0.320.0)
|
||||
|
||||
- **FileBrowser** is part of the box: running state, „Megnyitás ↗" to `files.<domain>`, Restart only, no Install, no
|
||||
Remove, a two-line guide. Its login: user `admin` and „Megjelenítés". If the box does not know the password: the
|
||||
sentence „Ezt a jelszót te állítottad be a Fájlkezelőben; a doboz nem ismeri." and the button „Új jelszó
|
||||
beállítása" (confirm: „A régi jelszó nem fog működni.").
|
||||
- **Every installed app** has „Belépés az alkalmazásba" with exactly one answer:
|
||||
- the box made it → „Megjelenítés" (calibre-web, kimai, nextcloud… — every app with a `type: password` field);
|
||||
- the app wrote it → „Megjelenítés" (crafty-controller; FileBrowser's is this answer too);
|
||||
- the box never had one → the v0.319.0 sentence;
|
||||
- first-visit admin → „Az admin fiókot az első megnyitáskor te hozod létre…" (the default: every other app);
|
||||
- e-mail → rallly, outline; no login → bentopdf, homepage, privatebin, onlyoffice; family sign-in → glance, metube.
|
||||
- **„Fájlok ↗"** in the header: RomM → `roms`, Calibre-Web → `media/books`, Plex → the first drive's `media`. Immich →
|
||||
no button (its data is in a named volume). The data tab lists each drive's films and series for Plex, Jellyfin and
|
||||
Emby, series for Sonarr, films for Radarr, the download folder for qBittorrent and Transmission. A folder on a drive
|
||||
that is not there now is text, not a link. Under the list: „A Fájlkezelő a saját jelszavát kéri az első alkalommal…"
|
||||
with a link to the FileBrowser page.
|
||||
|
||||
## 3. Part 1b — the „new password" button
|
||||
|
||||
- **Spike (demo-hp only):** FileBrowser 1.5.6's CLI `set -u admin,<pw>` (`backend/cmd/cli.go:206-269`) sets the
|
||||
password with no old one. The server must be stopped first: the CLI opens the database with no lock timeout
|
||||
(`backend/database/storage/storage.go:26`). Measured: unknown → K1 200; K1 → K2: K2 200, K1 401, admin/admin 401; a
|
||||
test user, a test share and the settings unchanged; cleaned up. `P1b/`.
|
||||
- **Built and pressed on demo-hp through the endpoint the button calls** (`live/fb-reset-press.txt`,
|
||||
`live/fb-reset-verify.txt`): 200 in 3.4 s; the page then offers „Megjelenítés"; the reveal returns a 16-character
|
||||
password; a second press is refused (409, the box already knows it). Checked from a second channel, FileBrowser's
|
||||
own login inside the box: new 200, the old 401, admin/admin 401; users, shares and settings unchanged; state
|
||||
`generated`. The controller log names the act and never the value.
|
||||
- **The N100: pressed at the operator's yes in chat** (14:19 UTC), after it took 0.320.0 by the normal update path: 200 in
|
||||
3.5 s; inside the box the new password 200, admin/admin 401, state `generated` (`live/n100-fb-reset-*.txt`). The
|
||||
operator looked at the pages on demo-hp and confirmed the FileBrowser login works.
|
||||
|
||||
## 4. Browser check (headless Chrome through `https://felhom.enkisfelhom.hu`, `browser/`)
|
||||
|
||||
Method: `tools/cdp_logins.py` (the last session's CDP client, extended). FileBrowser, RomM, Calibre-Web and Plex pages
|
||||
and data tabs; screenshots for each. **The cold „Fájlok" click**, with every cookie cleared first: RomM, Calibre-Web
|
||||
and Plex each went to `files…/login?redirect=<the folder>`; after FileBrowser's login each landed ON the folder
|
||||
(`/files/NVME%201TB/roms`, `…/media/books`, `…/media`). No console error on any page, except the guide tab's missing
|
||||
screenshot for FileBrowser (404; the section hides itself, as for every app without screenshots). No page wider than
|
||||
the window. The FileBrowser line's link was dark blue in rc.1 — fixed in rc.2 / the release.
|
||||
|
||||
To be able to show RomM, RomM was installed fresh on demo-hp (its old data kept aside by the product's own „fresh"
|
||||
choice). It stays for the operator's look.
|
||||
|
||||
## 5. Version, commits, fixtures, rows
|
||||
|
||||
- **felhom-controller** `39bf562` → `f30a808` (the work) → `1296e7a` (the link style) → docs. Test images
|
||||
`0.320.0-rc.1`, `rc.2` (demo-hp only); release **0.320.0**, MinAgent 0.158.1.
|
||||
- **app-catalog-felhom.eu** `806d2d0` → `5630979` (`login_kind` on 8 apps, gate `login-kind`).
|
||||
- **felhom.eu** `bc9a8713` → this commit (design, register, STATUS, evidence).
|
||||
- **Fixtures replaced (25):** the 24 `app_settings_*` fixtures and `app_data_deployed` (the login panel; the FileBrowser
|
||||
line). **New (6):** `app_settings_infra_filebrowser`, `app_guide_infra_filebrowser`, `app_settings_login_email`,
|
||||
`app_settings_login_none`, `app_settings_login_family`, `app_data_files_media`. Every other fixture, the install page
|
||||
included, byte-identical.
|
||||
- **Tests, each red-proved** (the mutation and its failure: `audits/logins-folders-2026-10-16/redproof.txt`):
|
||||
`TestAppPage_InfraAppHasNoInstall`, `_EveryInstalledAppHasOneLoginAnswer`, `_FilesButtonTarget`,
|
||||
`_UnknownSourceIsTextNotLink`, `TestFileBrowserReset_SetsVerifiesAndStoresEncrypted`, `_FailedVerifyKeepsOperator`,
|
||||
`_OnlyInOperatorState`, `TestDeployStack_RefusesProtectedStack`; catalog gate `login-kind` (decoys).
|
||||
- **Register: before 176, after 179, opened 3 (R-1100, R-1101, R-1102), closed 0.**
|
||||
|
||||
**Release (`09` §3 decision 250, `.claude/rules/unprompted-work.md` item 6):** per-box floors 0.320.0 for Tester 1,
|
||||
demo-hp and the N100; each took 0.320.0 through its normal update path (the N100 and Tester 1 within minutes, demo-hp
|
||||
at its next 15-minute report). Box checks green on all three (`release/containers.txt`: nothing unhealthy, restarting,
|
||||
exited or created; `dashboards.txt`: 200 ×3; `https-only.txt`: 35 names → 301, the negative control convicted). Then
|
||||
the **global floor 0.320.0 / MinAgent 0.158.1** with CC's token (303 `floor_set`), read back from the hub DB with its
|
||||
WAL (`release/hubdb-floors-after.txt`), and the three per-box floors cleared. On the N100, read only: the FileBrowser
|
||||
page shows the „operator" sentence and the button (`live/n100-readonly-0.320.0.txt`).
|
||||
|
||||
**System poster:** no listed fact changed (no machine, role, path, tier, time, retention or key) — none of the three
|
||||
was needed. **`unproven.py --summary`:** not walked 35 of 55, unchanged.
|
||||
|
||||
## 6. Observations
|
||||
|
||||
1. One sign-in for the dashboard and FileBrowser. FILED: R-1100.
|
||||
2. FileBrowser's stored password goes stale when changed in FileBrowser, and the page says to change it. FILED: R-1101.
|
||||
3. Eight apps say „starts with a known, shared password" about a generated or typed login. FILED: R-1102 (decision D10).
|
||||
4. The install button could deploy a protected stack. NOT-A-FINDING: small, fixed in this release with a test.
|
||||
5. Plex gets the first-visit sentence, but its login is a plex.tv account. NOT-A-FINDING: what the sentence says is
|
||||
true (the box does not know it; the household makes it at the claim).
|
||||
6. The build tags every image `latest`, test images too. NOT-A-FINDING: nothing installs or offers `latest`
|
||||
(searched the hub, the agent and the scripts).
|
||||
7. The guide tab asks for screenshots FileBrowser does not have (404 in the console). NOT-A-FINDING: the section hides
|
||||
itself, the same for every app without screenshots.
|
||||
|
||||
## 7. Teardown (three layers)
|
||||
|
||||
- **Machine:** demo-hp guest 9201 — the spike's files (`/root/fbspike`: test passwords as files, the database backup)
|
||||
removed; the spike's test user and share deleted. RomM installed fresh and **kept** (for the operator's look).
|
||||
FileBrowser's password on demo-hp is now the box's own (`generated`), shown by „Megjelenítés". The N100: nothing
|
||||
changed but the normal release update.
|
||||
- **Host:** demo-hp `/root/fbspike-tmp` and the pushed password files removed. Nothing else.
|
||||
- **Hub:** the three per-box floors set and cleared; the global floor raised to 0.320.0 / MinAgent 0.158.1 (this is the
|
||||
release, not a leftover).
|
||||
@@ -0,0 +1,108 @@
|
||||
# REPORT — night 2026-10-16 → 17: restore after a reinstall · restic 0.19.1 · small fixes · dashboard hardening
|
||||
|
||||
Written in ASD-STE100 Simplified Technical English. Brief: the night task of 2026-10-16 (operator decisions 1A and 2A,
|
||||
21:57). Design read first: `architecture/07-backup-architecture.md` §7.4 (now carries the rule), §6.8, the restore table;
|
||||
`_recovery-inventory-2026-07-28.md`; `02-controller-module-map.md`. Evidence: `documentation/audits/night-restore-2026-10-17/`.
|
||||
|
||||
## 0. Readings I took without asking
|
||||
|
||||
1. **The rule „the backup's database password wins" applies only when the restore puts back the database's DATA
|
||||
VOLUME.** A backup that holds only a dump goes into the current data folder, which only the current password opens.
|
||||
2. **„Not on the box" means: the current install is not older than the data.** A unit without the password over an
|
||||
install that existed when the backup was taken uses the install's value; over a newer install it is refused.
|
||||
3. **Grimmory's on-box restore after its books were removed refused by design** (R-479: a unit holds settings and
|
||||
database, not the household's files). Its on-box route is „use my kept data", which I proved instead.
|
||||
4. **Part 6: researched, not changed.** Changing five apps' Redis settings on every box needs answers I do not have
|
||||
tonight (the ladder step files, the old volumes left behind, keeping data across the change). R-1096 holds the plan.
|
||||
5. **The prune with 0.19.1 on demo-hp's real store cannot run tonight:** its clean-up window opens after 2026-10-18 14:16
|
||||
UTC and only the operator can grant one sooner. Proven on a scratch store instead.
|
||||
|
||||
## 1. The restore fix (R-1106, R-127 b, R-926)
|
||||
|
||||
| Route | Where the database password came from before | Now |
|
||||
|---|---|---|
|
||||
| On-box restore („Visszaállítás"), update rollback, „use my kept data", off-site at the snapshot's version | the unit (unit wins); O4 GENERATED it when missing | the unit; missing → install older than the data → kept, else **refused before any change**; never generated |
|
||||
| Off-site full restore, same version (the reinstall case) | the current install (**the bug**) | the snapshot's, written before the database starts, when the snapshot replays the DB volume |
|
||||
| `.fab` import | the bundle's app.yaml | unchanged |
|
||||
|
||||
**Drill on demo-hp 9201 (rc.1, the dashboard's own posts), Rallly = Postgres, Grimmory = MariaDB:**
|
||||
|
||||
| Path | Rallly | Grimmory |
|
||||
|---|---|---|
|
||||
| Off-site full restore after remove + reinstall | marker row back, healthy, 0 auth errors, app's DB login works | marker row + books file (sha256) back, healthy, root and app logins work |
|
||||
| On-box restore after remove (backups kept) + reinstall | both marker rows back, healthy, logins work | refused by design (books removed — R-479) |
|
||||
| Refusal: unit without the password over a newer install | refused before any change; the app kept running, data untouched | — |
|
||||
| „Use my kept data" | — | both passwords from the backup; marker row back; kept file unchanged |
|
||||
|
||||
Last night the same off-site steps left Rallly crash-looping and Grimmory held. **R-926's answer:** after a unit restore
|
||||
the page hides a login it had to invent and says to use the old password — correct. On the reinstall path the page
|
||||
would have shown the reinstall's admin login; now it records it as not the app's login. Tests `TestR1106_*` (8),
|
||||
red-proved by six mutations; catalog gate `db-credentials` (17 apps, 20 fields, all `type: secret`).
|
||||
|
||||
## 2. restic 0.19.1 (R-1107)
|
||||
|
||||
- **Notes read** 0.18.1 → 0.19.1. One change touches the table: a missing top-level backup path now exits 3 (was 0).
|
||||
The box removes such paths before the call, so only a race reaches it — now a loud failure instead of a silent partial
|
||||
copy. No repository format change.
|
||||
- **Pinned** like 0.18.1 (official file, signed sums, our two pinned sums). A wrong sum fails the build (proved).
|
||||
- **Fixtures** for 0.14.0, 0.18.1, 0.19.1 from one store; `TestR1105_*` runs each against 0.14.0 — all pass.
|
||||
- **Both directions on demo-hp's REAL store:** copies made by 0.14.0, 0.18.1 and 0.19.1, each restored by all three —
|
||||
byte-identical every time; all three list the same 234 copies. **After a 0.19.1 prune:** scratch store only (reading 5) —
|
||||
0.14.0 lists, checks and restores.
|
||||
- **Scanner** (trivy 0.74.0): critical 3 (2 fixable) → **1 (0)**; high 122 (52) → **93 (23)**. restic's own part 50 → 19.
|
||||
|
||||
## 3. Small fixes (Part 3)
|
||||
|
||||
- **R-1108** done — a share added before the first password is served (test red-proved; live `smbclient` from DooPlex).
|
||||
- **R-1065** done — a Plex claim schedules the wiring (test red-proved; no live claim tonight).
|
||||
- **R-1017** done — the guard exists and found one more bare drive path (the remove dialog's kept-folders list), fixed.
|
||||
|
||||
## 4. Dashboard hardening (Part 4)
|
||||
|
||||
All six R-983 items and both R-976 items done, one red-proved test each. 135 i18n fixtures re-captured (the sign-out
|
||||
control became a form; one minimum-length change), 22 unchanged. **Headless Chrome through demo-hp's public address:**
|
||||
sign in → `/launcher`; `X-Frame-Options: DENY`; sign-out is a POST with CSRF → `/login`; sign in again; the
|
||||
family-gated Grimmory and the setup-gated Rallly open their own pages.
|
||||
|
||||
## 5. Part 6 — Redis (R-1096)
|
||||
|
||||
Seven Redis services: five append-only (romm, docmost, paperless-ngx, immich, nextcloud), two snapshot-only (outline,
|
||||
dawarich). Table and proposed lines: `P6/redis-persistence.md`. Not changed (reading 4). Also: RomM's file WAS repaired on
|
||||
2026-09-24 — the row's history was wrong, corrected.
|
||||
|
||||
## 6. Release
|
||||
|
||||
**0.324.0** built from `b695857` (code identical to rc.1 `f75b3aa`, the README differs), MinAgent 0.158.1 (the hub's
|
||||
declared value — not lowered). Per-box floors at 21:51:39 UTC (`303` → `floor_set`, all three); **Tester 1, demo-hp and
|
||||
demo-felhom on 0.324.0 at 21:52 UTC**, each through the normal update path. Box walk: no container unhealthy, restarting,
|
||||
exited or created (28 / 9 / 17); restic 0.19.1 in each; dashboards 200; https-only 37 names OK (`release/`).
|
||||
|
||||
NIGHT-PLACEHOLDER
|
||||
|
||||
## 7. Rows
|
||||
|
||||
**Before 177. Opened 7** (R-1109 … R-1115). **Closed 8** (R-1106, R-127, R-926, R-1108, R-1065, R-1017, R-983, R-976).
|
||||
FLOOR-ROWS-PLACEHOLDER R-1096 corrected and READY. `unproven.py`: NOT WALKED 35 of 55, unchanged.
|
||||
|
||||
## 8. Observations
|
||||
|
||||
1. A reinstalled database app restored from the off-site copy came back unusable. NOT-A-FINDING: it is R-1106, closed tonight.
|
||||
2. A share renamed at the same path is never applied to a running samba. FILED: R-1109
|
||||
3. Two sibling path helpers are called with drive-named arguments, unguarded. FILED: R-1110
|
||||
4. The share-password and escrow limiters count a try after the password check. FILED: R-1111
|
||||
5. Family sessions are stored raw, not as a fingerprint. FILED: R-1112
|
||||
6. Each `/__family/start` pass with a household session creates a new family session. FILED: R-1113
|
||||
7. `controller_gates.py` cannot pass in a worktree outside `git/`. FILED: R-1114
|
||||
8. A catalog decoy („a credential token rewritten in translation") no longer convicts. FILED: R-1115
|
||||
9. Grimmory's on-box restore after its books were removed refused. NOT-A-FINDING: by design (R-479), its route is „use my kept data", proven.
|
||||
10. The launcher logs 404s for SVG app logos. NOT-A-FINDING: the page asks for the SVG first and falls back to the PNG; the same 263 assets were in 0.322.0.
|
||||
11. Two catalog-wide stacks tests take about 26 s each. NOT-A-FINDING: test duration, no defect.
|
||||
12. R-1096's history said RomM's Redis file was not repaired on 2026-09-24. NOT-A-FINDING: the row text is corrected tonight (the file was repaired; the 2026-10-16 cause is unproven).
|
||||
|
||||
## 9. Teardown (three layers)
|
||||
|
||||
- **Machine (demo-hp 9201):** Rallly and Grimmory removed through the product (no container, volume, unit, copy or hold
|
||||
left); my marker files and the cover file removed; sharing off, the test share and its folders removed. 9201 runs
|
||||
0.324.0. The old Kimai hold is not mine and stays.
|
||||
- **Host:** nothing.
|
||||
- **Hub:** per-box floors set (§6).
|
||||
@@ -0,0 +1,80 @@
|
||||
# REPORT — RomM finds its games · Ghost does not lock the household out · the floor refuses withdrawn builds (2026-10-16)
|
||||
|
||||
Daytime, operator present. A second session works in this repo today, so this is a dated report, not `REPORT.md`.
|
||||
Design read: `architecture/09-update-architecture.md` (§0, decisions 4 and 250), `05-hub-architecture.md` (floor
|
||||
serving), `02-controller-module-map.md` (app e-mail), `07-backup-architecture.md` (RomM's optional legs).
|
||||
Evidence: `documentation/audits/romm-ghost-2026-10-16/` (A1, A2, B, C1, C2, teardown).
|
||||
|
||||
## 1. RomM (R-1089) — measured, fixed, both scenarios pass
|
||||
|
||||
- **A1 (9201):** a dummy file where the first steps said (`roms/gba/`) → „No platforms found”, 0 games. One folder
|
||||
deeper (`roms/roms/gba/`) → found. Cause: RomM 5.3.1 scans `library/roms/<platform>/`; the drive's `roms/` was
|
||||
mounted as `library/`.
|
||||
- **Fix (catalog `fb18471`):** `roms` → `library/roms`; new drive folder `roms-bios` → `library/bios` (operator ruling);
|
||||
backup and folder labels for it; first steps corrected; stale `admin / admin` comment replaced.
|
||||
- **Scenario A** (fresh install): the file at `roms/gba/` is found. **Pass.**
|
||||
- **Scenario B** (old install, correction, restart): the old-steps file is found; the drive is unchanged byte for byte;
|
||||
the data tab shows both folders backed up. **Pass.** A game uploaded through RomM under the old mapping becomes
|
||||
„missing” and reappears under a fake platform „roms” (it sits in `roms/roms/<platform>/`).
|
||||
- **BIOS:** a file in `roms-bios/psx/` is read as psx firmware.
|
||||
- **Who runs RomM:** demo-felhom (N100) and demo-hp only (hub `/apps/romm`, read only). No customer box.
|
||||
|
||||
## 2. Withdrawn controller builds (R-1085) — refused at input and at serve time, live
|
||||
|
||||
- **Hub v0.155.0** (`b17e9e99`, deployed `f1e1c8a6` after the operator's yes). One list
|
||||
(`hub/internal/store/spoiled_tags.go`: `0.315.0`, `0.316.3`). Both floor forms and the impact probe refuse a listed
|
||||
tag; `ResolveManagedFloor` holds one that arrives any other way (stored value, override, config/env default), with its
|
||||
own reason and one `[WARN]` per change. The Configuration and customer pages say „withdrawn".
|
||||
- **The hand deploy:** a marked never-deploy line and a chained pre-check in the `felhom-build-deploy` skill; a test pins
|
||||
that both equal the hub's list.
|
||||
- **Tests:** store, forms, probe, both branches of both page notices, the real report handler, the skill parity.
|
||||
**Ten red-proofs, each seen failing** (`B/hub-redproofs.txt`). Full hub `go build && go vet && go test`: green.
|
||||
- **Live (`B/hub-live-check.txt`):** ArgoCD Synced/Healthy at `f1e1c8a6`, pod on `felhom-hub:0.155.0`. Impact probe for
|
||||
`0.316.3` → `valid:false, spoiled:true`; control `0.319.0` → valid. **POST `0.316.3` to the global floor with CC's
|
||||
token → 303 `flash=floor_spoiled`; the floor read back is still `0.319.0`**; the hub logged one `[WARN] … REFUSED`.
|
||||
Boxes kept being served `0.319.0`.
|
||||
|
||||
## 3. Ghost (R-1090) — measured, fixed, both ways pass
|
||||
|
||||
- **C1:** Ghost 6.67.0 skips the e-mailed code only on the first-ever sign-in; the next new device got **500 „Failed to
|
||||
send email”** — locked out.
|
||||
- **Fix (catalog `fb18471`):** the device check is off by default and turned on with the mail settings by a new
|
||||
`smtp_mapping` when app e-mail is on. One FAQ entry.
|
||||
- **Scenario E, e-mail off:** a new device signs in with the password alone (201).
|
||||
- **Scenario E, e-mail on:** 403 `2FA_NEW_DEVICE_DETECTED`; the shim relayed (hub 200); the mail arrived; the code from
|
||||
it was accepted and the device signed in (200).
|
||||
|
||||
## 4. Versions, commits, teardown, register
|
||||
|
||||
- Catalog: `fb18471` (RomM + Ghost), `806d2d0` (report, box-walk fix). felhom.eu: `b17e9e99` (hub code, skill, `09`,
|
||||
evidence), `f1e1c8a6` (manifest), and this commit (register, STATUS, report, last evidence). Hub v0.154.0 → **v0.155.0**.
|
||||
Controller and agent: not touched.
|
||||
- **CI by job id:** felhom.eu `b17e9e99` job 1934 success; catalog `fb18471` job 1931 success; the later pushes are
|
||||
listed in the session's final note.
|
||||
- **Teardown:** machine (9201) — RomM and Ghost removed, the drive's `roms/` as found, app e-mail back off, two empty
|
||||
helper folders removed; kept on purpose: two `kept/romm/<date>/` folders (the product's „start fresh" result, 8 KB).
|
||||
The pre-existing broken RomM was removed by operator ruling. Host — nothing. Hub — no record created; the hub
|
||||
deploy is the only hub change.
|
||||
- **Register:** **175 rows before, 176 after; 3 closed (R-1085, R-1089, R-1090), 4 opened (R-1096, R-1097, R-1098,
|
||||
R-1099).** STATUS: one line for the operator (R-1099). System poster: no poster fact changed — none of the three
|
||||
updates was needed.
|
||||
|
||||
## 5. Observations
|
||||
|
||||
- The standing RomM on 9201 crash-looped on Redis files damaged by the 2026-09-24 disk-full — FILED: R-1096.
|
||||
- RomM keeps saves, states and screenshots in an anonymous volume (`/romm/assets`), outside the backup — FILED: R-1097.
|
||||
- The hub build script does not refuse a version that already exists in the registry (the controller's does) —
|
||||
FILED: R-1098. Checked by hand for 0.155.0 today (not found; control 0.154.0 found).
|
||||
- The N100 may hold RomM uploads in the old place, now under a fake „roms" platform — FILED: R-1099 (operator, STATUS).
|
||||
- The box walk made a drive folder for a value the caller passed — NOT-A-FINDING: small, fixed (catalog `806d2d0`).
|
||||
- A drill fixture folder (`DRILL-2026-08-21`) on 9201's drive shows as a RomM platform — NOT-A-FINDING: another
|
||||
session's test file on a test drive, left untouched.
|
||||
- The task gave the new flash text in Hungarian and English — NOT-A-FINDING: the hub's operator pages are English only,
|
||||
so only the English text was used, like every neighbouring flash.
|
||||
- The task asked for a RomM FAQ line if a rescan is needed after the mount change — NOT-A-FINDING: a rescan does not
|
||||
fix it (the files must move), and no customer runs RomM; R-1099 covers the one box that may.
|
||||
- RomM's remove answered 502 while another session restarted 9201's controller; the remove had completed —
|
||||
NOT-A-FINDING: the proxy's answer during a deliberate controller restart.
|
||||
- 9201's controller moved `0.318.0` → `0.319.0-rc.1` during the test — NOT-A-FINDING: another session's release work.
|
||||
|
||||
`unproven.py --summary`: unchanged — 35 of 55 claims not walked.
|
||||
@@ -0,0 +1,78 @@
|
||||
# REPORT — storage identity, build B: the one-disk data area and partitions (2026-10-15 → 16)
|
||||
|
||||
Brief: „Storage identity, build B — the data area on one-disk boxes (D3, with the space check) and partitions as places
|
||||
(D5)". Design: `documentation/architecture/16-storage-physical-identity.md` §5–§7, §9 (§13 = what was built).
|
||||
Evidence: `documentation/audits/storage-identity-buildB-2026-10-15/` (README, SPACE-CHECK, live/). Method: endpoint level
|
||||
(no browser on DooPlex) and the VM console for the Proxmox install.
|
||||
|
||||
## 1. The space check, as approved (operator, 2026-10-16)
|
||||
|
||||
| Disk | Thin pool | Data volume | Reserve | Data area |
|
||||
|---|---|---|---|---|
|
||||
| 256 GB | ≈ 140 GiB | 50 GiB | 16 GiB | ≈ 42 GiB |
|
||||
| 512 GB | 349–383 GiB | 100 GiB | 35–38 GiB | ≈ 182–213 GiB |
|
||||
| 1 TB | ≈ 800 GiB | 130 GiB | 48 GiB | ≈ 590 GiB |
|
||||
| 2 TB | ≈ 1,720 GiB | 130 GiB | 48 GiB | ≈ 1,510 GiB |
|
||||
|
||||
Measured: the heaviest box uses 18 GB of its data volume; the whole catalog needs ≈ 74 GB; a whole-box copy's snapshot used
|
||||
12 MB. A disk below ≈ 230 GB gets no data area (less than 20 GiB left).
|
||||
|
||||
## 2. A fresh one-disk install
|
||||
|
||||
Scratch VM on demo-hp, 512 GB disk, the public ISO, the published installer 1.33.0, customer Tester4: data volume **100 GiB**,
|
||||
data area **213 GiB**, registered as the default drive by itself. **Jellyfin installed with no extra step** (domain and
|
||||
subdomain only; on 0.314.0 the same request was refused for the missing media path). A whole-box copy holds nothing of the
|
||||
data area.
|
||||
|
||||
## 3. Partitions — what the household can do now
|
||||
|
||||
- See each part of a free disk; format an empty part; use a part that has a filesystem; use the free part of a disk whose
|
||||
other part is already a drive.
|
||||
- Cannot: format a part while another part of the same disk is in use (the safety net refuses it — on purpose); create,
|
||||
delete or resize a part (installer only, D5). An empty part still asks for the typed confirmation (R-1093).
|
||||
|
||||
## 4. Versions and what waits on the operator
|
||||
|
||||
| Component | Version | State |
|
||||
|---|---|---|
|
||||
| agent | 0.158.1 (0.158.0 superseded — security review) | vouched by the operator; on the three test boxes with bundle 0.158.1 (through the step bundle `0.158.1-step1`) |
|
||||
| controller | 0.318.0 | released; the global floor 0.318.0 / MinAgent 0.158.1 (set by the operator) |
|
||||
| installer | 1.33.0 | published (tag `installer-v1.33.0`, `manifests/webpage.yaml`) |
|
||||
| golden | 0.318.0 | baked by CC (`documentation/tests/golden-0.318.0-2026-10-16/`), vouched by the operator |
|
||||
|
||||
Waits on the operator: discard the two unclaimed scratch registrations (ids 40, 41) and delete Tester4; Tester 1's dashboard
|
||||
password in the credentials file does not open Tester 1 (the checks ran on the scratch box instead); the poster drawing.
|
||||
|
||||
## 5. Rows
|
||||
|
||||
Rows before: 178 · after: 176 · opened 2 (R-1093, R-1094) · closed 4 (R-1068, R-1079, R-1082, R-1084).
|
||||
(Earlier in this session's build B part 1: R-1087, R-1088 opened; R-1088 was closed by another session.)
|
||||
|
||||
## 6. Claims in the brief that were wrong
|
||||
|
||||
1. „A one-disk scratch VM (200 GB)": under the approved sizes a 200 GB disk gets no data area (15 GiB < 20). Rebuilt at 512 GB.
|
||||
2. „Format the empty part, use the other" works only in that order: since the security review, a part next to an in-use part
|
||||
cannot be formatted.
|
||||
3. „Run after build A is released": build A's agent was never vouched; the vouch needed a fresh golden (the hub refused
|
||||
agent 0.158.1 next to golden 0.313.0) — CC baked golden 0.318.0 on the operator's word.
|
||||
4. The installer's design text (installer creates, agent mounts) holds, but the size rule and the only mkfs live in ONE root
|
||||
helper from the agent's bundle, run by both the installer and the signed job — so the installer makes the area only when
|
||||
the vouched agent carries that helper.
|
||||
|
||||
## 7. Fixed without a row (security review of agent 0.158.0)
|
||||
|
||||
The root mkfs wrapper briefly trusted a sibling mount the agent itself could make; the data-area helper read a failed `lvs`
|
||||
or `blkid` as „empty". Both fixed in 0.158.1, each test seen failing against 0.158.0; 0.158.0 marked „do not vouch".
|
||||
|
||||
## 8. Documents
|
||||
|
||||
`16` §7 (ruled sizes), §13 (build B status); capability map **D15** (PROVEN-LIVE) + detail; `OPEN-ITEMS` / `CLOSED-ITEMS`;
|
||||
`STATUS`; the OMV guide's second-disk line; the backup page (HU + EN); **system poster: facts file updated, drawing needs a
|
||||
refresh** (line added to STATUS).
|
||||
|
||||
## 9. Teardown
|
||||
|
||||
Machine: scratch VM 342 switched off the household way and destroyed; Tester 1's scratch disk removed. Host: helper scripts
|
||||
removed. Hub: build A's three per-customer floors cleared; two unclaimed registrations and Tester4 left for the operator.
|
||||
Secrets: the phrase, the dashboard password and the setup code lived only in private scratch files (shredded at the end);
|
||||
none in any commit (scanned).
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
**Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.**
|
||||
|
||||
**Updated 2026-10-15 (night).** Storage identity build A: agent 0.157.0 (not vouched yet), controller 0.317.0 and hub 0.154.0 — on the three test boxes only (per-box floors); the global floor stays 0.314.0 until you vouch the agent. Controller 0.317.0 also carries the app page in one column (a version number can now only be pushed once, and only from a clean tree); 0.314.0 is still the floor since 13:11 UTC — raised by CC with its own token after all three test boxes ran it (the first release with no golden; vouched golden 0.313.0); hub 0.153.0 (access tokens — CC uses its own token now, not your password). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
**Updated 2026-10-17 (night).** Controller 0.324.0 runs on the three test boxes: a restore now brings the database password back with the data (your 1A), restic 0.19.1 (your 2A), dashboard hardening — `REPORT-night-2026-10-17.md`. The update for every box: see „What we work on". **Earlier (2026-10-16 evening):** 0.323.0, Debian 13 — `REPORT-debian13-restic-2026-10-16.md`. **Earlier (midday):** Controller 0.319.0 (the app page in panels, the login in one panel) is the floor, MinAgent 0.158.1 — `REPORT-app-page-tuning-2026-10-16.md`. **Earlier (morning):** Storage identity build B shipped: agent 0.158.1 and golden 0.318.0 vouched by you, controller 0.318.0 is the floor (MinAgent 0.158.1), installer 1.33.0 published — new one-disk boxes get their own data area. Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
`documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`.
|
||||
|
||||
## What works
|
||||
- **A restore brings the database password back with the data** (controller 0.324.0, your decision 1A): remove an app, install it again, restore it — the app opens its own data (proven on demo-hp with Rallly and Grimmory, off-site and on-box, and through „use my kept data"). A backup without the password is refused before anything changes, with a plain sentence. Backup program restic 0.19.1 (your 2A): old and new copies read both ways; scanner findings in it 50 → 19.
|
||||
- **The dashboard is harder to attack** (0.324.0): no sending the browser to another site after sign-in, no framing, parallel password guesses counted, 12-character minimum, reset-code requests limited, sign-out needs a token (the old sign-out link did nothing), a password change ends every family and gate session.
|
||||
- **The box's backup program is now chosen, not inherited** (controller 0.323.0, 2026-10-16): restic 0.18.1, checked by its signature and fingerprint at every build, on Debian 13. Every way the box reads restic was compared old vs new with real outputs; old copies stay readable by the new program and new copies by the old one (on demo-hp's real store). The file-sharing image has 0 known high holes (was 48), and the same household password still works after the change.
|
||||
- **The hub's key can no longer open anything on a box** (controller 0.322.0, 2026-10-16): the settings route is gone, and every former door answers 401 even with the box's own key. Every box that reports answers only over https (35 names). The controller and the hub (0.156.0, deployed at your yes) are built on a supported Go (1.27.2): known Go weaknesses in each 62 → 1; the hub has 0 high findings.
|
||||
- **RomM game saves are in the backup now** (2026-10-16, catalog): a save survived a backup, a removal and a restore on demo-hp, byte for byte. Eight apps no longer claim „a known, shared password" (your D10 = A). If someone changes the file manager's password in the file manager, the box notices and offers a new one (controller 0.321.0).
|
||||
- **Every installed app says where its login is, and has a „Fájlok ↗" button to its folder** (controller 0.320.0, 2026-10-16). The file manager is shown as part of the box again, with its password; on a box that does not know that password, one press sets a new one (done on demo-hp and, at your yes, on the N100).
|
||||
- **Every app but three has a user guide and an FAQ, in Hungarian and English** (catalog, 2026-10-15 night): the „Útmutató” tab shows it on boxes with controller 0.315.0 or newer. Every button name in a guide was checked in the app's own files at the version we ship; a guide that could not be checked stays hidden (plant-it, home-assistant), and Emby has none (R-1092).
|
||||
- **The N100 is the lived-in box** (since 2026-10-15): used like a household's box, changed only through the product. qBittorrent, Prowlarr, Sonarr and Radarr are installed and linked by the box; films never go off-site (checked before any download). Its diary: `documentation/lived-in/n100.md`.
|
||||
- A restored app comes back exactly as safe as it was at the backup: closed to strangers until its first account is made (0.314.0, proven on the scratch box). A box on a test build now moves with the floor (Tester 1 did, by itself). The box claims Plex again (demo-hp's Plex claimed through the dashboard, 2026-10-15). A brand-new box from the public installer updated itself to the floor 3 minutes after it was linked (test box, since removed).
|
||||
@@ -23,6 +29,7 @@
|
||||
|
||||
- Felhom runs in a virtual machine on OpenMediaVault 8, tried on an exact copy of the first outside tester's box: install, bind, claim, apps, backup, and back by itself after an OMV restart. The Hungarian guide for him is `documentation/pilot/GUIDE-omv-kvm-hu.md`; open: one-disk Jellyfin (R-1068), NAS films for Jellyfin (R-1071, yours), no disk health in a VM (R-1073, yours). Waits on you: send the guide; delete test customer Tester4 in the hub.
|
||||
- A second local copy counts only when it is on a different physical disk, and the box can prove it. Inside a virtual machine the box makes the copy but says „A Felhom nem tudja ellenőrizni…" (Tester 1). On real disks nothing changes (demo-hp, N100). A NAS never hides the „only one copy" notice. A box with no drive refuses a media app in words, with a link to the Meghajtók page. Live on the three test boxes; the NAS and no-drive parts are unit-tested only. Next: build B (the internal place on one-disk boxes, R-1079). Your rulings D1–D6 are in the design (`architecture/16-storage-physical-identity.md`).
|
||||
- **One-disk boxes get their own data area** (2026-10-16): a new install makes a „Belső tárhely" on the disk, outside every whole-box copy, and media apps install there with no extra step (proven on a fresh 512 GB box: 213 GiB area, Jellyfin with no extra step). A disk split into parts can be used part by part. The backup offer no longer promises disk-failure safety when it cannot check the disk, and `/mnt/sys_drive` is no longer offered.
|
||||
|
||||
## What is broken
|
||||
- Several root-level weaknesses wait on your four decisions below (R-954, R-971, R-982, R-985).
|
||||
@@ -31,6 +38,7 @@
|
||||
- Tester 2 has never made an off-site check (R-862). The scratch box cannot read the drill catalog (R-992).
|
||||
|
||||
## What we work on
|
||||
- **The update for every box (R-1105, R-1107) waits on tonight's off-site backups on 0.324.0.** The night session checks from 02:45 UTC and raises the floor if all three are `ok`. If it could not, the morning check is: `ssh <host> "pct exec 9201 -- python3 -c \"import json;o=json.load(open('/var/lib/docker/volumes/felhom-controller-data/_data/data/settings.json'))['offbox'];print(o['last_status'],o['last_success'])\""` for `demo-hp`, `felhom-pve`, `root@192.168.0.154`; all `ok` after 2026-10-17T00:00Z on 0.324.0 → global floor `min_controller_version=0.324.0`, `min_agent=0.158.1` (`felhom-build-deploy` step 5), read back with the WAL, clear the three per-box floors. After 2026-10-18 14:16 UTC: the old restic still reads demo-hp's store after its first 0.19.1 prune (`audits/night-restore-2026-10-17/tools/rx.sh`).
|
||||
- The power-cut findings still open (R-1004, R-1005, R-1010, R-1012, R-1013).
|
||||
- Media apps on every drive (R-1023): live with the 0.313.0 floor — every drive in every media app, a new drive made ready by the box, a pulled drive loses nothing, a safe removal lets go first and the drive comes back by itself.
|
||||
|
||||
@@ -47,13 +55,14 @@
|
||||
| D7 | R-990, R-989 | Rule yes / no / later on the forum ideas (`audits/forum-research-2026-10-12/FELHOM.md` §5) and the selfh.st ideas (`audits/selfhst-survey-2026-10-12/FELHOM.md` §6) | — | — | nothing changes; the research ages | — |
|
||||
| D8 | R-206 | Install DooPlex's Docker settings from git (restarts Docker), and prune the build cache weekly: yes or no? | **Yes, in a quiet window** | — | a rebuild of DooPlex still has the settings in git | — |
|
||||
| D9 | R-1062 | The N100's USB drive is also its whole-box backup drive. Split it in two anyway? | **No — keep one drive**; the lived-in box keeps its nightly whole-box backup | Split: the box loses its local whole-box backup (or add a second drive first) | it stays one drive; multi-drive tests stay on Tester 1 | — |
|
||||
| D11 | R-1086 | qBittorrent's first steps say „set your own password”, but then the box can no longer link it to Sonarr and Radarr. Stop advising the change, or let the box ask for the new password? | **Stop advising it** — the generated password is long and shown on the dashboard | The box asks: a new card and controller work | a household that follows the advice loses its automatic downloads without being told | — |
|
||||
| D12 | R-1091 (1), (2) | Cal.com promises Google/Outlook calendar sync that is not set up, and BentoPDF says files are processed „on the server” (they are processed in the browser). Reword both to what is true? | **Yes, reword** | Keep: two promises that are not true | the app pages keep saying it | — |
|
||||
|
||||
## Waiting on you — do
|
||||
- **Olvasd el a Plex és a Sonarr útmutatót a demo-hp-n** (az alkalmazás oldalán az „Útmutató” fül). Ha más formát szeretnél, egy menetben átírom mind a 62-t. Ha nem nézed meg: a szöveg ellenőrzött, de ember még nem olvasta a képernyőn — a robot a DooPlexen nem tud bejelentkezni, mert a jelszófájl üres (R-1088).
|
||||
- **Rotate CC's hub token and restore CC's credentials file (R-1088).** A session overwrote the file with the token at 22:03; all other keys are gone, and the token was printed once into a transcript. If not: CC cannot log in to any box dashboard, and a printed token stays valid.
|
||||
- **Vouch agent 0.157.0 and its config bundle** (hub → Configuration → Artifacts). Then CC raises the global floor to controller 0.317.0 with MinAgent 0.157.0 and clears the three per-box floors. If not: households stay on 0.314.0 and keep counting a same-disk copy as a second copy; the three test boxes keep their own floor.
|
||||
- **Nézd meg a demo-hp-n a Calibre-Web „Adatok és mentés" fülét és a Fájlkezelőt** (`felhom.enkisfelhom.hu`, controller 0.321.0, minden dobozon kiadva). A mentés egy részben, egyszerű szavakkal; a Fájlkezelő nem kér frissítést és nem ajánl „Install app"-ot.
|
||||
- **Look at `roms/roms/` on the N100's drive in the file manager (R-1099).** RomM now scans `roms/<platform>/`. A game uploaded through RomM before today sits one folder deeper: if there are platform folders in `roms/roms/`, move them up into `roms/` and press „Scan” in RomM. If not: those games show under a platform called „roms”.
|
||||
- **Nézd meg a Calibre-Web oldalát a demo-hp-n** (`felhom.enkisfelhom.hu`, controller 0.319.0, minden dobozon kiadva). A fejléc, a fülsor és minden rész most külön panel (az „A” stílus). A belépési adatok egy panelben vannak, egymás mellett. A Calibre-Web jelszaváról a doboz nem tud, mert az alkalmazás előbb települt, mint hogy a sablonban jelszó lett volna — ezt most egy mondat mondja ki. Ha nem tetszik, írd meg, mit változtassak. Ha nem nézed meg: semmi nem romlik el.
|
||||
- **Discard two unclaimed boxes and delete Tester4** (hub, browser only): the build B scratch VMs registered as unclaimed boxes — registration ids 40 (`b43afa10…`) and 41 (`9c78ea30…`); both VMs are destroyed. Tester4 (the throwaway customer) is switched off the household way. If not: two dead entries stay in the Hosts list and Tester4 stays in Customers.
|
||||
- System poster needs a refresh: one-disk boxes now have a data area („Belső tárhely") on the system disk, outside the whole-guest backups (facts file updated 2026-10-16).
|
||||
- **Olvasd el a Plex és a Sonarr útmutatót a demo-hp-n** (az alkalmazás oldalán az „Útmutató” fül). Ha más formát szeretnél, egy menetben átírom mind a 62-t. A robot 2026-10-16-án bejelentkezve megnézte: mindkét oldal megjelenik magyarul és angolul, 4 rész és a GYIK, minden sor egyezik a katalógussal. Ha nem nézed meg: csak a forma marad emberi szemmel nem látott.
|
||||
- **Nézd meg az AdventureLog és a Plex oldalát a demo-hp-n, a rendes címén** (`felhom.enkisfelhom.hu`). A túl nagy keret javítva: a tartalom most 32 képpontra van a bal oszloptól és 36-ra a tetejétől (előtte 80 és 64 volt). Az alkalmazás oldala mostantól egy hasábban van: egy szélesség, vékony vonalak a szakaszok között, aláhúzott fül, és doboz csak ott, ahol az alkalmazás vár rád (kék) vagy ahol valami kockázatos (sárga). A „sikeres frissítés" sáv kis szürke sorrá vált a név alatt. Ha nem nézed meg: a felület él, csak senki nem látta még böngészőben.
|
||||
- **Nézd meg az új alkalmazás oldalt a demo-hp-n (egy telepített és egy nem telepített app).** Egy alkalmazás mostantól egy oldal négy füllel (Beállítások · Adatok és mentés · Útmutató · Napló), az Indítás/Újraindítás/Leállítás a „Működés" kártyán van, és a „Vissza" az alkalmazáshoz visz, nem a listához. Mondd meg, jó-e így az elrendezés. Ha nem nézed meg: a felület él, csak senki nem látta még böngészőben — a robot a DooPlexen nem tud kattintani.
|
||||
- **The N100 sheet (about 30 minutes):** `documentation/audits/n100-lived-in-2026-10-15/SHEET.md` — install Plex and claim it, make the admins in Prowlarr/Sonarr/Radarr, add your download sources in Prowlarr, add one show and one film. Then say „done". If not: the media apps wait, safe behind their gate.
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
| <a id="d12"></a>[D12](capability-map-detail/D.md#d12) | Boot ordering: automount + networking survive reboot; appliance self-heal watchdog | agent v0.85 | **PROVEN-LIVE** | — | `audits/CAMPAIGN-4-2026-07-13.md` (2026-07-13); `audits/CAMPAIGN-6A-2026-07-14.md` (2026-07-14) |
|
||||
| <a id="d13"></a>[D13](capability-map-detail/D.md#d13) | Media apps see EVERY drive; a drive added, pulled, back or safely removed is handled per app (notice, real restart, wiring, torrents paused/resumed) | controller v0.313.0, catalog `0188430`, hub v0.151.0 | **PROVEN-LIVE** | Tier-0 boxes (Tester 1, demo-hp 9201); the fleet with golden 0.313.0 + the floor | `audits/media-build-2026-10-15/G/` (2026-10-15); `audits/media-drive-events-2026-10-14/` (2026-10-14) |
|
||||
| <a id="d14"></a>[D14](capability-map-detail/D.md#d14) | A second local copy COUNTS only on a proven different physical disk: the agent knows each place's disks; a VM box makes the copy but says it cannot check; a same-disk copy is not made; a NAS never hides the one-copy notice; a box with no drive refuses a data-folder app in words | agent v0.157.0, controller v0.317.0, hub v0.154.0 | **PARTIAL** | live: VM `unknown` (Tester 1) and two real-disk boxes `proven` (demo-hp, N100); unit only: the same-disk skip, the NAS notice, the no-drive sentence; `usb_bridge` never seen on a device | `audits/storage-identity-buildA-2026-10-15/README.md` (2026-10-15); `architecture/16-storage-physical-identity.md` §13 |
|
||||
| <a id="d15"></a>[D15](capability-map-detail/D.md#d15) | A one-disk box gets its own data area („Belső tárhely") outside every whole-box copy, registered as the default drive with no press, so media and data-folder apps install with no extra step; a free disk's partitions can be formatted or used one by one | installer 1.33.0, agent v0.158.1, controller v0.318.0, golden 0.318.0 | **PROVEN-LIVE** | fresh 512 GB one-disk VM on demo-hp; partitions on a scratch disk; the old-box signed job and the D4 sentence unit-proven only; an empty part asks for confirmation (R-1093) | `audits/storage-identity-buildB-2026-10-15/README.md` (2026-10-16) |
|
||||
|
||||
## E. Access, networking & household use
|
||||
|
||||
|
||||
@@ -102,6 +102,11 @@ credentials.
|
||||
- The agent and the controller **poll** the hub; the hub never initiates inbound.
|
||||
- Operator actions are delivered as **signed jobs**: the agent verifies an operator signature
|
||||
before executing, so a compromised hub database alone cannot forge commands.
|
||||
- **[FACT, 2026-10-16, R-970]** The last contradiction is gone: until controller v0.322.0 the box's own hub API key,
|
||||
sent as a Bearer token, opened `/api/config/` (read `controller.yaml` with every secret in it, and REPLACE it,
|
||||
unsigned) and the self-update and geo routes. Nothing called them (the hub never connects to a box). The config
|
||||
routes are removed and no route accepts the hub key; every `/api/` route needs a dashboard session (pinned by
|
||||
`TestR970_HubKeyOpensNoDoor` on the real route table). The box exposes no control endpoint, as stated above.
|
||||
- All operator-initiated actions are recorded in a **customer-visible audit log**.
|
||||
|
||||
---
|
||||
|
||||
@@ -477,26 +477,99 @@ does not get to vouch for a version, so an overlay can neither set nor clear it.
|
||||
**[DESIGN] Guide text is DATA.** Plain text through `html/template` — no markdown, no raw HTML — so
|
||||
a step containing a tag is shown as the characters its author typed.
|
||||
|
||||
#### The app page's layout (v0.316.0, operator-approved mockup 2026-10-15)
|
||||
#### The app page's layout (v0.319.0, operator-approved 2026-10-16, section style A)
|
||||
|
||||
**[DESIGN]** Five rules; the first three are the mockup, the last two are what they imply.
|
||||
**[DESIGN]** The mockup canvas „Felhom app oldal — egy oszlop" (Settings screen), operator-approved
|
||||
2026-10-16 with section style **A — panels**. It replaces v0.316.0's hairline sections: with the hero,
|
||||
the tab bar and every section on the same page background, the operator found the page flat — nothing
|
||||
told one section from the next.
|
||||
|
||||
1. **One column of one width** (820px; the log tab alone is full width, because log lines are long).
|
||||
Boxes of differing widths read as a page assembled by accident, which is what v0.315.0 looked like.
|
||||
2. **A section is a hairline, not a card** — `padding: 28px 0` with a bottom `--line-soft`, no
|
||||
background, no border, no radius. A card says „this is a thing you act on", and most of an app
|
||||
page is a thing you READ; a card there is a promise the content cannot keep.
|
||||
3. **A box means something, and there are exactly two.** Blue (`--blue` on `--blue-dim`) when the app
|
||||
is WAITING for the household — an after-start value; amber (`--warn` on `--warn-dim`) when
|
||||
something is RISKY, such as a backup sharing a disk with the data it protects. The
|
||||
exception-colour principle, applied to shape as well as colour. Nothing informational is boxed.
|
||||
4. **The hero is not a box**: a 64px logo tile, the name, the tagline, hairline chips (state,
|
||||
lifecycle, update) and meta text (the address, the last automatic update). The tab bar is links
|
||||
with a 2px `--blue-bright` underline under the open one — no pills, no fills.
|
||||
5. **A success is not a banner.** The automatic-update SUCCESS line is meta text in the hero. It
|
||||
asked nothing of the household, so it is a date. Everything that DOES need a decision — a failed
|
||||
update, a hold, lost files, a missing drive, an interrupted install — stays a banner above the
|
||||
tabs, which is R-571's placement rule and is unchanged.
|
||||
Every panel is the column's full width — no narrower inner card. Boxes of differing widths read as
|
||||
a page assembled by accident.
|
||||
2. **The hero is a panel** (`--bg-1`, `--line` border, 20px 24px, 16px above it; the logo tile on
|
||||
`--bg-2`). Its content is unchanged: the name, the tagline, the chips, the meta text.
|
||||
3. **The tab bar is a panel** (`--bg-1`, 4px inside, 16px above, 20px below). A tab is 10px 18px,
|
||||
15px/600 in `--text-2`; the open tab is filled `--bg-2` with a 2px `--blue-bright` underline.
|
||||
4. **Every section is a panel, ONE style on every surface** — the four tabs and the install page,
|
||||
never mixed: `--bg-1`, `--line` border, `18px 24px 22px`, 16px apart; the title 17px/700 18px from
|
||||
the top edge with a `--line-soft` rule under it. The install page's form groups (`.form-section`)
|
||||
and its progress panel are drawn by the same rule. Pinned by `TestAppPage_OneSectionStyleEverywhere`.
|
||||
5. **Colour still means something.** A panel is neutral. Blue (`--blue` on `--blue-dim`) is the app
|
||||
WAITING for the household; amber (`--warn` on `--warn-dim`) is something RISKY. The callouts keep
|
||||
their colours, inside a panel or as their own.
|
||||
6. **A success is not a banner.** The automatic-update SUCCESS line is meta text in the hero.
|
||||
Everything that DOES need a decision — a failed update, a hold, lost files, a missing drive, an
|
||||
interrupted install — stays a banner above the tabs (R-571's placement rule, unchanged).
|
||||
|
||||
**[DESIGN] „Belépés az alkalmazásba" — the login in one panel.** The settings tab shows the app's
|
||||
login name and password side by side (stacked under 720px). A login field is a `type: password`
|
||||
field plus, when the template has one, the `secret` or `text` field with the same prefix and
|
||||
`USER`/`USERNAME`/`EMAIL` (`web/app_login.go`); a password with no clean pair stands alone. Values are
|
||||
fetched on demand (R-254, R-709) — the page learns only whether one is stored.
|
||||
|
||||
**[DESIGN] The empty-value rule.** A login field the box holds no value for is a SENTENCE („A doboz
|
||||
ennél a telepítésnél nem mentett jelszót. A belépéshez azt használd, amit az alkalmazásban
|
||||
beállítottál."), never an eye: the reveal endpoint answers 404 for it, a refusal the household could
|
||||
only meet by pressing. The page and the endpoint decide „stored" with ONE function
|
||||
(`storedValuePresent`). Measured cause of the case that earned it: an app installed before its
|
||||
template gained a `type: password` field — `InjectMissingFields` never fills one
|
||||
(`audits/app-page-tuning-2026-10-16/P0`).
|
||||
|
||||
#### Logins and folders (v0.320.0, task 2026-10-16 „logins-folders")
|
||||
|
||||
**[DESIGN] A protected infra stack is part of the box, and its app page says so.** FileBrowser (and traefik,
|
||||
cloudflared, samba) has no `app.yaml`, so `Deployed` is false. Every other surface counts `Deployed || Protected` as
|
||||
on the box; v0.315.0's app page counted `Deployed` alone and showed FileBrowser „not installed", with „Telepítés" and
|
||||
without its login (measured on both demo boxes, `audits/logins-folders-2026-10-16/P0`). The app page now treats a
|
||||
protected stack as installed: settings, guide and log tabs; Restart only (as the app list); the curated name
|
||||
(`inframeta.go`); no data tab and no install page (both 302 to the page). The deploy API and `DeployStack` refuse a
|
||||
protected stack — until then the Install press would have deployed the infra compose as an app.
|
||||
|
||||
**[DESIGN] Every installed app says where its login is — ONE answer.** The settings tab's „Belépés az alkalmazásba"
|
||||
panel is on every installed app with exactly one of: the box's login fields (on demand; a field with no stored value
|
||||
is the v0.319.0 sentence) · the login the app wrote, or FileBrowser's (`InitialCreds`) · a sentence chosen by the
|
||||
catalog's `app_info.login_kind` — `first_visit` (the default: „you create the admin on the first visit; the box does
|
||||
not know the password"), `email` (a sign-in link), `none` (no sign-in; behind `family_gate` the sentence says the
|
||||
family sign-in is the login). `login_kind` is an enum, not copy: the sentence is the controller's. An infra stack with
|
||||
no login of its own (traefik, cloudflared, samba) gets no panel. Code: `web/app_login.go` `loginAnswerFor`.
|
||||
|
||||
**[DESIGN] „Új jelszó beállítása" — FileBrowser's password when the box does not know it.** In R-513's `operator`
|
||||
state (both demo boxes) the panel says „Ezt a jelszót te állítottad be a Fájlkezelőben; a doboz nem ismeri." and offers
|
||||
the press. The box uses FileBrowser's own CLI (`set -u admin,<pw>`) with the server STOPPED (the CLI opens the
|
||||
database with no lock timeout), verifies new=200 and admin/admin refused, stores the value encrypted and records
|
||||
`generated`; a failed verify leaves `operator`. Not the `FILEBROWSER_ADMIN_PASSWORD` env: it re-applies on every
|
||||
start. Spike: `audits/logins-folders-2026-10-16/P1b`. FileBrowser still asks for its own login the first time; one
|
||||
sign-in for both is R-1100.
|
||||
|
||||
**[DESIGN] A stored FileBrowser password is checked before it is offered (v0.321.0, R-1101).** In `generated`, the
|
||||
page asks FileBrowser whether the stored value still logs in; only FileBrowser's own 401/403 counts as refused (a
|
||||
FileBrowser that does not answer is not a refusal — an unproven refusal must not tell the household its password
|
||||
changed). Refused → „A Fájlkezelőben megváltoztattad a jelszót, ezért a doboz már nem ismeri." and „Új jelszó
|
||||
beállítása". The page no longer tells the household to change FileBrowser's password, and FileBrowser's own update
|
||||
notice and „Install app" prompt are switched off in its config (the box updates it; it is opened from the dashboard).
|
||||
|
||||
**[DESIGN] The Data tab's backup part is ONE section (v0.321.0).** „Biztonsági mentés": an „Éjszakai mentés" row, the
|
||||
2nd copy's state as rows, what the 2nd copy is, its form. Plain words — no „off-drive", no „PBS", no
|
||||
„DB/konfiguráció" (pinned by `TestAppData_OneBackupSection`).
|
||||
|
||||
**[DESIGN] An installed app's install address goes to its settings tab (v0.321.0, R-1095)** — 302, GET only, like the
|
||||
`/stacks/{name}/deploy` bridge.
|
||||
|
||||
**[DESIGN] „Fájlok ↗" — one of the app's folders, in FileBrowser.** The header button opens the folder with the library
|
||||
role, else the first declared folder, else (a media app) the first present drive's `media` folder; built only with
|
||||
`fileBrowserLink` (PathEscape). The data tab lists each drive's media folders the app uses (Sonarr `tv`, Radarr
|
||||
`movies`, the players both, a download client its `media_library.folder`) with NO backup sentence — the bind's class
|
||||
there means „not this app's data", which is not the class-driven „temporary, not backed up". **A folder whose drive
|
||||
is not bound into FileBrowser now is text, never a link** (`drivePresent` = the same gates the FileBrowser sync
|
||||
applies). The button never promises one click: FileBrowser's router brings a cold visitor back to the folder after its
|
||||
login (measured in the headless browser, `audits/logins-folders-2026-10-16/browser/`).
|
||||
|
||||
**[DESIGN] „Cím és kulcsok" is rows, not the install form.** The settings tab does not re-render the
|
||||
install wizard's fields: the address is the „Cím" row, the storage is a „Tárhely: <név>" row, every
|
||||
other install answer and generated key is a row (keys keep their eye). The read-only hint sits under
|
||||
the title. `/apps/{slug}/install` opened for an installed app still renders the form read-only, with
|
||||
its secret guards.
|
||||
|
||||
**[DESIGN] The stylesheet's address changes with every build.** Every shell links
|
||||
`/static/style.css?v={{.AssetTag}}`, where the tag hashes version + `GitCommit` + `BuildTime` and
|
||||
|
||||
@@ -553,6 +553,13 @@ a normal night: nothing. A box off for weeks: one catch-up when it returns; the
|
||||
running all along. An upgrade from an older release: the ledger is seeded, the first missed night after it is the
|
||||
first one made up.
|
||||
|
||||
**[FACT, 2026-10-16, R-1097] An image's own anonymous VOLUME is in no backup.** The app backup takes the named volumes the
|
||||
compose DECLARES (`backup.ResolveDockerVolumeNames`) and the drive folders it binds; a path the image keeps in its
|
||||
`VOLUME` that the compose does not mount lands in an anonymous volume and is taken by nothing. Measured with RomM's saves
|
||||
(`/romm/assets`) on demo-hp; fixed with a named volume, and catalog gate `image-volumes` keeps a measured registry of such
|
||||
paths (`audits/burndown-apps-2026-10-16/P1/`). A survey of every running container on demo-hp found no other data path
|
||||
in an anonymous volume (PrivateBin's are temp folders).
|
||||
|
||||
### 6.2 Coverage per app class — and an unresolved count
|
||||
|
||||
**[FACT]** Of 53 catalog templates, **52 keep data in Docker named volumes**; exactly **13** carry a
|
||||
@@ -940,6 +947,39 @@ moves it to a wrong class. The order is deliberate: a cause that cannot be told
|
||||
rather than being folded into a neighbour. Where the warning is SHOWN on the dashboard is a separate rule —
|
||||
`02-controller-module-map.md`, „Alert placement".
|
||||
|
||||
### 6.8 Which restic the box runs — pinned outside the OS, and re-proven (R-1105, controller v0.323.0)
|
||||
|
||||
**[DESIGN] restic is a pinned decision, never „whatever the OS ships".** Until v0.322.0 it came from the runtime's apt
|
||||
list, so a base-image move (Debian 12 → 13) would have swapped restic 0.14.0 for 0.18.0 under the off-site code with
|
||||
nobody choosing it. Since v0.323.0 the controller image carries the **official restic release binary** of one version
|
||||
(`ARG RESTIC_VERSION`, **0.19.1** since controller v0.324.0 — 0.18.1 in v0.323.0; operator decision 2A, R-1107), checked at build time against restic's signed `SHA256SUMS` (vendored release
|
||||
key, no keyserver) and against a per-architecture sum pinned in the Dockerfile; any mismatch fails the build. The
|
||||
Proxmox hosts run no restic at all (measured on all three test hosts): the agent's escrow path only stores and returns
|
||||
the repository password, and every restic call happens in the guest.
|
||||
|
||||
**Moving the version is a re-proof, not an edit.** `felhom-controller/controller/internal/backup/testdata/restic/<version>/`
|
||||
holds REAL outputs of each version the code was checked against (captured on one shared scratch repository), and
|
||||
`TestR1105_*` push them through the code's own parsers and classifiers for every version. The 0.14.0 → 0.18.1 re-proof
|
||||
(`audits/debian13-restic-2026-10-16/`):
|
||||
|
||||
| What the code relies on | 0.18.1 |
|
||||
|---|---|
|
||||
| a missing source path is skipped with a warning, exit 0 (§6.3, SP-3.4); an unreadable file exits 3 | 0.18.1 same; **0.19.1 exits 3 for a missing top-level path** (restic #4467) — the capture's stat-filter keeps such paths out of argv, so only a path vanishing in between reaches it, and that race now reads as the app's failure instead of a silent partial copy |
|
||||
| `backup --json` status lines (`percent_done`, totals, `current_files`) | same field names |
|
||||
| `snapshots --json`, `snapshots latest --tag … --json`, `stats <id>` / `--mode raw-data` | same shapes (new fields only) |
|
||||
| the §6.7 classes: wrong password, no repository, refused port / key, unreachable host, dead rclone ssh | same class each |
|
||||
| lock text (both forms), `unlock` is stale-only, `unlock --remove-all` clears | same |
|
||||
| a damaged pack reads as damage in `check` | same verdict, other wording |
|
||||
| `restore --include <unit>` layout; `--no-lock` restores from a read-only repository | same (a plain `restore` DOES lock, on both) |
|
||||
| **the `forget` plan, 7 / 4 / 6, `--group-by host,tags`** | **moved, safe direction:** while a `--keep-monthly` window has unused slots, 0.17+ also keeps each group's OLDEST snapshot — a subset of 0.14.0's removals (41-day set: 60 vs 62; 230-day set: identical). The fake-snapshot guard's verdict holds. |
|
||||
| a store written by both: 0.14.0 reads and restores a 0.18.1 snapshot after a 0.18.1 `forget --prune`; 0.18.1 restores a 0.14.0 snapshot | proven on a scratch store; on demo-hp's real store both directions are proven (byte-identical trees); 0.14.0 reading after a 0.18.1 prune there waits for the store's next weekly clean-up window |
|
||||
|
||||
Exit codes changed (10 no repository, 11 locked, 12 wrong password); nothing in the controller reads a restic exit code.
|
||||
The 0.19.1 re-proof (2026-10-16 night): its notes read for every row above; fixtures for 0.14.0, 0.18.1 and 0.19.1
|
||||
recaptured from one shared store; `TestR1105_*` runs each version against 0.14.0 — same classes, same JSON, the same
|
||||
forget plan as 0.18.1. The 0.18.1 binary (Go 1.25.1) carried 50 fixable scanner findings; 0.19.1 (Go 1.26.4) is the
|
||||
reason for the move (`audits/night-restore-2026-10-17/P2/`).
|
||||
|
||||
## 7. The recovery chain (D3) — the reason this document exists
|
||||
|
||||
**[DESIGN] 3-2-1 describes copies. It does not describe recovery.**
|
||||
@@ -1115,6 +1155,28 @@ password would have failed. Separately, Grafana's `type: password` admin login w
|
||||
the sentinel value was live in the container and encrypted in the guest, and appeared in **0 files**
|
||||
anywhere under the backup namespace. Full record: `audits/D5-drive-alone-restore-2026-07-30.md`.
|
||||
|
||||
**[DESIGN — operator decision 1A, 2026-10-16 21:57; built in controller v0.324.0 — R-1106, R-127 (b)] A restored
|
||||
database comes back with the password it was saved with.** A database engine reads `POSTGRES_PASSWORD`,
|
||||
`MARIADB_ROOT_PASSWORD` and the like only when it initialises an EMPTY data directory; a restored data directory opens
|
||||
with its own password only. So when a restore puts back an app's database, the database credentials the data was
|
||||
saved with are written into the app's settings before the app starts, and they REPLACE whatever the current install
|
||||
holds. The credentials are the app.yaml variables that feed a database service's `*_PASSWORD` keys in the compose
|
||||
that will run (`appbackup.DBCredentialVars`; 17 catalog apps, 20 fields, every one `type: secret` — the catalog gate
|
||||
`db-credentials` refuses a template where that stops being true). Per route:
|
||||
|
||||
| Route | Where the database password comes from |
|
||||
|---|---|
|
||||
| On-box restore („Visszaállítás", `POST /backup/restore`), the update's rollback, „use my kept data", an off-site restore at the snapshot's version | the unit (unit-wins, `unitRestoreEnv`) |
|
||||
| Off-site full restore that keeps the app's definition (same version — the reinstall case) | the snapshot's unit, written by `stacks.SetStackSecretValues` before the database starts — **only when the snapshot replays the database's data volume**; a dump-only snapshot replays into the current data directory and keeps the current password |
|
||||
| `.fab` import | the bundle's own app.yaml (it carries the whole env) — unchanged |
|
||||
|
||||
**A restore whose unit does not carry the password is REFUSED before anything changes** — unless the current install
|
||||
is OLDER than the data (then the current value is the one the data was written with). The household reads „Ezt a
|
||||
mentést nem lehet visszaállítani: nem tartalmazza az adatbázis jelszavát, és nélküle az alkalmazás nem tudna elindulni.
|
||||
A mostani telepítés érintetlen maradt." **A database password is never generated for restored data** — O4 no longer
|
||||
reaches these names. On the reinstall path the reinstall's `type: password` admin logins are recorded as restored-login
|
||||
fields (R-694's rule — the page says to use the old password; R-926).
|
||||
|
||||
### 7.2 A tier whose prerequisites cannot be met in the failure it exists for
|
||||
|
||||
**[FACT]** Two instances, both current:
|
||||
|
||||
@@ -43,6 +43,24 @@ this closes the half that let it happen.
|
||||
number. A box already on the bad image is moved by the normal update path — never by re-tagging,
|
||||
which would be the same defect wearing the fix's clothes.
|
||||
|
||||
**[DESIGN] A withdrawn build is never selected (hub v0.155.0, R-1081, R-1085).** The rule above
|
||||
leaves the spoiled numbers in the registry, so nothing may pick them. Two controller tags hold
|
||||
other code than their number says: **`0.315.0`** (re-pushed with v0.316.0's code under it) and
|
||||
**`0.316.3`** (built from a working copy holding another session's uncommitted files). The
|
||||
household's button and the daily auto-update take the HIGHEST tag and cannot reach them; the
|
||||
**floor** targets a version exactly, so the floor is where the guard lives, in two halves:
|
||||
- **at input** — both floor forms (global and per customer, by session or by token) and the
|
||||
confirm dialog's impact probe refuse a listed tag (`flash=floor_spoiled`; the probe answers
|
||||
`valid:false, spoiled:true`). The check is its own, NOT inside `normalizeFloorInput`, which also
|
||||
validates agent and golden numbers;
|
||||
- **at serve time** — `store.ResolveManagedFloor` HOLDS a listed floor (served as none,
|
||||
`HeldSpoiled`, its own reason text) however it arrived: a value stored before the refusal, or the
|
||||
config/env default (which no form checks; the hub also warns about it at start). The stored
|
||||
value is not rewritten; the Configuration and customer pages say „withdrawn".
|
||||
The list is `hub/internal/store/spoiled_tags.go`; the hand deploy reads the same list as a marked
|
||||
line in the `felhom-build-deploy` skill, and a test fails when the two differ. A new spoiled
|
||||
build is added to both in the same commit.
|
||||
|
||||
## 1. How an update works today, as measured
|
||||
|
||||
### 1.1 The button
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
> whether two storage places sit on the same physical disk." Triggered by the OMV test (`audits/omv-vm-2026-10-15/`,
|
||||
> R-1068). Spike: `audits/storage-identity-2026-10-15/` (Part B there). Marks as in `01`: **[FACT]** read in a named
|
||||
> file or measured; **[DESIGN]** a proposal. **Status: ACCEPTED 2026-10-15** — the operator ruled D1–D6 as recommended (§12). **Build A shipped 2026-10-15**
|
||||
> (§3, §4, §5's D4 sentence, §8, §10); **§5's internal place, §6 and §7 wait for build B** — what is built, and where the
|
||||
> build differs from this text, is §13.
|
||||
> (§3, §4, §5's D4 sentence, §8, §10); **build B shipped 2026-10-16** (§5's internal place, §6, §7, §9) — what is built,
|
||||
> and where the build differs from this text, is §13.
|
||||
> Paths: A = `felhom-agent/`, C = `felhom-controller/controller/`, E = `felhom.eu/`.
|
||||
|
||||
## 1. Today, in one table (Part A)
|
||||
@@ -216,5 +216,25 @@ fields) · website and guides (the OMV guide's second-disk line; the backup expl
|
||||
- An older agent (no same-disk call): the controller keeps the pre-build `st_dev` rule and logs it once.
|
||||
- §11 rejects `sys_drive` for household data, but the drives page still offers `/mnt/sys_drive` to attach (R-280) — R-1082.
|
||||
|
||||
**Build B — waits:** §5's internal place, §6 partitions, §7 the installer (and the D3 space check the operator asked for).
|
||||
**Build B — the internal place, partitions, the installer (2026-10-16)** (agent v0.158.1 — v0.158.0 superseded; controller
|
||||
v0.318.0; installer 1.33.0; evidence `audits/storage-identity-buildB-2026-10-15/`; the D3 space check: its `SPACE-CHECK.md`,
|
||||
sizes ruled above in §7):
|
||||
|
||||
| Section | Built | Where it differs from the text above |
|
||||
|---|---|---|
|
||||
| §5 internal place | the root helper `felhom-internal-place` (bundle) makes `pve/felhom-belso` by the ruled rule; the installer's step 5b (appliance only) and the signed `add_internal_place` (an old box) run it; the agent mounts it at `/mnt/belso` like a drive (kind `internal`); the controller binds and registers it as the default drive | the place is made only in **appliance** mode (a byo host's pool is the owner's); every failing read in the helper refuses (security review of 0.158.0) |
|
||||
| §6 partitions | each part of a free disk is listed (empty / filesystem / in_use); „format this part", „use this part"; a data-bearing part's wipe id binds disk id + part number + fs-UUID | the „filesystem with no files" state is folded into `filesystem` (no root read-only mount; the typed confirmation covers both). **A part can be formatted only while nothing on its disk is mounted**: the root mkfs wrapper refuses any mounted node and deliberately trusts no „it is Felhom's own drive" claim (the agent could fake it — security review of 0.158.0). So a household formats its free parts before using any part; a free part next to an in-use drive can still be USED, not formatted |
|
||||
| §7 installer | `step_grows` by the ruled table; step 5b | the host root is left as Proxmox makes it (SPACE-CHECK §3) |
|
||||
| §9 old boxes | the signed `add_internal_place` (no parameters; the size is the rule's) | — |
|
||||
|
||||
**Proven live 2026-10-16** (`audits/storage-identity-buildB-2026-10-15/README.md`): a fresh one-disk install (512 GB VM,
|
||||
the public installer 1.33.0, agent 0.158.1 and golden 0.318.0 vouched) → data volume 100 GiB, internal place 213 GiB,
|
||||
registered as the default drive with no press; Jellyfin installed with no extra step onto it; a whole-box copy holds nothing
|
||||
of it; partitions formatted and used through the wizard; the honest backup offer on a VM. **Not as written:** an empty part
|
||||
asks for the typed confirmation (the probe reads the parent's partition table — R-1093); a 200 GB disk gets no internal
|
||||
place under the ruled sizes (15 GiB < 20).
|
||||
|
||||
Also in build B: R-1084 (the whole-box backup offer asks the same-disk question) and R-1082 (`/mnt/sys_drive` is never
|
||||
offered to attach, by device). A bundle that adds a path needs a step bundle on boxes with an older wrapper (R16, R-880):
|
||||
0.158.1 was delivered through `0.158.1-step1`.
|
||||
|
||||
|
||||
@@ -268,3 +268,29 @@ the installer (R-1079).
|
||||
|
||||
---
|
||||
|
||||
<a id="d15"></a>
|
||||
## D15 — The one-disk box's own data area; partitions as places
|
||||
|
||||
*Map row: [D15](../00-capability-map.md#d15) · added 2026-10-16.*
|
||||
|
||||
**Scenario:** a household with one disk installs a media app and expects it to work with no extra step — and expects its
|
||||
films not to fill every whole-box copy. A household with a split disk wants to use the free part without wiping the rest.
|
||||
|
||||
**Components:** installer 1.33.0 (step 3 sizes the data volume by the ruled table; step 5b runs the bundle's root helper
|
||||
`felhom-internal-place`), agent v0.158.1 (mounts the place like a drive, kind `internal`; lists partitions; the signed
|
||||
`add_internal_place` for an old box), controller v0.318.0 (binds and registers it as the default drive; the partition
|
||||
wizard). Design: `architecture/16-storage-physical-identity.md` §5–§7, §9, §13; sizes: `audits/storage-identity-buildB-2026-10-15/SPACE-CHECK.md`.
|
||||
|
||||
**Status:** **PROVEN-LIVE** (2026-10-16) on a fresh 512 GB one-disk VM from the public ISO and installer: data volume 100 GiB,
|
||||
data area 213 GiB, registered as the default drive with no press, Jellyfin installed with no extra step onto it, a whole-box
|
||||
copy holds nothing of it; partitions formatted and used through the wizard; formatting the whole disk under a used part
|
||||
refused in words.
|
||||
|
||||
**Unit only:** the old-box signed job (no box with ≥ 20 GiB free thin space was at hand), the D4 sentence (R-1068's other
|
||||
half). **Known gaps:** an empty part asks for the typed confirmation (R-1093); the whole-disk refusal sentence (R-1094); a
|
||||
part next to a mounted part can only be used, not formatted (the root wrapper trusts no „Felhom's own" claim, `16` §13).
|
||||
|
||||
**Evidence:** `audits/storage-identity-buildB-2026-10-15/README.md`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ Inside, from bottom to top:
|
||||
- the **system disk** (Proxmox + the guest),
|
||||
- **data drive 1** (household data and the **Tier-1** backup folder),
|
||||
- an optional **data drive 2** (the **Tier-2** copy).
|
||||
- on a **one-disk box**, the **data area** („Belső tárhely", installer 1.33.0) — a part of the system disk the box uses as
|
||||
data drive 1; it is **not** in the whole-guest backups, and a copy between it and the system data is never counted as a
|
||||
second copy (it is the same disk).
|
||||
Note on the drawing: the data drives are attached to the guest from the host; the **whole-guest backups do not include the data drives** (that is what the per-app tiers are for).
|
||||
|
||||
Arrows out of the box:
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
# Rendering check of the Plex and Sonarr guides on demo-hp — 2026-10-16 07:20 UTC (morning after the night)
|
||||
# Owed from the night (R-1088: the credentials file was overwritten; the operator restored it from backup).
|
||||
# Method: endpoint level, no browser on DooPlex. Public address https://felhom.enkisfelhom.hu (demo-hp guest 9201,
|
||||
# controller 0.317.0 per the page footer). Password read file->file by scripts/read_credential.py, never printed,
|
||||
# shredded after use. POST /login -> 302 + felhom_session. Pages: /apps/plex/guide, /apps/sonarr/guide (Sonarr is
|
||||
# not installed: 302 to /apps/sonarr, which is the guide view), each also with ?lang=en (no setting changed).
|
||||
# Read only: nothing installed, removed, restarted or set. Logged out after (POST /logout 302; the cookie then 302s to login).
|
||||
# Catalog on main: ec864b5; its plex/sonarr guide equals A/plex-draft.json and A/sonarr-draft.json (hu, en, checked_version).
|
||||
|
||||
## 1. Marker count (spec: assert the data-guide-section count)
|
||||
plex: page lang=hu data-guide-section=4 (want 4) data-guide-faq=1 FAQ entries=6 (want 6) checked=['1.41.4.9463-630c9f557'] (want 1.41.4.9463-630c9f557) titles match draft[hu]=True -> PASS
|
||||
sonarr: page lang=hu data-guide-section=4 (want 4) data-guide-faq=1 FAQ entries=6 (want 6) checked=['4.0.20'] (want 4.0.20) titles match draft[hu]=True -> PASS
|
||||
plex: page lang=en data-guide-section=4 (want 4) data-guide-faq=1 FAQ entries=6 (want 6) checked=['1.41.4.9463-630c9f557'] (want 1.41.4.9463-630c9f557) titles match draft[en]=True -> PASS
|
||||
sonarr: page lang=en data-guide-section=4 (want 4) data-guide-faq=1 FAQ entries=6 (want 6) checked=['4.0.20'] (want 4.0.20) titles match draft[en]=True -> PASS
|
||||
|
||||
## 2. Every rendered string vs the catalog text (titles, intros, steps, FAQ q/a)
|
||||
plex hu: strings on page 33, in catalog 33, mismatched 0, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
sonarr hu: strings on page 35, in catalog 35, mismatched 0, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
plex en: strings on page 33, in catalog 33, mismatched 0, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
sonarr en: strings on page 35, in catalog 35, mismatched 0, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
|
||||
## 3. Negative control: the same compare with one step and one FAQ answer changed in memory
|
||||
plex hu: strings on page 33, in catalog 33, mismatched 2, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
sonarr hu: strings on page 35, in catalog 35, mismatched 2, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
plex en: strings on page 33, in catalog 33, mismatched 2, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
sonarr en: strings on page 35, in catalog 35, mismatched 2, <label>.DOMAIN filled 0, unfilled DOMAIN left 0
|
||||
|
||||
## The guide block as served: plex.html
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="1.41.4.9463-630c9f557">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Nézés tévén vagy telefonon</h3>
|
||||
<p>A Plex appok a Plex fiókodon keresztül találják meg a szerveredet.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Nyisd meg a Plex appot, és jelentkezz be ugyanazzal a Plex fiókkal, amellyel a kódot kérted.</li><li>A „Kezdőképernyő” oldalon megjelennek a könyvtáraid.</li><li>Válassz egy filmet, és kattints a „Lejátszás” gombra.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Új filmek és új meghajtó</h3>
|
||||
<p>Amit a meghajtód media mappájába teszel, azt a Plex magától észreveszi.</p>
|
||||
|
||||
<ol class="app-info-list"><li>A Radarr a media/movies, a Sonarr a media/tv mappába tesz mindent, a Plex innen olvas.</li><li>Új meghajtónál a doboz hozzáadja a meghajtó mappáit a meglévő film- és sorozatkönyvtáraidhoz, és elindít egy beolvasást.</li><li>Ha egy új fájl mégsem jelenik meg, a könyvtár menüjében kattints a „Könyvtárfájlok beolvasása” pontra.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Rossz cím vagy borító javítása</h3>
|
||||
<p>A Plex a fájl és a mappa nevéből találja ki, melyik film ez.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Nyisd meg a filmet, és kattints a „Több” menüre.</li><li>Válaszd a „Találatok pontosítása” pontot, és keresd meg a helyes címet.</li><li>Legközelebb segít, ha a mappa neve a film címe és éve, például: Nosferatu (1922).</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Megosztás a családdal</h3>
|
||||
<p>Minden családtag a saját Plex fiókjával nézhet.</p>
|
||||
|
||||
<ol class="app-info-list"><li>A „Beállítások” oldalon nyisd meg a „Könyvtár hozzáférés kezelése” részt.</li><li>Válaszd ki, kivel és mely könyvtárakat osztod meg.</li><li>A családtag a saját Plex fiókjával lép be az appba.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<h3>Gyakori kérdések</h3>
|
||||
<dl class="app-faq">
|
||||
<dt>Miért nem látom a filmet?</dt>
|
||||
<dd>Nézd meg, hogy a fájl a media/movies mappában van-e, és hogy a mappa neve a film címe és éve. Utána kattints a „Könyvtárfájlok beolvasása” pontra.</dd>
|
||||
<dt>Törölhetek filmet a Plexből?</dt>
|
||||
<dd>Nem: a Plex csak olvassa a meghajtót. Törölni a fájlböngészőben, a Radarrban vagy a Sonarrban tudsz.</dd>
|
||||
<dt>Kell Plex Pass?</dt>
|
||||
<dd>Otthoni nézéshez nem. Néhány funkcióhoz igen, például a bevezető átugrásához; ezt a Plex dönti el, nem a doboz.</dd>
|
||||
<dt>Akadozik a lejátszás. Mit tehetek?</dt>
|
||||
<dd>A Plex ilyenkor átalakítja a videót, és ez a dobozon lassú lehet. Az appban a „Videó minőség” beállításnál válaszd az „Eredeti” értéket.</dd>
|
||||
<dt>Elérem otthonon kívül?</dt>
|
||||
<dd>A doboz beállította, hogy a Plex a saját címeden is elérhető legyen. Otthonon kívüli nézéshez a Plex előfizetést kérhet; ezt a Plex szabja meg.</dd>
|
||||
<dt>Lejárt a kód. Hol kérek újat?</dt>
|
||||
<dd>Ugyanott, ahol az elsőt: a címet az első lépések között találod. Az új kódot a vezérlőpulton, a Plex „Beállítások” fülén add meg.</dd>
|
||||
</dl>
|
||||
|
||||
## The guide block as served: sonarr.html
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="4.0.20">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Első sorozat hozzáadása</h3>
|
||||
<p>A Sonarr figyeli a sorozatot, és letölti az új részeket, amint megjelennek.</p>
|
||||
|
||||
<ol class="app-info-list"><li>A bal oldali menüben a „Series” (Sorozatok) alatt kattints az „Add New” (Új hozzáadása) pontra.</li><li>Írd be a sorozat címét, és kattints a megfelelő találatra.</li><li>A „Root Folder” (gyökérmappa) mezőben válaszd annak a meghajtónak a media/tv mappáját, ahová a sorozatot szeretnéd.</li><li>A „Tags” (címkék) mezőbe írd ugyanannak a meghajtónak a címkéjét, például hdd-1. Címke nélkül a sorozat nem kap letöltőt.</li><li>Hagyd bejelölve a „Start search for missing episodes” (hiányzó részek keresése) jelölőt, és add hozzá a sorozatot.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>A letöltés követése</h3>
|
||||
<p>A letöltés a háttérben fut, az oldalt be is zárhatod.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Az „Activity” (Tevékenység) menü „Queue” (Sor) lapján látod, mi töltődik éppen.</li><li>Ha egy rész letöltődött, a Sonarr magától átteszi a sorozat mappájába. A Plex és a Jellyfin innen látja.</li><li>A „Calendar” (Naptár) mutatja, mikor jön a következő rész.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Hiányzó részek keresése</h3>
|
||||
<p>Ha egy régebbi évad hiányzik, egyszerre kerestetheted.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Nyisd meg a „Wanted” (Keresett) menü „Missing” (Hiányzó) lapját.</li><li>Jelöld be a kívánt részeket, és kattints a „Search Selected” (kijelöltek keresése) gombra.</li><li>Ha mindet keresnéd, használd a „Search All” (összes keresése) gombot.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Ha valami nem működik</h3>
|
||||
<p>A Sonarr maga jelzi, ha valami hibás.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Nézd meg a „System” (Rendszer) menü „Status” (Állapot) lapját: a „Health” (állapot) rész sorolja a hibákat.</li><li>A „Settings” (Beállítások) menü „Download Clients” (letöltő kliensek) oldalán látod a letöltőket, amelyeket a doboz bekötött.</li><li>Ha nincs találat, nézd meg a Prowlarr „Indexers” (indexerek) oldalát: legalább egy forrás kell.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<h3>Gyakori kérdések</h3>
|
||||
<dl class="app-faq">
|
||||
<dt>Honnan tölt le a Sonarr?</dt>
|
||||
<dd>Azokról a forrásokról (indexerekről), amelyeket te adsz meg a Prowlarrban. A doboz összeköti a kettőt, de forrást nem ad és nem ajánl: ez a te döntésed.</dd>
|
||||
<dt>Be kell állítanom a letöltő klienst és a mappákat?</dt>
|
||||
<dd>Nem. A doboz minden meghajtódhoz bekötötte a telepített letöltőket (qBittorrent, Transmission), a gyökérmappát és a címkét. Neked csak a sorozatot kell hozzáadnod.</dd>
|
||||
<dt>Miért van ugyanaz a fájl a torrents és a tv mappában is?</dt>
|
||||
<dd>Nem foglal dupla helyet: ugyanaz a fájl két néven (hardlink). A letöltő tovább oszthatja, a könyvtárad pedig rendezett marad.</dd>
|
||||
<dt>Letöltötte, de nem került a könyvtárba. Miért?</dt>
|
||||
<dd>Ha a meghajtón kevesebb mint 10 GB maradna, a Sonarr nem importál. Ezt a doboz állította be: „Settings”, „Media Management”, „Minimum Free Space” (legkevesebb szabad hely). Szabadíts fel helyet, és a következő ellenőrzéskor folytatja.</dd>
|
||||
<dt>Hol nézhetem meg a sorozatokat?</dt>
|
||||
<dd>A Plexben vagy a Jellyfinben. A Sonarr csak letölt és rendez, lejátszani nem tud.</dd>
|
||||
<dt>Mit tölthetek le?</dt>
|
||||
<dd>Csak olyan tartalmat, amihez jogod van, például szabad licencű műsorokat.</dd>
|
||||
</dl>
|
||||
|
||||
## The guide block as served: en/plex.html
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="1.41.4.9463-630c9f557">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Watch on a TV or a phone</h3>
|
||||
<p>The Plex apps find your server through your Plex account.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Open the Plex app and sign in with the same Plex account you got the code with.</li><li>Your libraries appear on the „Home” page.</li><li>Choose a film and select „Play”.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>New films and a new drive</h3>
|
||||
<p>Whatever you put into your drive's media folder, Plex notices by itself.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Radarr puts everything into media/movies and Sonarr into media/tv; Plex reads from there.</li><li>With a new drive, the box adds its folders to your existing film and series libraries and starts a scan.</li><li>If a new file still does not show up, select „Scan Library Files” in the library's menu.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Fix a wrong title or cover</h3>
|
||||
<p>Plex works out which film it is from the names of the file and the folder.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Open the film and select the „More” menu.</li><li>Choose „Fix Match” and search for the right title.</li><li>Next time it helps to name the folder after the film's title and year, for example: Nosferatu (1922).</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Share with the family</h3>
|
||||
<p>Every family member can watch with their own Plex account.</p>
|
||||
|
||||
<ol class="app-info-list"><li>On the „Settings” page, open „Manage Library Access”.</li><li>Choose who gets which libraries.</li><li>The family member signs in to the app with their own Plex account.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<h3>Frequently asked questions</h3>
|
||||
<dl class="app-faq">
|
||||
<dt>Why can I not see the film?</dt>
|
||||
<dd>Check that the file is in the media/movies folder and that the folder is named after the film's title and year. Then select „Scan Library Files”.</dd>
|
||||
<dt>Can I delete a film from Plex?</dt>
|
||||
<dd>No: Plex only reads the drive. You delete in the file browser, in Radarr or in Sonarr.</dd>
|
||||
<dt>Do I need Plex Pass?</dt>
|
||||
<dd>Not for watching at home. Some features need it, for example skipping intros; Plex decides that, not the box.</dd>
|
||||
<dt>Playback stutters. What can I do?</dt>
|
||||
<dd>Plex is converting the video, and that can be slow on the box. In the app, set „Video Quality” to „Original”.</dd>
|
||||
<dt>Can I reach it away from home?</dt>
|
||||
<dd>The box set Plex up to answer on your own address. To watch away from home, Plex may ask for a subscription; Plex sets that rule.</dd>
|
||||
<dt>The code ran out. Where do I get a new one?</dt>
|
||||
<dd>In the same place as the first one: its address is in the first steps. Enter the new code on the dashboard, on Plex's „Settings” tab.</dd>
|
||||
</dl>
|
||||
|
||||
## The guide block as served: en/sonarr.html
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="4.0.20">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Add your first series</h3>
|
||||
<p>Sonarr watches a series and downloads each new episode as it comes out.</p>
|
||||
|
||||
<ol class="app-info-list"><li>In the menu on the left, under „Series”, select „Add New”.</li><li>Type the title of the series and select the right result.</li><li>In „Root Folder”, choose the media/tv folder of the drive you want the series on.</li><li>In „Tags”, type the tag of that same drive, for example hdd-1. Without a tag the series gets no download client.</li><li>Leave „Start search for missing episodes” ticked and add the series.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Follow a download</h3>
|
||||
<p>Downloads run in the background, so you can close the page.</p>
|
||||
|
||||
<ol class="app-info-list"><li>The „Queue” tab of the „Activity” menu shows what is downloading now.</li><li>When an episode is done, Sonarr moves it into the series folder by itself. Plex and Jellyfin see it there.</li><li>The „Calendar” shows when the next episode is due.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>Search for missing episodes</h3>
|
||||
<p>If an older season is missing, you can search for it in one go.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Open the „Missing” tab of the „Wanted” menu.</li><li>Tick the episodes you want and select „Search Selected”.</li><li>To search for all of them, use „Search All”.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>When something does not work</h3>
|
||||
<p>Sonarr tells you itself when something is wrong.</p>
|
||||
|
||||
<ol class="app-info-list"><li>Look at the „Status” tab of the „System” menu: the „Health” part lists the problems.</li><li>The „Download Clients” page of the „Settings” menu shows the download clients the box connected.</li><li>If nothing is found, look at the „Indexers” page in Prowlarr: you need at least one source.</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<h3>Frequently asked questions</h3>
|
||||
<dl class="app-faq">
|
||||
<dt>Where does Sonarr download from?</dt>
|
||||
<dd>From the sources (indexers) you add in Prowlarr. The box connects the two, but it neither provides nor suggests a source: that is your choice.</dd>
|
||||
<dt>Do I have to set up the download client and the folders?</dt>
|
||||
<dd>No. For every drive, the box connected the installed download clients (qBittorrent, Transmission), the root folder and the tag. You only add the series.</dd>
|
||||
<dt>Why is the same file in both the torrents and the tv folder?</dt>
|
||||
<dd>It does not take double space: it is one file with two names (a hardlink). The download client can keep sharing it, and your library stays tidy.</dd>
|
||||
<dt>It downloaded, but it is not in the library. Why?</dt>
|
||||
<dd>Sonarr does not import if less than 10 GB would be left on the drive. The box set this: „Settings”, „Media Management”, „Minimum Free Space”. Free up some space and it goes on at the next check.</dd>
|
||||
<dt>Where do I watch the series?</dt>
|
||||
<dd>In Plex or Jellyfin. Sonarr only downloads and files; it cannot play anything.</dd>
|
||||
<dt>What may I download?</dt>
|
||||
<dd>Only content you have the right to, for example shows under a free licence.</dd>
|
||||
</dl>
|
||||
@@ -0,0 +1,57 @@
|
||||
# Part 0 — why demo-hp's Calibre-Web has no stored password (2026-10-16, read only, no value printed)
|
||||
|
||||
Method: SSH to demo-hp (`ssh hp`) → `pct exec 9201`; the controller's own reveal endpoint
|
||||
(`POST /stacks/calibre-web/auto-field/reveal`, the one the eye button calls) through traefik inside the guest;
|
||||
Calibre-Web's own user table (`docker exec calibre-web sqlite3 /config/app.db 'select name from user'`), compared
|
||||
inside the guest. The hub's per-app page (`/apps/calibre-web`) with the CC token; then the box itself
|
||||
(demo-felhom, `ssh felhom-pve`) as the second channel.
|
||||
|
||||
## 1. Keys in calibre-web's app.yaml (9201), names only
|
||||
|
||||
```
|
||||
env: ADMIN_USER DOMAIN HDD_PATH SUBDOMAIN <- no ADMIN_PASSWORD key at all
|
||||
locked_fields: DOMAIN SUBDOMAIN HDD_PATH ADMIN_USER
|
||||
deployed_at: 2026-08-21T16:27:08Z
|
||||
default_login: changed_at 2026-09-29T07:21:06Z by household
|
||||
app.yaml mtime: 2026-10-01 11:26:10Z <- InjectMissingFields wrote ADMIN_USER here
|
||||
reveal ADMIN_PASSWORD -> 404, ok false, no value
|
||||
reveal ADMIN_USER -> 200, value present
|
||||
```
|
||||
|
||||
## 2. Install date vs the template
|
||||
|
||||
| what | when |
|
||||
|---|---|
|
||||
| calibre-web installed on 9201 | 2026-08-21 16:27Z |
|
||||
| `ADMIN_PASSWORD` (`type: password`) enters the template | catalog `d0e7e2e`, 2026-09-29 07:19Z |
|
||||
| `ADMIN_USER` (`type: secret`, `generate: hex:5`) enters the template | catalog `e9f50b5`, 2026-10-01 11:19Z |
|
||||
| the box injects `ADMIN_USER` into app.yaml | 2026-10-01 11:26:10Z (app.yaml mtime; A3 of `audits/calibre-name-and-prune-2026-10-01`) |
|
||||
| R-757 guard (no invented after_install-only secret) | controller `1991c74`, v0.298.0, 2026-10-06 |
|
||||
|
||||
`InjectMissingFields` (`internal/stacks/deploy.go`) fills only `secret`, `domain` and `subdomain`; a new
|
||||
`type: password` field falls to the `default:` branch (`requires manual configuration`) and is never stored. So the
|
||||
**hypothesis holds for the password**: installed before the field existed, never filled, and after_install (which sets
|
||||
it in the app) never ran for this install.
|
||||
|
||||
## 3. Is the stored ADMIN_USER invented?
|
||||
|
||||
```
|
||||
calibre users count 2 admin present False
|
||||
equal (stored ADMIN_USER is a Calibre-Web user): True
|
||||
```
|
||||
|
||||
**No — on demo-hp the name is NOT invented any more.** On 2026-10-01 11:27Z a session renamed Calibre-Web's admin
|
||||
user by hand to the name the box had just recorded (`audits/calibre-name-and-prune-2026-10-01/A/A2-demo-hp-rename.txt`,
|
||||
second block). The name the page shows is the real login name. **So the task's "remove the invented value from
|
||||
app.yaml on 9201" does not apply: removing it would hide a TRUE login name.** Nothing was removed.
|
||||
The password that goes with it was set by hand in the app; the operator holds it out-of-band.
|
||||
|
||||
## 4. Other boxes (hub, then the box)
|
||||
|
||||
The hub's `/apps/calibre-web` says „Deployments 2", affected customers demo-felhom and demo-hp (telemetry). Tester 1
|
||||
and Tester 2 are not listed. The second channel — the box: demo-felhom guest 9201 has `/opt/docker/stacks/calibre-web/`
|
||||
with only `.felhom.yml` + `docker-compose.yml` (catalog mirror), **no app.yaml, no calibre-web container** — it is not
|
||||
installed there now. **No customer box has this state. No register row for customer boxes.**
|
||||
|
||||
(The hub's „Deployments 2" counts telemetry history, not what is installed today — a presence-not-success reading;
|
||||
the box is the truth. NOT-A-FINDING for this task: the hub page labels it telemetry.)
|
||||
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 470 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 96 KiB |
@@ -0,0 +1,239 @@
|
||||
after login url: /launcher
|
||||
{
|
||||
"settings": {
|
||||
"path": "/apps/calibre-web/settings",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 3,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16,
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": true,
|
||||
"loginFields": [
|
||||
"ADMIN_USER",
|
||||
"ADMIN_PASSWORD"
|
||||
],
|
||||
"notStored": [
|
||||
"ADMIN_PASSWORD"
|
||||
],
|
||||
"reveals": [
|
||||
"ADMIN_USER"
|
||||
],
|
||||
"loginPairCols": "377px 377px",
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": null,
|
||||
"console": []
|
||||
},
|
||||
"data": {
|
||||
"path": "/apps/calibre-web/data",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 4,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": null,
|
||||
"console": []
|
||||
},
|
||||
"guide": {
|
||||
"path": "/apps/calibre-web/guide",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 10,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": null,
|
||||
"console": [
|
||||
[
|
||||
"log-error",
|
||||
"Failed to load resource: the server responded with a status of 404 ()",
|
||||
"https://felhom.enkisfelhom.hu/static/assets/calibre-web-screenshot-3.webp"
|
||||
]
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
"path": "/apps/calibre-web/logs",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 1,
|
||||
"sectionWidths": [
|
||||
966
|
||||
],
|
||||
"columnWidth": 966,
|
||||
"sectionSpacing": [],
|
||||
"titleTopGap": null,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": 916,
|
||||
"console": []
|
||||
},
|
||||
"settings_390px": {
|
||||
"loginPairCols": "324px",
|
||||
"scrollWidth": 390
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
after login url: /launcher
|
||||
{
|
||||
"install": {
|
||||
"path": "/apps/grocy/install",
|
||||
"activeTab": null,
|
||||
"tabBar": null,
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 2,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": true,
|
||||
"deployForm": true,
|
||||
"logBoxWidth": null,
|
||||
"console": []
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 412 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 99 KiB |
@@ -0,0 +1,231 @@
|
||||
after login url: /launcher
|
||||
{
|
||||
"settings": {
|
||||
"path": "/apps/paperless-ngx/settings",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 3,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16,
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": true,
|
||||
"loginFields": [
|
||||
"PAPERLESS_ADMIN_USER",
|
||||
"PAPERLESS_ADMIN_PASSWORD"
|
||||
],
|
||||
"notStored": [],
|
||||
"reveals": [
|
||||
"PAPERLESS_ADMIN_PASSWORD"
|
||||
],
|
||||
"loginPairCols": "377px 377px",
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": null,
|
||||
"console": []
|
||||
},
|
||||
"data": {
|
||||
"path": "/apps/paperless-ngx/data",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 4,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": null,
|
||||
"console": []
|
||||
},
|
||||
"guide": {
|
||||
"path": "/apps/paperless-ngx/guide",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 10,
|
||||
"sectionWidths": [
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820,
|
||||
820
|
||||
],
|
||||
"columnWidth": 820,
|
||||
"sectionSpacing": [
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"titleTopGap": 19,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": null,
|
||||
"console": []
|
||||
},
|
||||
"logs": {
|
||||
"path": "/apps/paperless-ngx/logs",
|
||||
"activeTab": {
|
||||
"background-color": "rgb(22, 38, 63)",
|
||||
"color": "rgb(237, 242, 249)",
|
||||
"border-bottom-color": "rgb(46, 168, 245)",
|
||||
"border-bottom-width": "2px",
|
||||
"padding-top": "10px",
|
||||
"padding-left": "18px"
|
||||
},
|
||||
"tabBar": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "4px"
|
||||
},
|
||||
"hero": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"padding-top": "20px",
|
||||
"padding-left": "24px"
|
||||
},
|
||||
"section": {
|
||||
"background-color": "rgb(15, 27, 46)",
|
||||
"border-top-color": "rgb(34, 52, 79)",
|
||||
"border-top-width": "1px",
|
||||
"padding-top": "18px",
|
||||
"padding-left": "24px",
|
||||
"margin-bottom": "16px",
|
||||
"border-radius": "2px"
|
||||
},
|
||||
"visibleSections": 1,
|
||||
"sectionWidths": [
|
||||
966
|
||||
],
|
||||
"columnWidth": 966,
|
||||
"sectionSpacing": [],
|
||||
"titleTopGap": null,
|
||||
"loginPanel": false,
|
||||
"loginFields": [],
|
||||
"notStored": [],
|
||||
"reveals": [],
|
||||
"loginPairCols": null,
|
||||
"domainInput": false,
|
||||
"deployForm": false,
|
||||
"logBoxWidth": 916,
|
||||
"console": []
|
||||
},
|
||||
"settings_390px": {
|
||||
"loginPairCols": "324px",
|
||||
"scrollWidth": 390
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
## TestAppPage_LoginPairPanel — mutation on internal/web/app_login.go
|
||||
=== RUN TestAppPage_LoginPairPanel
|
||||
app_page_tuning_test.go:171: the login panel must hold BOTH halves (user=-1 password=301)
|
||||
--- FAIL: TestAppPage_LoginPairPanel (0.07s)
|
||||
## TestAppPage_NoStoredValueShowsSentence — mutation on internal/web/app_login.go
|
||||
=== RUN TestAppPage_NoStoredValueShowsSentence
|
||||
app_page_tuning_test.go:217: ADMIN_PASSWORD: no sentence for a field the box holds no value for
|
||||
app_page_tuning_test.go:220: ADMIN_PASSWORD: an eye is offered for a value the endpoint answers 404 for
|
||||
app_page_tuning_test.go:217: ADMIN_USER: no sentence for a field the box holds no value for
|
||||
app_page_tuning_test.go:220: ADMIN_USER: an eye is offered for a value the endpoint answers 404 for
|
||||
app_page_tuning_test.go:225: the page offers an eye for ADMIN_USER and the endpoint answers 404
|
||||
## TestAppPage_SettingsHasNoDuplicateDomain — mutation on internal/web/templates/app_settings.html
|
||||
=== RUN TestAppPage_SettingsHasNoDuplicateDomain
|
||||
app_page_tuning_test.go:242: the settings tab still renders id="auto-field-DOMAIN"
|
||||
app_page_tuning_test.go:242: the settings tab still renders id="deploy-form"
|
||||
app_page_tuning_test.go:242: the settings tab still renders id="field-HDD_PATH"
|
||||
--- FAIL: TestAppPage_SettingsHasNoDuplicateDomain (0.09s)
|
||||
## TestAppPage_OneSectionStyleEverywhere — mutation on internal/web/templates/app_logs.html
|
||||
=== RUN TestAppPage_OneSectionStyleEverywhere
|
||||
app_page_tuning_test.go:379: app_logs: a block outside the section style: <div data-tab-panel="logs">
|
||||
--- FAIL: TestAppPage_OneSectionStyleEverywhere (0.59s)
|
||||
## TestDeployPage_DeployedSelectShowsStoredHDDPath — row reads the DEFAULT drive's label
|
||||
## TestDeployPage_DeployedSelectShowsStoredHDDPath — row reads the DEFAULT drive's label
|
||||
networkstub_test.go:191: the storage row must name the STORED path's drive, got: data-app-storage-row="1">Tárhely (felhom-usb)
|
||||
networkstub_test.go:194: the storage row must NOT name the default drive for a deployed app, got: data-app-storage-row="1">Tárhely (felhom-usb)
|
||||
--- FAIL: TestDeployPage_DeployedSelectShowsStoredHDDPath (0.07s)
|
||||
@@ -0,0 +1,9 @@
|
||||
== tester-1
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.319.0 Up 58 seconds (healthy)
|
||||
== demo-hp
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.319.0 Up About a minute (healthy)
|
||||
== demo-felhom
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.319.0 Up About a minute (healthy)
|
||||
@@ -0,0 +1,3 @@
|
||||
tester-1 Location: /customers/tester-1?flash=floor_set
|
||||
demo-hp Location: /customers/demo-hp?flash=floor_set
|
||||
demo-felhom Location: /customers/demo-felhom?flash=floor_set
|
||||
@@ -0,0 +1,3 @@
|
||||
felhom.enkisfelhom.hu 200
|
||||
felhom.enkicsifelhom.hu 200
|
||||
felhom.demo-felhom.eu 200
|
||||
@@ -0,0 +1,5 @@
|
||||
10:37:01
|
||||
HTTP/1.1 303 See Other
|
||||
Location: /configuration?flash=floor_set
|
||||
('min_controller_version', '0.319.0', '2026-10-16 10:37:02')
|
||||
('min_controller_version_declared_min_agent', '0.319.0=0.158.1', '2026-10-16 10:37:02')
|
||||
@@ -0,0 +1,3 @@
|
||||
10:35:57
|
||||
HTTP/1.1 303 See Other
|
||||
Location: /configuration?flash=floor_set
|
||||
@@ -0,0 +1,39 @@
|
||||
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
|
||||
== tester-1 (enkicsifelhom.hu): 13 public name(s)
|
||||
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
|
||||
== demo-hp (enkisfelhom.hu): 13 public name(s)
|
||||
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
|
||||
== demo-felhom (demo-felhom.eu): 8 public name(s)
|
||||
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1
|
||||
== every public name redirects http -> https
|
||||
@@ -0,0 +1,4 @@
|
||||
['key', 'value', 'updated_at']
|
||||
('min_controller_version', '0.319.0', '2026-10-16 10:35:57')
|
||||
('artifact_min_agent', '0.155.0', '2026-10-16 07:23:10')
|
||||
('min_controller_version_declared_min_agent', '0.319.0=0.157.0', '2026-10-16 10:35:57')
|
||||
@@ -0,0 +1,6 @@
|
||||
('demo-felhom', '', '')
|
||||
('demo-hp', '', '')
|
||||
('tester-1', '', '')
|
||||
('Tester-2', '', '')
|
||||
('min_controller_version', '0.319.0', '2026-10-16 10:35:57')
|
||||
('min_controller_version_declared_min_agent', '0.319.0=0.157.0', '2026-10-16 10:35:57')
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal CDP client: log in to the controller through its PUBLIC address in headless Chrome, open each
|
||||
app-page tab, assert computed styles, collect console errors, save screenshots. The password is read
|
||||
from a 0600 file and only ever sent inside a CDP message; it is never printed."""
|
||||
import base64, json, os, socket, struct, subprocess, sys, time, urllib.request
|
||||
|
||||
CHROME = os.path.expanduser('~/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux64/chrome-headless-shell')
|
||||
BASE = sys.argv[1] # https://felhom.enkisfelhom.hu
|
||||
SLUG = sys.argv[2] # calibre-web
|
||||
PWFILE = sys.argv[3]
|
||||
OUT = sys.argv[4]
|
||||
PORT = 9333
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
prof = os.path.join(OUT, '.profile')
|
||||
proc = subprocess.Popen([CHROME, f'--remote-debugging-port={PORT}', '--no-sandbox', f'--user-data-dir={prof}',
|
||||
'--window-size=1280,900', 'about:blank'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
for _ in range(50):
|
||||
try:
|
||||
tabs = json.load(urllib.request.urlopen(f'http://127.0.0.1:{PORT}/json'))
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.2)
|
||||
ws_url = [t for t in tabs if t['type'] == 'page'][0]['webSocketDebuggerUrl']
|
||||
host, path = ws_url[len('ws://'):].split('/', 1)
|
||||
h, p = host.split(':')
|
||||
sock = socket.create_connection((h, int(p)))
|
||||
key = base64.b64encode(os.urandom(16)).decode()
|
||||
sock.send(f'GET /{path} HTTP/1.1\r\nHost: {host}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n'.encode())
|
||||
buf = b''
|
||||
while b'\r\n\r\n' not in buf:
|
||||
buf += sock.recv(4096)
|
||||
st = {'pending': buf.split(b'\r\n\r\n', 1)[1]}
|
||||
|
||||
def send(obj):
|
||||
data = json.dumps(obj).encode()
|
||||
hdr = bytearray([0x81])
|
||||
n = len(data)
|
||||
if n < 126: hdr.append(0x80 | n)
|
||||
elif n < 65536: hdr.append(0x80 | 126); hdr += struct.pack('>H', n)
|
||||
else: hdr.append(0x80 | 127); hdr += struct.pack('>Q', n)
|
||||
mask = os.urandom(4); hdr += mask
|
||||
sock.sendall(bytes(hdr) + bytes(b ^ mask[i % 4] for i, b in enumerate(data)))
|
||||
|
||||
def recv_exact(n):
|
||||
while len(st['pending']) < n:
|
||||
st['pending'] += sock.recv(1 << 20)
|
||||
out, st['pending'] = st['pending'][:n], st['pending'][n:]
|
||||
return out
|
||||
|
||||
def recv():
|
||||
msg = b''
|
||||
while True:
|
||||
b0, b1 = recv_exact(2)
|
||||
n = b1 & 0x7f
|
||||
if n == 126: n = struct.unpack('>H', recv_exact(2))[0]
|
||||
elif n == 127: n = struct.unpack('>Q', recv_exact(8))[0]
|
||||
msg += recv_exact(n)
|
||||
if b0 & 0x80: return json.loads(msg)
|
||||
|
||||
console = []
|
||||
mid = [0]
|
||||
def call(method, **params):
|
||||
mid[0] += 1; i = mid[0]
|
||||
send({'id': i, 'method': method, 'params': params})
|
||||
while True:
|
||||
m = recv()
|
||||
if m.get('method') == 'Runtime.exceptionThrown':
|
||||
console.append(('exception', m['params']['exceptionDetails'].get('text', ''), m['params']['exceptionDetails'].get('exception', {}).get('description', '')[:200]))
|
||||
elif m.get('method') == 'Runtime.consoleAPICalled' and m['params']['type'] in ('error', 'warning'):
|
||||
console.append((m['params']['type'], ' '.join(str(a.get('value', a.get('description', ''))) for a in m['params']['args'])[:200]))
|
||||
elif m.get('method') == 'Log.entryAdded' and m['params']['entry']['level'] in ('error', 'warning'):
|
||||
e = m['params']['entry']; console.append(('log-' + e['level'], e.get('text', '')[:200], e.get('url', '')))
|
||||
if m.get('id') == i:
|
||||
if 'error' in m: raise RuntimeError(f'{method}: {m["error"]}')
|
||||
return m.get('result', {})
|
||||
|
||||
def ev(expr):
|
||||
r = call('Runtime.evaluate', expression=expr, returnByValue=True, awaitPromise=True)
|
||||
return r.get('result', {}).get('value')
|
||||
|
||||
def goto(url):
|
||||
call('Page.navigate', url=url)
|
||||
for _ in range(100):
|
||||
time.sleep(0.2)
|
||||
if ev('document.readyState') == 'complete':
|
||||
break
|
||||
time.sleep(1.5) # images' onerror, layout
|
||||
|
||||
for d in ('Page', 'Runtime', 'Log', 'Network'):
|
||||
call(d + '.enable')
|
||||
pw = open(PWFILE).read().strip()
|
||||
goto(BASE + '/login')
|
||||
ev("(function(){var f=document.querySelector('input[type=password]'); f.value=%s; f.form.submit(); return 1})()" % json.dumps(pw))
|
||||
del pw
|
||||
time.sleep(3)
|
||||
print('after login url:', ev('location.pathname'))
|
||||
console.clear() # the login page is not under test
|
||||
|
||||
report = {}
|
||||
for tab in ['settings', 'data', 'guide', 'logs']:
|
||||
n0 = len(console)
|
||||
goto(f'{BASE}/apps/{SLUG}/{tab}')
|
||||
res = ev('''(function(){
|
||||
function cs(el, props){ if(!el) return null; var s=getComputedStyle(el), o={}; props.forEach(function(p){o[p]=s.getPropertyValue(p)}); return o; }
|
||||
var secs=[].slice.call(document.querySelectorAll('.app-section')).filter(function(e){return e.offsetParent!==null});
|
||||
var col=document.querySelector('.app-page');
|
||||
var t=document.querySelector('.app-section h3'); var first=secs[0];
|
||||
var gap = (t && first) ? Math.round(t.getBoundingClientRect().top - t.closest('.app-section').getBoundingClientRect().top) : null;
|
||||
var widths = secs.map(function(e){return Math.round(e.getBoundingClientRect().width)});
|
||||
var spacing = []; for (var i=1;i<secs.length;i++){ spacing.push(Math.round(secs[i].getBoundingClientRect().top - secs[i-1].getBoundingClientRect().bottom)); }
|
||||
return {path: location.pathname,
|
||||
activeTab: cs(document.querySelector('.app-tab-active'), ['background-color','color','border-bottom-color','border-bottom-width','padding-top','padding-left']),
|
||||
tabBar: cs(document.querySelector('.app-tabs'), ['background-color','border-top-color','padding-top']),
|
||||
hero: cs(document.querySelector('.app-hero'), ['background-color','border-top-color','padding-top','padding-left']),
|
||||
section: cs(first, ['background-color','border-top-color','border-top-width','padding-top','padding-left','margin-bottom','border-radius']),
|
||||
visibleSections: secs.length, sectionWidths: widths, columnWidth: col ? Math.round(col.getBoundingClientRect().width) : null,
|
||||
sectionSpacing: spacing, titleTopGap: gap,
|
||||
loginPanel: !!document.querySelector('[data-login-panel]'),
|
||||
loginFields: [].map.call(document.querySelectorAll('[data-login-field]'), function(e){return e.getAttribute('data-login-field')}),
|
||||
notStored: [].map.call(document.querySelectorAll('[data-login-not-stored]'), function(e){return e.getAttribute('data-login-not-stored')}),
|
||||
reveals: [].map.call(document.querySelectorAll('[data-login-reveal]'), function(e){return e.getAttribute('data-login-reveal')}),
|
||||
loginPairCols: (function(){var g=document.querySelector('.app-login-pair'); return g?getComputedStyle(g).gridTemplateColumns:null})(),
|
||||
domainInput: !!document.querySelector('#auto-field-DOMAIN, #field-DOMAIN'), deployForm: !!document.querySelector('#deploy-form'),
|
||||
logBoxWidth: (function(){var l=document.querySelector('.logs-container'); return l?Math.round(l.getBoundingClientRect().width):null})()
|
||||
};})()''')
|
||||
res['console'] = console[n0:]
|
||||
shot = call('Page.captureScreenshot', format='png', captureBeyondViewport=True)
|
||||
open(os.path.join(OUT, f'{SLUG}-{tab}.png'), 'wb').write(base64.b64decode(shot['data']))
|
||||
report[tab] = res
|
||||
# phone width: the login pair stacks
|
||||
call('Emulation.setDeviceMetricsOverride', width=390, height=844, deviceScaleFactor=1, mobile=True)
|
||||
goto(f'{BASE}/apps/{SLUG}/settings')
|
||||
report['settings_390px'] = {'loginPairCols': ev("(function(){var g=document.querySelector('.app-login-pair'); return g?getComputedStyle(g).gridTemplateColumns:null})()"),
|
||||
'scrollWidth': ev('document.documentElement.scrollWidth')}
|
||||
shot = call('Page.captureScreenshot', format='png', captureBeyondViewport=True)
|
||||
open(os.path.join(OUT, f'{SLUG}-settings-390px.png'), 'wb').write(base64.b64decode(shot['data']))
|
||||
print(json.dumps(report, indent=1, ensure_ascii=False))
|
||||
finally:
|
||||
proc.terminate()
|
||||
subprocess.run(['rm', '-rf', prof])
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal CDP client: log in to the controller through its PUBLIC address in headless Chrome, open each
|
||||
app-page tab, assert computed styles, collect console errors, save screenshots. The password is read
|
||||
from a 0600 file and only ever sent inside a CDP message; it is never printed."""
|
||||
import base64, json, os, socket, struct, subprocess, sys, time, urllib.request
|
||||
|
||||
CHROME = os.path.expanduser('~/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux64/chrome-headless-shell')
|
||||
BASE = sys.argv[1] # https://felhom.enkisfelhom.hu
|
||||
SLUG = sys.argv[2] # calibre-web
|
||||
PWFILE = sys.argv[3]
|
||||
OUT = sys.argv[4]
|
||||
PORT = 9333
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
prof = os.path.join(OUT, '.profile')
|
||||
proc = subprocess.Popen([CHROME, f'--remote-debugging-port={PORT}', '--no-sandbox', f'--user-data-dir={prof}',
|
||||
'--window-size=1280,900', 'about:blank'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
for _ in range(50):
|
||||
try:
|
||||
tabs = json.load(urllib.request.urlopen(f'http://127.0.0.1:{PORT}/json'))
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.2)
|
||||
ws_url = [t for t in tabs if t['type'] == 'page'][0]['webSocketDebuggerUrl']
|
||||
host, path = ws_url[len('ws://'):].split('/', 1)
|
||||
h, p = host.split(':')
|
||||
sock = socket.create_connection((h, int(p)))
|
||||
key = base64.b64encode(os.urandom(16)).decode()
|
||||
sock.send(f'GET /{path} HTTP/1.1\r\nHost: {host}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n'.encode())
|
||||
buf = b''
|
||||
while b'\r\n\r\n' not in buf:
|
||||
buf += sock.recv(4096)
|
||||
st = {'pending': buf.split(b'\r\n\r\n', 1)[1]}
|
||||
|
||||
def send(obj):
|
||||
data = json.dumps(obj).encode()
|
||||
hdr = bytearray([0x81])
|
||||
n = len(data)
|
||||
if n < 126: hdr.append(0x80 | n)
|
||||
elif n < 65536: hdr.append(0x80 | 126); hdr += struct.pack('>H', n)
|
||||
else: hdr.append(0x80 | 127); hdr += struct.pack('>Q', n)
|
||||
mask = os.urandom(4); hdr += mask
|
||||
sock.sendall(bytes(hdr) + bytes(b ^ mask[i % 4] for i, b in enumerate(data)))
|
||||
|
||||
def recv_exact(n):
|
||||
while len(st['pending']) < n:
|
||||
st['pending'] += sock.recv(1 << 20)
|
||||
out, st['pending'] = st['pending'][:n], st['pending'][n:]
|
||||
return out
|
||||
|
||||
def recv():
|
||||
msg = b''
|
||||
while True:
|
||||
b0, b1 = recv_exact(2)
|
||||
n = b1 & 0x7f
|
||||
if n == 126: n = struct.unpack('>H', recv_exact(2))[0]
|
||||
elif n == 127: n = struct.unpack('>Q', recv_exact(8))[0]
|
||||
msg += recv_exact(n)
|
||||
if b0 & 0x80: return json.loads(msg)
|
||||
|
||||
console = []
|
||||
mid = [0]
|
||||
def call(method, **params):
|
||||
mid[0] += 1; i = mid[0]
|
||||
send({'id': i, 'method': method, 'params': params})
|
||||
while True:
|
||||
m = recv()
|
||||
if m.get('method') == 'Runtime.exceptionThrown':
|
||||
console.append(('exception', m['params']['exceptionDetails'].get('text', ''), m['params']['exceptionDetails'].get('exception', {}).get('description', '')[:200]))
|
||||
elif m.get('method') == 'Runtime.consoleAPICalled' and m['params']['type'] in ('error', 'warning'):
|
||||
console.append((m['params']['type'], ' '.join(str(a.get('value', a.get('description', ''))) for a in m['params']['args'])[:200]))
|
||||
elif m.get('method') == 'Log.entryAdded' and m['params']['entry']['level'] in ('error', 'warning'):
|
||||
e = m['params']['entry']; console.append(('log-' + e['level'], e.get('text', '')[:200], e.get('url', '')))
|
||||
if m.get('id') == i:
|
||||
if 'error' in m: raise RuntimeError(f'{method}: {m["error"]}')
|
||||
return m.get('result', {})
|
||||
|
||||
def ev(expr):
|
||||
r = call('Runtime.evaluate', expression=expr, returnByValue=True, awaitPromise=True)
|
||||
return r.get('result', {}).get('value')
|
||||
|
||||
def goto(url):
|
||||
call('Page.navigate', url=url)
|
||||
for _ in range(100):
|
||||
time.sleep(0.2)
|
||||
if ev('document.readyState') == 'complete':
|
||||
break
|
||||
time.sleep(1.5) # images' onerror, layout
|
||||
|
||||
for d in ('Page', 'Runtime', 'Log', 'Network'):
|
||||
call(d + '.enable')
|
||||
pw = open(PWFILE).read().strip()
|
||||
goto(BASE + '/login')
|
||||
ev("(function(){var f=document.querySelector('input[type=password]'); f.value=%s; f.form.submit(); return 1})()" % json.dumps(pw))
|
||||
del pw
|
||||
time.sleep(3)
|
||||
print('after login url:', ev('location.pathname'))
|
||||
console.clear() # the login page is not under test
|
||||
|
||||
report = {}
|
||||
for tab in ['install']:
|
||||
n0 = len(console)
|
||||
goto(f'{BASE}/apps/{SLUG}/{tab}')
|
||||
res = ev('''(function(){
|
||||
function cs(el, props){ if(!el) return null; var s=getComputedStyle(el), o={}; props.forEach(function(p){o[p]=s.getPropertyValue(p)}); return o; }
|
||||
var secs=[].slice.call(document.querySelectorAll('.app-section, .deploy-form .form-section')).filter(function(e){return e.offsetParent!==null});
|
||||
var col=document.querySelector('.app-page');
|
||||
var t=document.querySelector('.deploy-form .form-section h4'); var first=secs[0];
|
||||
var gap = (t && first) ? Math.round(t.getBoundingClientRect().top - t.closest('.app-section, .form-section').getBoundingClientRect().top) : null;
|
||||
var widths = secs.map(function(e){return Math.round(e.getBoundingClientRect().width)});
|
||||
var spacing = []; for (var i=1;i<secs.length;i++){ spacing.push(Math.round(secs[i].getBoundingClientRect().top - secs[i-1].getBoundingClientRect().bottom)); }
|
||||
return {path: location.pathname,
|
||||
activeTab: cs(document.querySelector('.app-tab-active'), ['background-color','color','border-bottom-color','border-bottom-width','padding-top','padding-left']),
|
||||
tabBar: cs(document.querySelector('.app-tabs'), ['background-color','border-top-color','padding-top']),
|
||||
hero: cs(document.querySelector('.app-hero'), ['background-color','border-top-color','padding-top','padding-left']),
|
||||
section: cs(first, ['background-color','border-top-color','border-top-width','padding-top','padding-left','margin-bottom','border-radius']),
|
||||
visibleSections: secs.length, sectionWidths: widths, columnWidth: col ? Math.round(col.getBoundingClientRect().width) : null,
|
||||
sectionSpacing: spacing, titleTopGap: gap,
|
||||
loginPanel: !!document.querySelector('[data-login-panel]'),
|
||||
loginFields: [].map.call(document.querySelectorAll('[data-login-field]'), function(e){return e.getAttribute('data-login-field')}),
|
||||
notStored: [].map.call(document.querySelectorAll('[data-login-not-stored]'), function(e){return e.getAttribute('data-login-not-stored')}),
|
||||
reveals: [].map.call(document.querySelectorAll('[data-login-reveal]'), function(e){return e.getAttribute('data-login-reveal')}),
|
||||
loginPairCols: (function(){var g=document.querySelector('.app-login-pair'); return g?getComputedStyle(g).gridTemplateColumns:null})(),
|
||||
domainInput: !!document.querySelector('#auto-field-DOMAIN, #field-DOMAIN'), deployForm: !!document.querySelector('#deploy-form'),
|
||||
logBoxWidth: (function(){var l=document.querySelector('.logs-container'); return l?Math.round(l.getBoundingClientRect().width):null})()
|
||||
};})()''')
|
||||
res['console'] = console[n0:]
|
||||
shot = call('Page.captureScreenshot', format='png', captureBeyondViewport=True)
|
||||
open(os.path.join(OUT, f'{SLUG}-{tab}.png'), 'wb').write(base64.b64decode(shot['data']))
|
||||
report[tab] = res
|
||||
print(json.dumps(report, indent=1, ensure_ascii=False)); raise SystemExit
|
||||
call('Emulation.setDeviceMetricsOverride', width=390, height=844, deviceScaleFactor=1, mobile=True)
|
||||
goto(f'{BASE}/apps/{SLUG}/settings')
|
||||
report['settings_390px'] = {'loginPairCols': ev("(function(){var g=document.querySelector('.app-login-pair'); return g?getComputedStyle(g).gridTemplateColumns:null})()"),
|
||||
'scrollWidth': ev('document.documentElement.scrollWidth')}
|
||||
shot = call('Page.captureScreenshot', format='png', captureBeyondViewport=True)
|
||||
open(os.path.join(OUT, f'{SLUG}-settings-390px.png'), 'wb').write(base64.b64decode(shot['data']))
|
||||
print(json.dumps(report, indent=1, ensure_ascii=False))
|
||||
finally:
|
||||
proc.terminate()
|
||||
subprocess.run(['rm', '-rf', prof])
|
||||
@@ -0,0 +1,2 @@
|
||||
romm rommapp/romm:5.3.1 ['/redis-data', '/romm']
|
||||
privatebin privatebin/pdo:2.0.6 ['/run', '/tmp', '/var/lib/nginx/tmp']
|
||||
@@ -0,0 +1,7 @@
|
||||
customers reporting RomM telemetry, with their newest report (hub DB + WAL, read 2026-10-16):
|
||||
demo-felhom rows 1 newest 2026-10-13 09:37:46
|
||||
demo-hp rows 4450 newest 2026-10-16 16:32:25
|
||||
customers reporting ANY app in the last 2 hours:
|
||||
demo-felhom
|
||||
demo-hp
|
||||
tester-1
|
||||
@@ -0,0 +1,18 @@
|
||||
2026/10/16 13:47:23 main.go:814: [INFO] Self-update enabled (check every 6h, auto-update: false, auto-update time: 04:30)
|
||||
2026/10/16 14:51:36 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:40328: [db-dump tier2 offsite update-leg]
|
||||
2026/10/16 14:51:36 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
|
||||
2026/10/16 14:51:36 night_chain.go:77: [INFO] [night-chain] db-dump: started
|
||||
2026/10/16 14:53:42 night_chain.go:82: [INFO] [night-chain] db-dump: done in 2m6s
|
||||
2026/10/16 14:53:42 night_chain.go:77: [INFO] [night-chain] tier2: started
|
||||
2026/10/16 14:54:10 night_chain.go:82: [INFO] [night-chain] tier2: done in 28s
|
||||
2026/10/16 14:54:10 night_chain.go:77: [INFO] [night-chain] offsite: started
|
||||
2026/10/16 14:57:08 night_chain.go:82: [INFO] [night-chain] offsite: done in 2m58s
|
||||
2026/10/16 14:57:08 night_chain.go:77: [INFO] [night-chain] update-leg: started
|
||||
2026/10/16 14:57:08 unattended.go:270: [INFO] [update-leg] started (manual-chain): window 02:30, no step starts at or after 20:12
|
||||
2026/10/16 14:57:08 unattended.go:316: [INFO] [update-leg] kimai: skipped — held
|
||||
2026/10/16 14:57:08 unattended.go:248: [INFO] [update-leg] update leg (manual-chain): done=0 undone=0 held=0 failed=0 skipped=1 in 0s [skipped: kimai=held]
|
||||
2026/10/16 14:57:08 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
|
||||
2026/10/16 14:57:08 night_chain.go:93: [INFO] [night-chain] finished in 5m32s
|
||||
romm Up About a minute (healthy)
|
||||
romm-redis Up About a minute (healthy)
|
||||
romm-db Up About a minute (healthy)
|
||||
@@ -0,0 +1,40 @@
|
||||
== mounts of the running romm container
|
||||
volume <ANONYMOUS 64-hex> -> /redis-data
|
||||
volume <ANONYMOUS 64-hex> -> /romm
|
||||
volume romm_romm_config -> /romm/config
|
||||
bind -> /romm/resources
|
||||
bind -> /romm/library/bios
|
||||
bind -> /romm/library/roms
|
||||
|
||||
== compose volumes
|
||||
167:volumes:
|
||||
168- romm_config:
|
||||
169- romm_db_data:
|
||||
170- romm_redis_data:
|
||||
171-
|
||||
172-networks:
|
||||
173- traefik-public:
|
||||
18:# Cover art etc → ${HDD_PATH}/appdata/romm/resources (app-internal, NOT browsable)
|
||||
29: image: rommapp/romm:5.3.1@sha256:0d66b4ea152a237c7f24b95e87459f46d3c596a7c0b35a82f26bc971487278b3
|
||||
40: if [ ! -f /romm/config/config.yml ]; then
|
||||
42: cat > /romm/config/config.yml << 'CONF'
|
||||
80: # on demo-hp 9201 (R-1089, felhom.eu audits/romm-ghost-2026-10-16/A1). Now roms/<platform>/ IS
|
||||
83: - ${USERDATA_PATH}/roms:/romm/library/roms
|
||||
84: - ${USERDATA_PATH}/roms-bios:/romm/library/bios
|
||||
85: - ${HDD_PATH}/appdata/romm/resources:/romm/resources
|
||||
86: - romm_config:/romm/config
|
||||
== what earlier app backups of romm hold
|
||||
/mnt/felhom-drives/hdd_1/kept/romm/2026-10-16_113539/unit/volume-dumps
|
||||
total 203084
|
||||
drwxr-xr-x 2 root root 4096 Oct 4 05:35 .
|
||||
drwxr-xr-x 5 root root 4096 Oct 4 11:18 ..
|
||||
-rw-r--r-- 1 root root 2560 Oct 4 05:35 romm_romm_config.tar
|
||||
-rw-r--r-- 1 root root 163786752 Oct 4 05:35 romm_romm_db_data.tar
|
||||
-rw-r--r-- 1 root root 44148736 Oct 4 05:35 romm_romm_redis_data.tar
|
||||
== anonymous volume contents
|
||||
assets
|
||||
config
|
||||
library
|
||||
resources
|
||||
/var/lib/docker/volumes/567f3e5915421b542fc2e67d3195d03b32ef07ef79f2851c6198653cf29e00f6/_data/assets
|
||||
28K /var/lib/docker/volumes/567f3e5915421b542fc2e67d3195d03b32ef07ef79f2851c6198653cf29e00f6/_data
|
||||
@@ -0,0 +1,49 @@
|
||||
== 1. sync the catalog
|
||||
sync -> 200 {'ok': True, 'data': {'ok': True, 'updated': ['code-server', 'gokapi', 'grafana', 'kimai', 'nextcloud', 'nzbget', 'prowlarr', 'romm', 'transmission'], 'message': 'Sablonok frissítve — frissítve: code-
|
||||
applied compose names romm_assets: 3
|
||||
== 2. restart RomM (Újraindítás)
|
||||
restart -> 200 {'ok': True, 'data': {'state': 'starting'}, 'message': 'Stack romm restart requested — state now: starting'}
|
||||
state: running
|
||||
mounts after restart:
|
||||
volume <ANONYMOUS> -> /redis-data
|
||||
volume <ANONYMOUS> -> /romm
|
||||
volume romm_romm_assets -> /romm/assets
|
||||
volume romm_romm_config -> /romm/config
|
||||
bind -> /romm/resources
|
||||
bind -> /romm/library/bios
|
||||
bind -> /romm/library/roms
|
||||
== 3. a save where RomM writes saves (dummy bytes, as the app's own user)
|
||||
drwxr-xr-x 3 root root 4096 Oct 16 16:51 ..
|
||||
-rw-r--r-- 1 root root 32768 Oct 16 16:51 r1097-proof.sav
|
||||
4a55d00c3ee6bccd55649e48fd19d84703f755f84474ac0b6a41299b114ee6a5 /romm/assets/romm/users/1/saves/gba/r1097-proof/r1097-proof.sav
|
||||
== 4. the by-day backup (night chain)
|
||||
night-chain -> 202 {'data': {'legs': ['db-dump', 'tier2', 'offsite', 'update-leg']}, 'message': 'started', 'ok': True}
|
||||
night chain last line: 2026/10/16 14:57:08 night_chain.go:93: [INFO] [night-chain] finished in 5m32s after 342s
|
||||
romm volume dumps now: /mnt/felhom-drives/hdd_1/backups/primary/romm/volume-dumps/:
|
||||
total 176584
|
||||
drwxr-xr-x 2 root root 4096 Oct 16 14:56 .
|
||||
drwxr-xr-x 5 root root 4096 Oct 16 14:56 ..
|
||||
-rw-r--r-- 1 root root 37888 Oct 16 14:56 romm_romm_assets.tar
|
||||
-rw-r--r-- 1 root root 2560 Oct 16 14:56 romm_romm_config.tar
|
||||
-rw-r--r-- 1 root root 162300416 Oct 16 14:56 romm_romm_db_data.tar
|
||||
-rw-r--r-- 1 root root 18463744 Oct 16 14:56 romm_romm_redis_data.tar
|
||||
/mnt/felhom-drives/hdd_1/backups/primary/romm/volume-dumps/romm_romm_assets.tar
|
||||
|
||||
== 5. remove RomM, KEEPING its backups
|
||||
remove -> 200 {'ok': True, 'data': {'removed': 'romm', 'volumes_removed': ['romm_romm_assets', 'romm_romm_config', 'romm_romm_db_data', 'romm_romm_redis_data'], 'hdd_paths_removed': [], 'hdd_paths_preserved': ['/mn
|
||||
volumes left: romm_romm_redis_data.pre-aof-fix-20260924T135937Z
|
||||
== 6. restore from the backups page
|
||||
restore: {'ok': True, 'snapshot_id': 'helyi', 'http': 'HTTP/2 302', 'seconds': 77.0}
|
||||
state: running
|
||||
mounts after restore:
|
||||
volume <ANONYMOUS> -> /redis-data
|
||||
volume <ANONYMOUS> -> /romm
|
||||
volume romm_romm_assets -> /romm/assets
|
||||
volume romm_romm_config -> /romm/config
|
||||
bind -> /romm/resources
|
||||
bind -> /romm/library/bios
|
||||
bind -> /romm/library/roms
|
||||
drwxr-xr-x 3 root root 4096 Oct 16 16:58 ..
|
||||
-rw-r--r-- 1 root root 32768 Oct 16 16:51 r1097-proof.sav
|
||||
4a55d00c3ee6bccd55649e48fd19d84703f755f84474ac0b6a41299b114ee6a5 /romm/assets/romm/users/1/saves/gba/r1097-proof/r1097-proof.sav
|
||||
== VERDICT: the save came back byte-identical: True
|
||||
@@ -0,0 +1,6 @@
|
||||
tables with customer: ['customer_notifications', 'customer_configs', 'customer_claims', 'customer_resets']
|
||||
every customer's NEWEST report (hub DB + WAL):
|
||||
demo-hp 0.320.0 2026-10-16 14:58:09
|
||||
tester-1 0.320.0 2026-10-16 14:49:33
|
||||
demo-felhom 0.320.0 2026-10-16 14:49:28
|
||||
Tester-2 0.292.0 2026-10-04 18:06:24
|
||||
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,60 @@
|
||||
rm: cannot remove '/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/burndown-apps-2026-10-16/browser/.profile/Default': Directory not empty
|
||||
after login url: /launcher
|
||||
{
|
||||
"/apps/calibre-web/data": {
|
||||
"path": "/apps/calibre-web/data",
|
||||
"backupSections": 1,
|
||||
"rows": [
|
||||
"nightly",
|
||||
"second",
|
||||
"target"
|
||||
],
|
||||
"offDrivePBS": false,
|
||||
"valueFont": "\"Plus Jakarta Sans\"",
|
||||
"exportToggle": [
|
||||
"Megjelenítés",
|
||||
"Jelszó mutatása"
|
||||
],
|
||||
"fbReveal": false,
|
||||
"fbStale": false,
|
||||
"changeItLine": false,
|
||||
"scrollWidth": 1270,
|
||||
"console": []
|
||||
},
|
||||
"/apps/filebrowser": {
|
||||
"path": "/apps/filebrowser",
|
||||
"backupSections": 0,
|
||||
"rows": [],
|
||||
"offDrivePBS": false,
|
||||
"valueFont": null,
|
||||
"exportToggle": null,
|
||||
"fbReveal": true,
|
||||
"fbStale": false,
|
||||
"changeItLine": false,
|
||||
"scrollWidth": 1280,
|
||||
"console": []
|
||||
},
|
||||
"/apps/calibre-web/install": {
|
||||
"path": "/apps/calibre-web/settings",
|
||||
"backupSections": 0,
|
||||
"rows": [],
|
||||
"offDrivePBS": false,
|
||||
"valueFont": null,
|
||||
"exportToggle": null,
|
||||
"fbReveal": false,
|
||||
"fbStale": false,
|
||||
"changeItLine": false,
|
||||
"scrollWidth": 1270,
|
||||
"console": []
|
||||
},
|
||||
"filebrowser": {
|
||||
"path": "/files/Beolvas%C3%A1s",
|
||||
"updateBanner": false,
|
||||
"installApp": false,
|
||||
"releaseBanners": 0,
|
||||
"globals": {
|
||||
"disablePWAInstall": true,
|
||||
"updateAvailable": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
control: bookstack (keeps default_creds):
|
||||
302 known-login markers: 0
|
||||
grafana 200 known-login=0 answer=
|
||||
grafana/install 200 known-login=0 answer=
|
||||
gokapi 200 known-login=0 answer=
|
||||
gokapi/install 200 known-login=0 answer=
|
||||
kimai 200 known-login=0 answer=data-login-answer="fields"
|
||||
kimai/install 302 known-login=0 answer=
|
||||
code-server 200 known-login=0 answer=
|
||||
code-server/install 200 known-login=0 answer=
|
||||
nzbget 200 known-login=0 answer=
|
||||
nzbget/install 200 known-login=0 answer=
|
||||
nextcloud 200 known-login=0 answer=
|
||||
nextcloud/install 200 known-login=0 answer=
|
||||
transmission 200 known-login=0 answer=
|
||||
transmission/install 200 known-login=0 answer=
|
||||
prowlarr 200 known-login=0 answer=
|
||||
prowlarr/install 200 known-login=0 answer=
|
||||
control bookstack: 200 known-login=0 alapertelmezett=0
|
||||
control wger: 200 known-login=0 alapertelmezett=0
|
||||
control mealie: 200 known-login=0 alapertelmezett=0
|
||||
control claper: 200 known-login=0 alapertelmezett=0
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL unreachable (000) -> refused (exit 1)
|
||||
FAIL server error (500) -> refused (exit 1)
|
||||
2 of 5 cases FAILED
|
||||
@@ -0,0 +1,4 @@
|
||||
XX DECOY: the PARENT /romm mounted rc=0 (expected 1)
|
||||
XX DECOY: a CHILD /romm/assets/x mounted rc=0 (expected 1)
|
||||
AssertionError: Lists differ: [' default_creds: "admin / x"\n'] != []
|
||||
FAILED (failures=1)
|
||||
@@ -0,0 +1,26 @@
|
||||
## refused = any non-200 (FileBrowser down reads as refused)
|
||||
=== RUN TestFileBrowserStoredPassword_RefusedOnlyOnA401
|
||||
filebrowser_reset_test.go:182: FileBrowser down reads as refused — an unproven refusal must not tell the household its password changed
|
||||
--- FAIL: TestFileBrowserStoredPassword_RefusedOnlyOnA401 (0.00s)
|
||||
## no stored-password check on the page
|
||||
=== RUN TestFileBrowserLogin_StoredPasswordRefusedOffersReset
|
||||
logins_folders_test.go:365: stale: the page still offers the reveal of a password FileBrowser refuses
|
||||
--- FAIL: TestFileBrowserLogin_StoredPasswordRefusedOffersReset (0.08s)
|
||||
## the change-it line back on FileBrowser
|
||||
=== RUN TestFileBrowserLogin_StoredPasswordRefusedOffersReset
|
||||
logins_folders_test.go:380: FileBrowser still says to change the password after the first login (R-1101)
|
||||
--- FAIL: TestFileBrowserLogin_StoredPasswordRefusedOffersReset (0.08s)
|
||||
## no redirect for an installed app's /install
|
||||
=== RUN TestInstallAddress_InstalledRedirectsToSettings
|
||||
logins_folders_test.go:432: installed: 200 → "", want 302 → /apps/calibre-web/settings
|
||||
--- FAIL: TestInstallAddress_InstalledRedirectsToSettings (0.08s)
|
||||
## off-drive back in a Data tab string (hu)
|
||||
=== RUN TestAppData_OneBackupSection
|
||||
## a second backup section
|
||||
=== RUN TestAppData_OneBackupSection
|
||||
logins_folders_test.go:408: hu: 2 backup sections, want 1
|
||||
logins_folders_test.go:408: en: 2 backup sections, want 1
|
||||
--- FAIL: TestAppData_OneBackupSection (0.10s)
|
||||
## (redo) off-drive/PBS back in the always-shown 2nd-copy text (hu)
|
||||
logins_folders_test.go:415: hu: off-drive/PBS on the Data tab ("off-drive")
|
||||
--- FAIL: TestAppData_OneBackupSection (0.09s)
|
||||
@@ -0,0 +1,6 @@
|
||||
FAIL --push, tag present -> refuses: exit 0, want 1
|
||||
FAIL --push, tag absent -> proceeds: no 'is not present in the registry'
|
||||
FAIL --multiarch, tag present -> refuses: exit 0, want 1
|
||||
FAIL --push, registry unreachable -> refuses: exit 0, want 1
|
||||
FAIL --push, auth failure -> refuses: exit 0, want 1
|
||||
5 of 6 cases FAILED
|
||||
@@ -0,0 +1,15 @@
|
||||
== tester-1
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.321.0 Up 18 seconds (healthy)
|
||||
filebrowser Up 16 seconds (healthy)
|
||||
2
|
||||
== demo-hp
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.321.0 Up 21 seconds (healthy)
|
||||
filebrowser Up 3 minutes (healthy)
|
||||
2
|
||||
== demo-felhom
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.321.0 Up 25 seconds (healthy)
|
||||
filebrowser Up 23 seconds (healthy)
|
||||
2
|
||||
@@ -0,0 +1,3 @@
|
||||
tester-1 Location: /customers/tester-1?flash=floor_set
|
||||
demo-hp Location: /customers/demo-hp?flash=floor_set
|
||||
demo-felhom Location: /customers/demo-felhom?flash=floor_set
|
||||
@@ -0,0 +1,3 @@
|
||||
demo-hp Location: /customers/demo-hp?flash=floor_set
|
||||
tester-1 Location: /customers/tester-1?flash=floor_set
|
||||
demo-felhom Location: /customers/demo-felhom?flash=floor_set
|
||||
@@ -0,0 +1,3 @@
|
||||
felhom.enkisfelhom.hu 200
|
||||
felhom.enkicsifelhom.hu 200
|
||||
felhom.demo-felhom.eu 200
|
||||
@@ -0,0 +1,3 @@
|
||||
17:22:20
|
||||
HTTP/1.1 303 See Other
|
||||
Location: /configuration?flash=floor_set
|
||||
@@ -0,0 +1,40 @@
|
||||
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
|
||||
== tester-1 (enkicsifelhom.hu): 13 public name(s)
|
||||
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
|
||||
== demo-hp (enkisfelhom.hu): 14 public name(s)
|
||||
OK http://arcade.enkisfelhom.hu/r1033-check?q=1 -> 301 https://arcade.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
|
||||
== demo-felhom (demo-felhom.eu): 8 public name(s)
|
||||
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1
|
||||
== every public name redirects http -> https
|
||||
@@ -0,0 +1,7 @@
|
||||
FIRST READ (hub.db, -wal, -shm copied ONE BY ONE with kubectl exec cat) — STALE:
|
||||
('min_controller_version', '0.320.0', '2026-10-16 13:48:34')
|
||||
('min_controller_version_declared_min_agent', '0.320.0=0.158.1', '2026-10-16 13:48:34')
|
||||
SECOND READ (one tar of the three files) — 17:23:
|
||||
('min_controller_version', '0.321.0', '2026-10-16 15:22:20')
|
||||
('min_controller_version_declared_min_agent', '0.321.0=0.158.1', '2026-10-16 15:22:20')
|
||||
THIRD CHANNEL — GET /configuration with CC's token: the page shows 0.321.0
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""v0.321.0 apps-burndown browser check (Data tab, FileBrowser page and FileBrowser itself, an install address). Minimal CDP client: log in to the controller through its PUBLIC address in headless Chrome, open each
|
||||
app-page tab, assert computed styles, collect console errors, save screenshots. The password is read
|
||||
from a 0600 file and only ever sent inside a CDP message; it is never printed."""
|
||||
import base64, json, os, socket, struct, subprocess, sys, time, urllib.request
|
||||
|
||||
CHROME = os.path.expanduser('~/.cache/ms-playwright/chromium_headless_shell-1243/chrome-headless-shell-linux64/chrome-headless-shell')
|
||||
BASE = sys.argv[1] # https://felhom.enkisfelhom.hu
|
||||
PWFILE = sys.argv[2] # the controller password (0600 file)
|
||||
FBPWFILE = sys.argv[3] # FileBrowser's admin password (0600 file) — for the cold „Fajlok" click
|
||||
OUT = sys.argv[4]
|
||||
PORT = 9333
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
prof = os.path.join(OUT, '.profile')
|
||||
proc = subprocess.Popen([CHROME, f'--remote-debugging-port={PORT}', '--no-sandbox', f'--user-data-dir={prof}',
|
||||
'--window-size=1280,900', 'about:blank'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
for _ in range(50):
|
||||
try:
|
||||
tabs = json.load(urllib.request.urlopen(f'http://127.0.0.1:{PORT}/json'))
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.2)
|
||||
ws_url = [t for t in tabs if t['type'] == 'page'][0]['webSocketDebuggerUrl']
|
||||
host, path = ws_url[len('ws://'):].split('/', 1)
|
||||
h, p = host.split(':')
|
||||
sock = socket.create_connection((h, int(p)))
|
||||
key = base64.b64encode(os.urandom(16)).decode()
|
||||
sock.send(f'GET /{path} HTTP/1.1\r\nHost: {host}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n'.encode())
|
||||
buf = b''
|
||||
while b'\r\n\r\n' not in buf:
|
||||
buf += sock.recv(4096)
|
||||
st = {'pending': buf.split(b'\r\n\r\n', 1)[1]}
|
||||
|
||||
def send(obj):
|
||||
data = json.dumps(obj).encode()
|
||||
hdr = bytearray([0x81])
|
||||
n = len(data)
|
||||
if n < 126: hdr.append(0x80 | n)
|
||||
elif n < 65536: hdr.append(0x80 | 126); hdr += struct.pack('>H', n)
|
||||
else: hdr.append(0x80 | 127); hdr += struct.pack('>Q', n)
|
||||
mask = os.urandom(4); hdr += mask
|
||||
sock.sendall(bytes(hdr) + bytes(b ^ mask[i % 4] for i, b in enumerate(data)))
|
||||
|
||||
def recv_exact(n):
|
||||
while len(st['pending']) < n:
|
||||
st['pending'] += sock.recv(1 << 20)
|
||||
out, st['pending'] = st['pending'][:n], st['pending'][n:]
|
||||
return out
|
||||
|
||||
def recv():
|
||||
msg = b''
|
||||
while True:
|
||||
b0, b1 = recv_exact(2)
|
||||
n = b1 & 0x7f
|
||||
if n == 126: n = struct.unpack('>H', recv_exact(2))[0]
|
||||
elif n == 127: n = struct.unpack('>Q', recv_exact(8))[0]
|
||||
msg += recv_exact(n)
|
||||
if b0 & 0x80: return json.loads(msg)
|
||||
|
||||
console = []
|
||||
mid = [0]
|
||||
def call(method, **params):
|
||||
mid[0] += 1; i = mid[0]
|
||||
send({'id': i, 'method': method, 'params': params})
|
||||
while True:
|
||||
m = recv()
|
||||
if m.get('method') == 'Runtime.exceptionThrown':
|
||||
console.append(('exception', m['params']['exceptionDetails'].get('text', ''), m['params']['exceptionDetails'].get('exception', {}).get('description', '')[:200]))
|
||||
elif m.get('method') == 'Runtime.consoleAPICalled' and m['params']['type'] in ('error', 'warning'):
|
||||
console.append((m['params']['type'], ' '.join(str(a.get('value', a.get('description', ''))) for a in m['params']['args'])[:200]))
|
||||
elif m.get('method') == 'Log.entryAdded' and m['params']['entry']['level'] in ('error', 'warning'):
|
||||
e = m['params']['entry']; console.append(('log-' + e['level'], e.get('text', '')[:200], e.get('url', '')))
|
||||
if m.get('id') == i:
|
||||
if 'error' in m: raise RuntimeError(f'{method}: {m["error"]}')
|
||||
return m.get('result', {})
|
||||
|
||||
def ev(expr):
|
||||
r = call('Runtime.evaluate', expression=expr, returnByValue=True, awaitPromise=True)
|
||||
return r.get('result', {}).get('value')
|
||||
|
||||
def goto(url):
|
||||
call('Page.navigate', url=url)
|
||||
for _ in range(100):
|
||||
time.sleep(0.2)
|
||||
if ev('document.readyState') == 'complete':
|
||||
break
|
||||
time.sleep(1.5) # images' onerror, layout
|
||||
|
||||
for d in ('Page', 'Runtime', 'Log', 'Network'):
|
||||
call(d + '.enable')
|
||||
pw = open(PWFILE).read().strip()
|
||||
goto(BASE + '/login')
|
||||
ev("(function(){var f=document.querySelector('input[type=password]'); f.value=%s; f.form.submit(); return 1})()" % json.dumps(pw))
|
||||
del pw
|
||||
time.sleep(3)
|
||||
print('after login url:', ev('location.pathname'))
|
||||
console.clear() # the login page is not under test
|
||||
|
||||
def shot(name):
|
||||
s = call('Page.captureScreenshot', format='png', captureBeyondViewport=True)
|
||||
open(os.path.join(OUT, name + '.png'), 'wb').write(base64.b64decode(s['data']))
|
||||
|
||||
report = {}
|
||||
for p in ['/apps/calibre-web/data', '/apps/filebrowser', '/apps/calibre-web/install']:
|
||||
n0 = len(console)
|
||||
goto(BASE + p)
|
||||
report[p] = ev("""(function(){ return {path: location.pathname,
|
||||
backupSections: document.querySelectorAll('[data-backup-section]').length,
|
||||
rows: [].map.call(document.querySelectorAll('[data-backup-row]'), function(e){return e.getAttribute('data-backup-row')}),
|
||||
offDrivePBS: /off-drive|\\bPBS\\b/i.test(document.body.innerText),
|
||||
valueFont: (function(){var v=document.querySelector('[data-backup-row] .app-field-value'); return v?getComputedStyle(v).fontFamily.split(',')[0]:null})(),
|
||||
exportToggle: (function(){var b=document.getElementById('exportPwToggle'); return b?[b.textContent.trim(), b.getAttribute('aria-label')]:null})(),
|
||||
fbReveal: !!document.querySelector('#initcred-reveal'), fbStale: !!document.querySelector('[data-fb-stale]'),
|
||||
changeItLine: document.body.innerText.indexOf('hagyd figyelmen') >= 0,
|
||||
scrollWidth: document.documentElement.scrollWidth};})()""")
|
||||
report[p]['console'] = console[n0:]
|
||||
shot(p.strip('/').replace('/', '-'))
|
||||
# FileBrowser itself, logged in with the box's own password: no update banner, no install-app prompt.
|
||||
fbpw = open(FBPWFILE).read().strip()
|
||||
call('Network.clearBrowserCookies')
|
||||
goto(BASE.replace('://felhom.', '://files.') + '/')
|
||||
ev("""(function(pw){var u=document.querySelector('input[type=text],input[name=username],input#username');
|
||||
var p=document.querySelector('input[type=password]'); if(!u||!p) return 0;
|
||||
function set(el,v){var d=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value'); d.set.call(el,v); el.dispatchEvent(new Event('input',{bubbles:true}));}
|
||||
set(u,'admin'); set(p,pw); var b=document.querySelector('form button[type=submit], form input[type=submit], button.button--blue');
|
||||
if(b){b.click()} else {p.form && p.form.requestSubmit && p.form.requestSubmit()} return 1})(%s)""" % json.dumps(fbpw))
|
||||
del fbpw
|
||||
time.sleep(5)
|
||||
report['filebrowser'] = ev("""(function(){ var t=document.body.innerText;
|
||||
return {path: location.pathname, updateBanner: /update is available/i.test(t), installApp: /install app/i.test(t),
|
||||
releaseBanners: document.querySelectorAll('.release-banner').length,
|
||||
globals: (function(){ try { var g=window.globalVars||{}; return {disablePWAInstall: g.disablePWAInstall, updateAvailable: g.updateAvailable}; } catch(e) { return null; } })()};})()""")
|
||||
shot('filebrowser-sidebar')
|
||||
print(json.dumps(report, indent=1, ensure_ascii=False))
|
||||
finally:
|
||||
proc.terminate()
|
||||
subprocess.run(['rm', '-rf', prof])
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""romm_saves.py — R-1097 proof on demo-hp 9201 through the product's endpoints (box_walk helpers, TARGET=9201).
|
||||
Sync the catalog, restart RomM (the applied compose gains romm_assets), write a save where RomM writes saves, run the
|
||||
by-day night chain (the sanctioned backup trigger), remove RomM KEEPING its backups, restore from the backups page,
|
||||
and read the save back. Env: SC (0600 scratch with .ctlpw), EV. The save is a dummy file; no secret is printed."""
|
||||
import os, sys, time, hashlib
|
||||
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
||||
import box_walk as w
|
||||
APP = "romm"
|
||||
SAVE_DIR = "/romm/assets/romm/users/1/saves/gba/r1097-proof"
|
||||
SAVE = SAVE_DIR + "/r1097-proof.sav"
|
||||
log = open(os.path.join(w.EV, "romm-saves.txt"), "a", buffering=1)
|
||||
def say(*a):
|
||||
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
|
||||
def mounts():
|
||||
return w.guest("docker inspect romm --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{println}}{{end}}' "
|
||||
"| sed -E 's/volume [0-9a-f]{64}/volume <ANONYMOUS>/'").strip()
|
||||
def save_state():
|
||||
return w.guest(f"docker exec romm sh -c 'ls -la {SAVE_DIR} 2>&1; sha256sum {SAVE} 2>&1' | tail -3").strip()
|
||||
|
||||
w.login()
|
||||
say("== 1. sync the catalog")
|
||||
code, d = w.ctl("POST", "/api/sync"); say("sync ->", code, str(d)[:200])
|
||||
say("applied compose names romm_assets:", w.guest("grep -c romm_assets /opt/docker/stacks/romm/docker-compose.yml").strip())
|
||||
say("== 2. restart RomM (Újraindítás)")
|
||||
code, d = w.ctl("POST", f"/api/stacks/{APP}/restart"); say("restart ->", code, str(d)[:160])
|
||||
for _ in range(40):
|
||||
time.sleep(5)
|
||||
if w.stack(APP).get("state") == "running":
|
||||
break
|
||||
say("state:", w.stack(APP).get("state")); say("mounts after restart:\n" + mounts())
|
||||
say("== 3. a save where RomM writes saves (dummy bytes, written with docker exec into the running container)")
|
||||
w.guest(f"docker exec romm sh -c 'mkdir -p {SAVE_DIR} && head -c 32768 /dev/urandom > {SAVE}'")
|
||||
before = save_state(); say(before)
|
||||
say("== 4. the by-day backup (night chain)")
|
||||
t0 = time.time(); code, d = w.ctl("POST", "/api/debug/backup/night-chain"); say("night-chain ->", code, str(d)[:160])
|
||||
for _ in range(180):
|
||||
time.sleep(10)
|
||||
tail = w.guest("docker logs --since 60m felhom-controller 2>&1 | grep 'night-chain' | tail -1").strip()
|
||||
if "finished in" in tail or "REFUSED" in tail:
|
||||
break
|
||||
say("night chain last line:", tail[-220:], f"after {round(time.time()-t0)}s")
|
||||
say("romm volume dumps now:", w.guest("ls -la /mnt/felhom-drives/hdd_1/backups/*/romm/volume-dumps/ /mnt/felhom-drives/hdd_1/*/romm/recovery-unit/volume-dumps/ 2>/dev/null; find /mnt/felhom-drives/hdd_1 -name 'romm_romm_assets.tar' -newer /proc/1 2>/dev/null | head -5"))
|
||||
say("== 5. remove RomM, KEEPING its backups")
|
||||
w.ctl("POST", f"/api/stacks/{APP}/stop"); time.sleep(10)
|
||||
code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": False, "remove_backups": False})
|
||||
say("remove ->", code, str(d)[:200])
|
||||
time.sleep(5)
|
||||
say("volumes left:", w.guest("docker volume ls --format '{{.Name}}' | grep -i romm").strip() or "(none)")
|
||||
say("== 6. restore from the backups page")
|
||||
r = w.restore(APP); say("restore:", {k: r.get(k) for k in ("ok", "snapshot_id", "http", "seconds")})
|
||||
for _ in range(60):
|
||||
time.sleep(5)
|
||||
if w.stack(APP).get("state") == "running":
|
||||
break
|
||||
say("state:", w.stack(APP).get("state")); say("mounts after restore:\n" + mounts())
|
||||
after = save_state(); say(after)
|
||||
same = before.split()[-2:] == after.split()[-2:]
|
||||
say("== VERDICT: the save came back byte-identical:", same)
|
||||
|
After Width: | Height: | Size: 168 KiB |
@@ -0,0 +1,17 @@
|
||||
after login url: /launcher
|
||||
{
|
||||
"path": "/apps/calibre-web/data",
|
||||
"keyField": {
|
||||
"name": "fab_encryption_key",
|
||||
"autocomplete": "new-password",
|
||||
"type": "password",
|
||||
"valueLength": 0
|
||||
},
|
||||
"oldIdPresent": false,
|
||||
"statusRows": 0,
|
||||
"targetLabels": 1,
|
||||
"form": true,
|
||||
"warnCallout": true,
|
||||
"scrollWidth": 1270,
|
||||
"console": []
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
every box's newest controller report (hub DB + WAL, one tar, read 2026-10-16T16:41:33Z):
|
||||
demo-hp 0.322.0-rc.1 https://felhom.enkisfelhom.hu 2026-10-16 16:40:15
|
||||
tester-1 0.321.0 https://felhom.enkicsifelhom.hu 2026-10-16 16:36:41
|
||||
demo-felhom 0.321.0 https://felhom.demo-felhom.eu 2026-10-16 16:36:35
|
||||
Tester-2 0.292.0 https://felhom.sajatfelhom.hu 2026-10-04 18:06:24
|
||||
@@ -0,0 +1,40 @@
|
||||
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
|
||||
== tester-1 (enkicsifelhom.hu): 13 public name(s)
|
||||
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
|
||||
== demo-hp (enkisfelhom.hu): 14 public name(s)
|
||||
OK http://arcade.enkisfelhom.hu/r1033-check?q=1 -> 301 https://arcade.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
|
||||
== demo-felhom (demo-felhom.eu): 8 public name(s)
|
||||
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1
|
||||
== every public name redirects http -> https
|
||||
@@ -0,0 +1,3 @@
|
||||
Tester 2 (sajatfelhom.hu), read only, 2026-10-16T16:42:09Z:
|
||||
http://felhom.sajatfelhom.hu/login -> 530 server=cloudflare
|
||||
https://felhom.sajatfelhom.hu/login -> 530 server=cloudflare
|
||||
@@ -0,0 +1,14 @@
|
||||
key length read: 64
|
||||
GET /api/config with the hub key -> 401
|
||||
POST /api/config with the hub key -> 401
|
||||
GET /api/config/ with the hub key -> 401
|
||||
POST /api/config/ with the hub key -> 401
|
||||
GET /api/config/apply with the hub key -> 401
|
||||
POST /api/config/apply with the hub key -> 401
|
||||
GET /api/config/hash with the hub key -> 401
|
||||
POST /api/config/hash with the hub key -> 401
|
||||
GET /api/selfupdate/check with the hub key -> 401
|
||||
POST /api/selfupdate/check with the hub key -> 401
|
||||
GET /api/geo/settings with the hub key -> 401
|
||||
POST /api/geo/settings with the hub key -> 401
|
||||
control: GET /api/health (no auth) -> 200
|
||||
@@ -0,0 +1,4 @@
|
||||
ctl-0.322.0-rc.2.bin: go1.27.2
|
||||
ctl-0.322.0-rc.2: 1 vulnerabilities reported
|
||||
hub-0.156.0.bin: go1.27.2
|
||||
hub-0.156.0: 1 vulnerabilities reported
|
||||
@@ -0,0 +1,4 @@
|
||||
ctl-0.321.0.bin: go1.24.13
|
||||
ctl-0.321.0: 62 vulnerabilities reported
|
||||
hub-0.155.0.bin: go1.24.13
|
||||
hub-0.155.0: 62 vulnerabilities reported
|
||||
@@ -0,0 +1,40 @@
|
||||
2026/10/16 16:50:06 offsiteapply.go:148: [INFO] [offsite-apply] settle-gate: GO — at/above floor 0.321.0 (we are 0.322.0-rc.2), no managed update running
|
||||
2026/10/16 16:50:43 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:57016: [db-dump tier2 offsite update-leg]
|
||||
2026/10/16 16:50:43 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
|
||||
2026/10/16 16:50:43 night_chain.go:77: [INFO] [night-chain] db-dump: started
|
||||
2026/10/16 16:52:40 night_chain.go:82: [INFO] [night-chain] db-dump: done in 1m57s
|
||||
2026/10/16 16:52:40 night_chain.go:77: [INFO] [night-chain] tier2: started
|
||||
2026/10/16 16:53:03 night_chain.go:82: [INFO] [night-chain] tier2: done in 22s
|
||||
2026/10/16 16:53:03 night_chain.go:77: [INFO] [night-chain] offsite: started
|
||||
2026/10/16 16:53:03 offbox.go:1021: [INFO] [offbox] backup run started (15 app(s) toggled)
|
||||
2026/10/16 16:54:56 scheduler.go:450: [INFO] [scheduler] Running job: offsite-credential-retry
|
||||
2026/10/16 16:54:56 scheduler.go:467: [INFO] [scheduler] Job offsite-credential-retry completed (took 0s)
|
||||
2026/10/16 16:55:05 offbox.go:1068: [INFO] [offbox] pre-push dump leg completed in 2m2.212s — snapshot pair is coherent
|
||||
2026/10/16 16:55:08 offbox_capture.go:45: [WARN] [offbox] prowlarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:08 offbox_capture.go:45: [WARN] [offbox] qbittorrent: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:08 offbox_capture.go:45: [WARN] [offbox] radarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:08 offbox_capture.go:45: [WARN] [offbox] sonarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:12 offbox.go:1504: [INFO] [offbox] backed up bookstack (/mnt/sys_drive/felhom-data/backups/primary/bookstack, 0 mandatory path(s))
|
||||
2026/10/16 16:55:15 offbox.go:1504: [INFO] [offbox] backed up docmost (/mnt/sys_drive/felhom-data/backups/primary/docmost, 0 mandatory path(s))
|
||||
2026/10/16 16:55:17 offbox.go:1504: [INFO] [offbox] backed up opengist (/mnt/sys_drive/felhom-data/backups/primary/opengist, 0 mandatory path(s))
|
||||
2026/10/16 16:55:20 offbox.go:1504: [INFO] [offbox] backed up paperless-ngx (/mnt/felhom-drives/hdd_1/backups/primary/paperless-ngx, 1 mandatory path(s))
|
||||
2026/10/16 16:55:22 offbox.go:1504: [INFO] [offbox] backed up privatebin (/mnt/sys_drive/felhom-data/backups/primary/privatebin, 0 mandatory path(s))
|
||||
2026/10/16 16:55:22 offbox_capture.go:45: [WARN] [offbox] prowlarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:24 offbox.go:1501: [WARN] [offbox] backed up prowlarr (/mnt/sys_drive/felhom-data/backups/primary/prowlarr, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:55:26 offbox.go:1501: [WARN] [offbox] backed up bentopdf (/mnt/sys_drive/felhom-data/backups/primary/bentopdf, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:55:28 offbox.go:1504: [INFO] [offbox] backed up kimai (/mnt/sys_drive/felhom-data/backups/primary/kimai, 0 mandatory path(s))
|
||||
2026/10/16 16:55:30 offbox.go:1504: [INFO] [offbox] backed up plex (/mnt/felhom-drives/hdd_1/backups/primary/plex, 0 mandatory path(s))
|
||||
2026/10/16 16:55:30 offbox.go:1420: [WARN] [offbox] qbittorrent: no recovery unit found on any connected drive — skipping
|
||||
2026/10/16 16:55:30 offbox_capture.go:45: [WARN] [offbox] radarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:33 offbox.go:1501: [WARN] [offbox] backed up radarr (/mnt/felhom-drives/hdd_1/backups/primary/radarr, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:55:36 offbox.go:1504: [INFO] [offbox] backed up romm (/mnt/felhom-drives/hdd_1/backups/primary/romm, 0 mandatory path(s))
|
||||
2026/10/16 16:55:38 offbox.go:1504: [INFO] [offbox] backed up calibre-web (/mnt/felhom-drives/hdd_1/backups/primary/calibre-web, 1 mandatory path(s))
|
||||
2026/10/16 16:55:42 offbox.go:1504: [INFO] [offbox] backed up home-assistant (/mnt/sys_drive/felhom-data/backups/primary/home-assistant, 0 mandatory path(s))
|
||||
2026/10/16 16:55:42 offbox_capture.go:45: [WARN] [offbox] sonarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:55:44 offbox.go:1501: [WARN] [offbox] backed up sonarr (/mnt/felhom-drives/hdd_1/backups/primary/sonarr, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:55:46 offbox_window.go:231: [INFO] [offbox] retention skipped (after-run): no clean-up window now (not due (last window 2026-10-12T02:16:53Z)) — nothing deleted (decision 68)
|
||||
2026/10/16 16:55:52 offbox.go:1266: [INFO] [offbox] backup OK: 14 app(s) backed up, 1 skipped (no unit), 186 snapshot(s), 2m43s
|
||||
2026/10/16 16:55:52 night_chain.go:82: [INFO] [night-chain] offsite: done in 2m49s
|
||||
2026/10/16 16:55:52 night_chain.go:77: [INFO] [night-chain] update-leg: started
|
||||
2026/10/16 16:55:52 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
|
||||
2026/10/16 16:55:52 night_chain.go:93: [INFO] [night-chain] finished in 5m9s
|
||||
@@ -0,0 +1,2 @@
|
||||
{"data":{"legs":["db-dump","tier2","offsite","update-leg"]},"message":"started","ok":true}
|
||||
202
|
||||
@@ -0,0 +1,40 @@
|
||||
2026/10/16 16:40:18 offsiteapply.go:148: [INFO] [offsite-apply] settle-gate: GO — at/above floor 0.321.0 (we are 0.322.0-rc.1), no managed update running
|
||||
2026/10/16 16:41:03 night_chain.go:108: [INFO] [night-chain] manual run started from 172.18.0.4:42566: [db-dump tier2 offsite update-leg]
|
||||
2026/10/16 16:41:03 night_chain.go:84: [INFO] [night-chain] manual run of tonight's chain: dump → second drive → off-site → update leg
|
||||
2026/10/16 16:41:03 night_chain.go:77: [INFO] [night-chain] db-dump: started
|
||||
2026/10/16 16:42:59 night_chain.go:82: [INFO] [night-chain] db-dump: done in 1m56s
|
||||
2026/10/16 16:42:59 night_chain.go:77: [INFO] [night-chain] tier2: started
|
||||
2026/10/16 16:43:21 night_chain.go:82: [INFO] [night-chain] tier2: done in 23s
|
||||
2026/10/16 16:43:21 night_chain.go:77: [INFO] [night-chain] offsite: started
|
||||
2026/10/16 16:43:21 offbox.go:1021: [INFO] [offbox] backup run started (15 app(s) toggled)
|
||||
2026/10/16 16:45:08 scheduler.go:450: [INFO] [scheduler] Running job: offsite-credential-retry
|
||||
2026/10/16 16:45:08 scheduler.go:467: [INFO] [scheduler] Job offsite-credential-retry completed (took 0s)
|
||||
2026/10/16 16:45:22 offbox.go:1068: [INFO] [offbox] pre-push dump leg completed in 2m0.527s — snapshot pair is coherent
|
||||
2026/10/16 16:45:25 offbox_capture.go:45: [WARN] [offbox] sonarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:25 offbox_capture.go:45: [WARN] [offbox] qbittorrent: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:25 offbox_capture.go:45: [WARN] [offbox] prowlarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:25 offbox_capture.go:45: [WARN] [offbox] radarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:29 offbox.go:1504: [INFO] [offbox] backed up paperless-ngx (/mnt/felhom-drives/hdd_1/backups/primary/paperless-ngx, 1 mandatory path(s))
|
||||
2026/10/16 16:45:31 offbox.go:1504: [INFO] [offbox] backed up privatebin (/mnt/sys_drive/felhom-data/backups/primary/privatebin, 0 mandatory path(s))
|
||||
2026/10/16 16:45:31 offbox_capture.go:45: [WARN] [offbox] sonarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:33 offbox.go:1501: [WARN] [offbox] backed up sonarr (/mnt/felhom-drives/hdd_1/backups/primary/sonarr, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:45:36 offbox.go:1504: [INFO] [offbox] backed up calibre-web (/mnt/felhom-drives/hdd_1/backups/primary/calibre-web, 1 mandatory path(s))
|
||||
2026/10/16 16:45:36 offbox.go:1420: [WARN] [offbox] qbittorrent: no recovery unit found on any connected drive — skipping
|
||||
2026/10/16 16:45:38 offbox.go:1504: [INFO] [offbox] backed up romm (/mnt/felhom-drives/hdd_1/backups/primary/romm, 0 mandatory path(s))
|
||||
2026/10/16 16:45:38 offbox_capture.go:45: [WARN] [offbox] prowlarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:40 offbox.go:1501: [WARN] [offbox] backed up prowlarr (/mnt/sys_drive/felhom-data/backups/primary/prowlarr, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:45:40 offbox_capture.go:45: [WARN] [offbox] radarr: not deployed — offsite push is unit-only (mandatory userdata not resolvable)
|
||||
2026/10/16 16:45:42 offbox.go:1501: [WARN] [offbox] backed up radarr (/mnt/felhom-drives/hdd_1/backups/primary/radarr, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:45:44 offbox.go:1504: [INFO] [offbox] backed up opengist (/mnt/sys_drive/felhom-data/backups/primary/opengist, 0 mandatory path(s))
|
||||
2026/10/16 16:45:47 offbox.go:1504: [INFO] [offbox] backed up plex (/mnt/felhom-drives/hdd_1/backups/primary/plex, 0 mandatory path(s))
|
||||
2026/10/16 16:45:49 offbox.go:1501: [WARN] [offbox] backed up bentopdf (/mnt/sys_drive/felhom-data/backups/primary/bentopdf, 0 mandatory path(s)) — but the recovery unit carried NO database dump and NO volume tar, so this snapshot holds none of the app's data; the next run with a dump leg will replace it
|
||||
2026/10/16 16:45:52 offbox.go:1504: [INFO] [offbox] backed up bookstack (/mnt/sys_drive/felhom-data/backups/primary/bookstack, 0 mandatory path(s))
|
||||
2026/10/16 16:45:54 offbox.go:1504: [INFO] [offbox] backed up docmost (/mnt/sys_drive/felhom-data/backups/primary/docmost, 0 mandatory path(s))
|
||||
2026/10/16 16:45:57 offbox.go:1504: [INFO] [offbox] backed up home-assistant (/mnt/sys_drive/felhom-data/backups/primary/home-assistant, 0 mandatory path(s))
|
||||
2026/10/16 16:45:59 offbox.go:1504: [INFO] [offbox] backed up kimai (/mnt/sys_drive/felhom-data/backups/primary/kimai, 0 mandatory path(s))
|
||||
2026/10/16 16:46:01 offbox_window.go:231: [INFO] [offbox] retention skipped (after-run): no clean-up window now (not due (last window 2026-10-12T02:16:53Z)) — nothing deleted (decision 68)
|
||||
2026/10/16 16:46:06 offbox.go:1266: [INFO] [offbox] backup OK: 14 app(s) backed up, 1 skipped (no unit), 172 snapshot(s), 2m40s
|
||||
2026/10/16 16:46:06 night_chain.go:82: [INFO] [night-chain] offsite: done in 2m44s
|
||||
2026/10/16 16:46:06 night_chain.go:77: [INFO] [night-chain] update-leg: started
|
||||
2026/10/16 16:46:06 night_chain.go:82: [INFO] [night-chain] update-leg: done in 0s
|
||||
2026/10/16 16:46:06 night_chain.go:93: [INFO] [night-chain] finished in 5m3s
|
||||
@@ -0,0 +1,5 @@
|
||||
== gitea.dooplex.hu/admin/felhom-controller:0.322.0-rc.3
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.322.0-rc.3 (debia debian crit 4 (fixable 0) high 136 (fixable 0)
|
||||
usr/libexec/docker/cli-plugins/docker-compose gobinary crit 0 (fixable 0) high 4 (fixable 4)
|
||||
usr/local/bin/felhom-controller gobinary crit 0 (fixable 0) high 0 (fixable 0)
|
||||
TOTAL crit 4 (fixable 0) high 140 (fixable 4)
|
||||
@@ -0,0 +1,9 @@
|
||||
== gitea.dooplex.hu/admin/felhom-controller:0.322.0-rc.2
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.322.0-rc.2 (debia debian crit 6 (fixable 2) high 144 (fixable 8)
|
||||
usr/libexec/docker/cli-plugins/docker-compose gobinary crit 0 (fixable 0) high 4 (fixable 4)
|
||||
usr/local/bin/felhom-controller gobinary crit 0 (fixable 0) high 0 (fixable 0)
|
||||
TOTAL crit 6 (fixable 2) high 148 (fixable 12)
|
||||
== gitea.dooplex.hu/admin/felhom-hub:0.156.0
|
||||
gitea.dooplex.hu/admin/felhom-hub:0.156.0 (alpine 3.24.2) alpine crit 0 (fixable 0) high 0 (fixable 0)
|
||||
usr/local/bin/felhom-hub gobinary crit 0 (fixable 0) high 0 (fixable 0)
|
||||
TOTAL crit 0 (fixable 0) high 0 (fixable 0)
|
||||
@@ -0,0 +1,10 @@
|
||||
== gitea.dooplex.hu/admin/felhom-controller:0.321.0
|
||||
gitea.dooplex.hu/admin/felhom-controller:0.321.0 (debian 12. debian crit 16 (fixable 12) high 159 (fixable 23)
|
||||
usr/bin/docker gobinary crit 0 (fixable 0) high 3 (fixable 3)
|
||||
usr/libexec/docker/cli-plugins/docker-compose gobinary crit 0 (fixable 0) high 4 (fixable 4)
|
||||
usr/local/bin/felhom-controller gobinary crit 0 (fixable 0) high 34 (fixable 34)
|
||||
TOTAL crit 16 (fixable 12) high 200 (fixable 64)
|
||||
== gitea.dooplex.hu/admin/felhom-hub:0.155.0
|
||||
gitea.dooplex.hu/admin/felhom-hub:0.155.0 (alpine 3.21.8) alpine crit 0 (fixable 0) high 2 (fixable 2)
|
||||
usr/local/bin/felhom-hub gobinary crit 0 (fixable 0) high 34 (fixable 34)
|
||||
TOTAL crit 0 (fixable 0) high 36 (fixable 36)
|
||||
@@ -0,0 +1,8 @@
|
||||
## no autocomplete=new-password on the export key
|
||||
logins_folders_test.go:423: hu: the export password field lacks autocomplete="new-password" or still has a login-like name
|
||||
logins_folders_test.go:423: en: the export password field lacks autocomplete="new-password" or still has a login-like name
|
||||
--- FAIL: TestAppData_OneBackupSection (0.09s)
|
||||
## a status row back above the form
|
||||
logins_folders_test.go:416: hu: the 2nd copy's status rows repeat the form
|
||||
logins_folders_test.go:419: hu: the target drive is labelled 2 times, want once (the form)
|
||||
logins_folders_test.go:416: en: the 2nd copy's status rows repeat the form
|
||||
@@ -0,0 +1,3 @@
|
||||
## R-694 on the settings tab: the Restored branch removed
|
||||
r694_restored_login_test.go:35: the restored login is offered for reveal
|
||||
--- FAIL: TestR694_TheSettingsTabDoesNotOfferAGeneratedLogin (0.07s)
|
||||
@@ -0,0 +1,10 @@
|
||||
## an /api/geo/ route without the session check (a door)
|
||||
r970_no_hub_key_door_test.go:74: GET /api/geo/countries with the hub key answered 200 — the key opens a door
|
||||
--- FAIL: TestR970_HubKeyOpensNoDoor (0.08s)
|
||||
## the /config GET case back
|
||||
r970_config_routes_test.go:31: GET /api/config answered 200, want the router's 404
|
||||
r970_config_routes_test.go:31: GET /api/config/ answered 200, want the router's 404
|
||||
--- FAIL: TestR970_ConfigRoutesAreGone (0.00s)
|
||||
## the Bearer CSRF exemption back
|
||||
r970_csrf_test.go:40: a POST carrying the hub key passed CSRF (code 200)
|
||||
--- FAIL: TestR970_HubKeyIsNoCSRFExemption (0.00s)
|
||||
@@ -0,0 +1,12 @@
|
||||
== tester-1
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.322.0 Up About a minute (healthy)
|
||||
restic 0.14.0
|
||||
== demo-hp
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.322.0 Up About a minute (healthy)
|
||||
restic 0.14.0
|
||||
== demo-felhom
|
||||
unhealthy=0 restarting=0 exited=0 created=0
|
||||
controller gitea.dooplex.hu/admin/felhom-controller:0.322.0 Up About a minute (healthy)
|
||||
restic 0.14.0
|
||||
@@ -0,0 +1,3 @@
|
||||
tester-1 Location: /customers/tester-1?flash=floor_set
|
||||
demo-hp Location: /customers/demo-hp?flash=floor_set
|
||||
demo-felhom Location: /customers/demo-felhom?flash=floor_set
|
||||
@@ -0,0 +1,3 @@
|
||||
demo-hp Location: /customers/demo-hp?flash=floor_set
|
||||
tester-1 Location: /customers/tester-1?flash=floor_set
|
||||
demo-felhom Location: /customers/demo-felhom?flash=floor_set
|
||||
@@ -0,0 +1,3 @@
|
||||
felhom.enkisfelhom.hu 200
|
||||
felhom.enkicsifelhom.hu 200
|
||||
felhom.demo-felhom.eu 200
|
||||
@@ -0,0 +1,9 @@
|
||||
integrity ok
|
||||
newest report 2026-10-16 16:54:54 now 2026-10-16 17:06:26
|
||||
min_controller_version ('0.322.0',)
|
||||
min_controller_version_declared_min_agent ('0.322.0=0.158.1',)
|
||||
customer floor columns []
|
||||
customer_configs ['min_controller_version', 'min_controller_declared_min_agent', 'min_controller_set_at']
|
||||
('demo-felhom', '', '', '')
|
||||
('demo-hp', '', '', '')
|
||||
('tester-1', '', '', '')
|
||||
@@ -0,0 +1,3 @@
|
||||
19:04:50
|
||||
HTTP/1.1 303 See Other
|
||||
Location: /configuration?flash=floor_set
|
||||
@@ -0,0 +1,40 @@
|
||||
control NEGATIVE (a stub answering 200 over plain http): convicted, as it must (200 -)
|
||||
== tester-1 (enkicsifelhom.hu): 13 public name(s)
|
||||
OK control POSITIVE http://felhom.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://files.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://media.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://media.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paperless.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paperless.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://paste.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://plex.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://prowlarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://prowlarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://radarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://radarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://sonarr.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://sonarr.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://torrent.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://torrent.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://transmission.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://transmission.enkicsifelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkicsifelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkicsifelhom.hu/r1033-check?q=1
|
||||
== demo-hp (enkisfelhom.hu): 14 public name(s)
|
||||
OK http://arcade.enkisfelhom.hu/r1033-check?q=1 -> 301 https://arcade.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://books.enkisfelhom.hu/r1033-check?q=1 -> 301 https://books.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://docs.enkisfelhom.hu/r1033-check?q=1 -> 301 https://docs.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://felhom.enkisfelhom.hu/r1033-check?q=1 -> 301 https://felhom.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://files.enkisfelhom.hu/r1033-check?q=1 -> 301 https://files.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://gist.enkisfelhom.hu/r1033-check?q=1 -> 301 https://gist.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://ha.enkisfelhom.hu/r1033-check?q=1 -> 301 https://ha.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1 -> 301 https://no-such-name-r1033.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://papir.enkisfelhom.hu/r1033-check?q=1 -> 301 https://papir.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://paste.enkisfelhom.hu/r1033-check?q=1 -> 301 https://paste.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://pdf.enkisfelhom.hu/r1033-check?q=1 -> 301 https://pdf.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://plex.enkisfelhom.hu/r1033-check?q=1 -> 301 https://plex.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://travel.enkisfelhom.hu/r1033-check?q=1 -> 301 https://travel.enkisfelhom.hu/r1033-check?q=1
|
||||
OK http://wiki.enkisfelhom.hu/r1033-check?q=1 -> 301 https://wiki.enkisfelhom.hu/r1033-check?q=1
|
||||
== demo-felhom (demo-felhom.eu): 8 public name(s)
|
||||
OK http://felhom.demo-felhom.eu/r1033-check?q=1 -> 301 https://felhom.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://files.demo-felhom.eu/r1033-check?q=1 -> 301 https://files.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://gist.demo-felhom.eu/r1033-check?q=1 -> 301 https://gist.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1 -> 301 https://no-such-name-r1033.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://prowlarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://prowlarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://radarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://radarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://sonarr.demo-felhom.eu/r1033-check?q=1 -> 301 https://sonarr.demo-felhom.eu/r1033-check?q=1
|
||||
OK http://torrent.demo-felhom.eu/r1033-check?q=1 -> 301 https://torrent.demo-felhom.eu/r1033-check?q=1
|
||||
== every public name redirects http -> https
|
||||