Logins and folders (controller 0.320.0 released): design, R-1100..R-1102 opened, STATUS, evidence, report
gates / gates (push) Successful in 7m4s

02 "Logins and folders" [DESIGN]: an infra stack is part of the box on the app page; one login answer per
installed app (catalog app_info.login_kind); FileBrowser's new-password press (its CLI, server stopped);
"Fájlok ↗" and the per-drive media folders, a folder with no source is text. Register 176 -> 179.
STATUS: look at FileBrowser, RomM and Plex on demo-hp; say yes for the N100's press; decision D10 (R-1102).

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-16 15:53:12 +02:00
parent bc9a871391
commit ea8e67f341
41 changed files with 1173 additions and 1 deletions
@@ -0,0 +1,237 @@
=== demo-hp
VMID Status Lock Name
9201 running demo-hp
9202 running demo-hp-scratch
9401 stopped upgrade-harness
felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.319.0
filebrowser gtstef/filebrowser:1.5.6-stable
SETTINGS:
config.yaml
docker-compose.yml
- path: "/srv/beolvasas"
name: "Beolvasás"
- path: "/srv/hdd_1"
name: "NVME 1TB"
- path: "/srv/megorzott"
name: "Megőrzött adatok"
- /mnt/felhom-drives/hdd_1/userdata:/srv/hdd_1
- /mnt/sys_drive/felhom-data/userdata/import:/srv/beolvasas
- /mnt/felhom-drives/hdd_1/appdata/romm:/srv/megorzott/hdd_1-romm:ro
- /mnt/felhom-drives/hdd_1/kept/romm/2026-10-16_120728:/srv/megorzott/hdd_1-romm-2026-10-16_120728:ro
- /mnt/felhom-drives/hdd_1/kept/romm/2026-10-16_113539:/srv/megorzott/hdd_1-romm-2026-10-16_113539:ro
=== felhom-pve
VMID Status Lock Name
9201 running demo-felhom
felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.319.0
filebrowser gtstef/filebrowser:1.5.6-stable
SETTINGS:
config.yaml
docker-compose.yml
- path: "/srv/beolvasas"
name: "Beolvasás"
- path: "/srv/hdd_1"
name: "Külső tárhely (hdd_1)"
- path: "/srv/megorzott"
name: "Megőrzött adatok"
- /mnt/felhom-drives/hdd_1/userdata:/srv/hdd_1
- /mnt/sys_drive/felhom-data/userdata/import:/srv/beolvasas
- /mnt/felhom-drives/hdd_1/appdata/romm:/srv/megorzott/hdd_1-romm:ro
- /mnt/felhom-drives/hdd_1/appdata/paperless:/srv/megorzott/hdd_1-paperless:ro
- /mnt/felhom-drives/hdd_1/appdata/nextcloud:/srv/megorzott/hdd_1-nextcloud:ro
- /mnt/felhom-drives/hdd_1/appdata/immich:/srv/megorzott/hdd_1-immich:ro
=== demo-hp
/mnt->/mnt /etc/felhom-bootstrap->/etc/felhom-bootstrap /var/lib/docker/volumes/felhom-controller-data/_data->/opt/docker/felhom-controller /opt/docker/stacks->/opt/docker/stacks /var/run/docker.sock->/var/run/docker.sock
/mnt/felhom-drives/hdd_1/userdata/:
documents
downloads
import
media
roms
roms-bios
/mnt/felhom-drives/hdd_1/userdata/media/:
audiobooks
books
comics
grimmory
metube
movies
music
nzbget
photos
podcasts
torrents
transmission
tv
usenet
actualbudget
adventurelog
audiobookshelf
bentopdf
bookstack
calcom
calibre-web
claper
cloudflared
code-server
crafty-controller
dawarich
docmost
emby
filebrowser
ghost
gitea
glance
gokapi
grafana
gramps-web
grimmory
grocy
home-assistant
homebox
homepage
immich
jellyfin
karakeep
kimai
komga
lubelogger
mealie
metube
n8n
navidrome
nextcloud
nzbget
onlyoffice
opengist
outline
paperless-ngx
papra
plant-it
plex
privatebin
prowlarr
qbittorrent
radarr
radicale
rallly
recipe-importer
romm
sabnzbd
seerr
sonarr
sparkyfitness
tandoor
termix
traefik
transmission
uptime-kuma
vaultwarden
vikunja
wanderer
wger
wishlist
zipline
=== felhom-pve
/mnt->/mnt /etc/felhom-bootstrap->/etc/felhom-bootstrap /var/lib/docker/volumes/felhom-controller-data/_data->/opt/docker/felhom-controller /opt/docker/stacks->/opt/docker/stacks /var/run/docker.sock->/var/run/docker.sock
/mnt/felhom-drives/hdd_1/userdata/:
documents
downloads
import
media
roms
roms-bios
/mnt/felhom-drives/hdd_1/userdata/media/:
audiobooks
books
comics
grimmory
metube
movies
music
nzbget
photos
podcasts
torrents
transmission
tv
usenet
actualbudget
adventurelog
audiobookshelf
bentopdf
bookstack
calcom
calibre-web
claper
cloudflared
code-server
crafty-controller
dawarich
docmost
emby
filebrowser
ghost
gitea
glance
gokapi
grafana
gramps-web
grimmory
grocy
home-assistant
homebox
homepage
immich
jellyfin
karakeep
kimai
komga
lubelogger
mealie
metube
n8n
navidrome
nextcloud
nzbget
onlyoffice
opengist
outline
paperless-ngx
papra
plant-it
plex
privatebin
prowlarr
qbittorrent
radarr
radicale
rallly
recipe-importer
romm
sabnzbd
seerr
sonarr
sparkyfitness
tandoor
termix
traefik
transmission
uptime-kuma
vaultwarden
vikunja
wanderer
wger
wishlist
zipline
=== demo-hp
FILE /var/lib/docker/volumes/felhom-controller-data/_data/data/settings.json
"filebrowser_admin_state": "operator"
"filebrowser_admin_decided_at": "2026-09-15T08:52:10Z"
=== felhom-pve
FILE /var/lib/docker/volumes/felhom-controller-data/_data/data/settings.json
"filebrowser_admin_state": "operator"
"filebrowser_admin_decided_at": "2026-09-16T15:37:26Z"
@@ -0,0 +1,48 @@
# P0 — why FileBrowser showed „Telepítés" and lost its password (2026-10-16, read only)
Method: source read (controller `main` 39bf562 = v0.319.0) + the live page through
`https://felhom.enkisfelhom.hu` (curl, logged in) + names/states read with `pct exec 9201` on both boxes.
No value of any secret was read or printed; only the state word of `filebrowser_admin_state`.
## 1. Why `/apps/filebrowser` renders as not installed, with an Install button
- FileBrowser is base infrastructure: `stacks/infra.go` `EnsureBaseStack` writes `/opt/docker/stacks/filebrowser/`
(compose + config.yaml) and starts it. It never writes `app.yaml`.
- `stacks/manager.go` `ScanStacks` lists every dir under the stacks dir with a compose file. FileBrowser is in the list
with `Deployed = appCfg != nil && appCfg.Deployed` = **false** (no app.yaml) and `Protected = true`.
Its metadata is the no-`.felhom.yml` default (`Slug = "filebrowser"`, DisplayName „Filebrowser").
- Every other surface already counts `Deployed || Protected` as „on the box" (dashboard, launcher, app list —
`handlers.go` ~154/315/361, `stacks.html` L30/L76). **The v0.315.0 app page (`e66d466`) uses `found.Deployed` only**
(`web/app_page.go` `appPageHandler`): not deployed → no tab bar, the guide tab is the whole page, and the guide
renders the Install CTA (`app_guide.html` L45, `canInstall` is true for metadata with no lifecycle).
- The R-513 block in `appCommonData` (handlers.go ~L856) still sets `InitialCreds` for FileBrowser — but the new
page shows `InitialCreds` only on the SETTINGS tab, which a „not deployed" app never reaches. The old
`app_info.html` rendered `{{if .InitialCreds}}` without a Deployed gate; that is how it showed before.
- **Commit that changed it: `e66d466` (v0.315.0, one app page with four tabs).**
- Extra finding: `DeployStack` has NO protected-stack guard (it refuses only Deployed / not installable). The Install
button led to a wizard whose POST would have deployed the infra stack as an app. Guarded in this task.
Live confirmation (demo-hp 9201, controller 0.319.0): `demo-hp-apps-filebrowser-markers.txt` — the page carries
`data-install-cta`, „Telepítés", no `data-tab`, no `initcred`.
## 2. FileBrowser admin state (state word only) — `P0-state.txt`
| box | state | decided at |
|---|---|---|
| demo-hp 9201 | `operator` | 2026-09-15T08:52:10Z |
| demo-felhom 9201 (N100, lived-in) | `operator` | 2026-09-16T15:37:26Z |
Both boxes: nobody knows the password (R-513 found admin/admin refused and left it).
## 3. FileBrowser sources (rendered `config.yaml`) — `P0-state.txt`
| box | drive source | import | kept |
|---|---|---|---|
| demo-hp | `NVME 1TB` → `/srv/hdd_1` = `/mnt/felhom-drives/hdd_1/userdata` | `Beolvasás` | `Megőrzött adatok` |
| demo-felhom | `Külső tárhely (hdd_1)` → `/srv/hdd_1` = `/mnt/felhom-drives/hdd_1/userdata` | `Beolvasás` | `Megőrzött adatok` |
Each box has ONE drive. The drive source root is the drive's `userdata/`, so `media/` and every
`media/<movies|tv|music|torrents|transmission|…>` folder is reachable as `<drive source>/media/<sub>` — measured: the
folders exist on both boxes (`ls /mnt/felhom-drives/hdd_1/userdata/media/`). Source names come from the storage
path's label, else the mount basename — the same rule `fbSourceNameForApp` uses, so the link builder and the
sidebar agree by construction.
@@ -0,0 +1,5 @@
1 /install" class="btn btn-primary" data-install-cta="1"
1 Telepítés
1 Telepítés gombbal.
1 Telepítési konfiguráció (
1 app-hero-name">Filebrowser
@@ -0,0 +1,33 @@
# P1b — resetting FileBrowser's admin password WITHOUT the old one (spike, demo-hp guest 9201 only, 2026-10-16)
**Verdict: a safe reset exists. Built.**
## The mechanism, read at the pinned tag
`gtstef/filebrowser:1.5.6-stable` = `gtsteffaniak/filebrowser` tag `v1.5.6-stable` (`5d9b4df`).
- **CLI `filebrowser set -u <user>,<password> -c <config>`** — `backend/cmd/cli.go:60-67` (the flag set), `:117-126`
(dispatch), `:206-269` `setUser`: an existing user gets `user.Password = password`, its TOTP secret cleared, and
`store.Users.Save(user, true, false)` (hashes the password). It needs NO old password.
- It opens the database with `storm.Open(path)` (`backend/database/storage/storage.go:26`) with no timeout: while the
server runs, the bolt lock is held and the CLI would wait forever. So the server is STOPPED for the CLI, and the CLI
runs in a one-shot container with the running container's volumes (`docker run --rm --volumes-from filebrowser`).
- Not chosen: `FILEBROWSER_ADMIN_PASSWORD` / `auth.adminPassword` (`backend/common/settings/config.go:923-926`,
applied in `backend/cmd/user.go:50-57` `validateUserInfo`) — it re-applies on EVERY start (R-513 measured the same on
1.3.3), so it would need a compose change plus two recreates, and the value would sit in the container env.
## Measured (`spike-step1.txt`, `spike-step2.txt`, `spike-step3.txt`, `spike-cleanup.txt`; script `fbspike.sh`)
1. State `operator`, password unknown (R-513 left it 2026-09-15). A DB backup copied first.
2. Reset to K1 with the server stopped: CLI rc 0, „successfully updated user", healthy 7 s after the stop; **K1 → 200**.
3. A test user and a test share made as K1 (user 201, share 200). Settings hash `12a6170ac405fa72`.
4. Reset to K2 without K1: **K2 → 200, K1 → 401, admin/admin → 401**; users `admin` + `fbspike`, the share, and
the settings hash **unchanged**.
5. Test user and share deleted (200/200).
Passwords were generated on DooPlex (16 letters and digits — never a comma, which the CLI's `user,password` form would
split on), sent as files (0600), used from files, and never printed. The CLI carries the password on its command line
for about one second inside the guest; this is the only exposure and it is inside the household's own box.
After the spike demo-hp's FileBrowser password is K2 (kept out-of-band in the session scratchpad) and the controller
still records `operator` — the built button replaces it (Part 1b build).
@@ -0,0 +1,64 @@
#!/bin/sh
# P1b spike — runs INSIDE demo-hp guest 9201. Passwords come from files in $D (0600); never printed.
# Usage: fbspike.sh <step> steps: backup | reset <pwfile> | login <pwfile> | inventory <pwfile> | seed <pwfile> | cleanup <pwfile>
set -u
D=/root/fbspike
IMG=gtstef/filebrowser:1.5.6-stable
IP=$(docker inspect filebrowser --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 2>/dev/null)
B="http://$IP:80"
hdr() { printf 'X-Password: %s\n' "$(cat "$1")" > $D/.h; chmod 600 $D/.h; }
login_code() { hdr "$1"; curl -s -o $D/.tok -w '%{http_code}' -X POST -H @$D/.h "$B/api/auth/login?username=admin"; rm -f $D/.h; }
tok() { login_code "$1" >/dev/null; tr -d '"' < $D/.tok; }
case "$1" in
backup)
V=$(docker volume inspect filebrowser_filebrowser_data --format '{{.Mountpoint}}')
cp -a "$V/database.db" $D/database.db.before-spike && ls -l $D/database.db.before-spike | awk '{print "backup bytes",$5}'
;;
reset)
t0=$(date +%s)
docker stop filebrowser >/dev/null && echo "stopped"
P=$(cat "$2")
timeout 60 docker run --rm --volumes-from filebrowser --user 1000:1000 --workdir /home/filebrowser \
--entrypoint /home/filebrowser/filebrowser "$IMG" set -u "admin,$P" -c /home/filebrowser/config.yaml >$D/.cli 2>&1
rc=$?; unset P
echo "cli rc=$rc"; grep -o 'successfully [a-z]* user[^ ]*' $D/.cli; rm -f $D/.cli
docker start filebrowser >/dev/null && echo "started"
for i in $(seq 1 60); do s=$(docker inspect filebrowser --format '{{.State.Health.Status}}'); [ "$s" = healthy ] && break; sleep 2; done
IP=$(docker inspect filebrowser --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'); echo "health=$s after $(( $(date +%s) - t0 ))s"
;;
login)
echo "login $(basename $2): $(login_code "$2")"; rm -f $D/.tok
;;
inventory)
T=$(tok "$2")
echo "users:"; curl -s -H "Authorization: Bearer $T" "$B/api/users" | python3 -c 'import json,sys;[print(" ",u.get("id"),u.get("username"),"admin" if u.get("permissions",{}).get("admin") else "-") for u in json.load(sys.stdin)]'
echo "shares:"; curl -s -H "Authorization: Bearer $T" "$B/api/share/list" | python3 -c 'import json,sys;d=json.load(sys.stdin);d=d if isinstance(d,list) else [];[print(" ",s.get("hash"),s.get("path"),s.get("source")) for s in d];print(" count",len(d))'
echo "settings:"; curl -s -H "Authorization: Bearer $T" "$B/api/settings" | python3 -c 'import json,sys,hashlib;d=json.load(sys.stdin);d.get("auth",{}).pop("key",None);print(" sources",[x.get("name") for x in d.get("server",{}).get("sources",[])]);print(" sha256",hashlib.sha256(json.dumps(d,sort_keys=True).encode()).hexdigest()[:16])'
rm -f $D/.tok
;;
seed)
T=$(tok "$2")
hdr "$2"; curl -s -o /dev/null -w 'create user %{http_code}\n' -X POST -H @$D/.h -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"which":[],"data":{"username":"fbspike","password":"fbspike-'"$(head -c6 /dev/urandom | od -An -tx1 | tr -d ' \n')"'","loginMethod":"password","scopes":[]}}' "$B/api/users"
rm -f $D/.h; [ -n "${3:-}" ] && { rm -f $D/.tok; exit 0; }
curl -s -o /dev/null -w 'create share %{http_code}\n' -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"path":"/media","source":"NVME 1TB","expires":"","unit":"hours","password":null}' "$B/api/share"
rm -f $D/.tok
;;
probe)
T=$(tok "$2"); echo "toklen ${#T}"
for p in api/users api/shares api/settings "api/users?id=self"; do
curl -s -o $D/.o -w "$p %{http_code} " -H "Authorization: Bearer $T" "$B/$p"
head -c 160 $D/.o | sed 's/"password":"[^"]*"/"password":"REDACTED"/g'; echo
done
rm -f $D/.o $D/.tok
;;
cleanup)
T=$(tok "$2")
id=$(curl -s -H "Authorization: Bearer $T" "$B/api/users" | python3 -c 'import json,sys;print(next((u["id"] for u in json.load(sys.stdin) if u["username"]=="fbspike"),""))')
hdr "$2"; [ -n "$id" ] && curl -s -o /dev/null -w "delete user %{http_code}\n" -X DELETE -H @$D/.h -H "Authorization: Bearer $T" "$B/api/users?id=$id"; rm -f $D/.h
for h in $(curl -s -H "Authorization: Bearer $T" "$B/api/share/list" | python3 -c 'import json,sys;d=json.load(sys.stdin);d=d if isinstance(d,list) else [];[print(s["hash"]) for s in d if s.get("path","").rstrip("/")=="/media"]'); do
curl -s -o /dev/null -w "delete share %{http_code}\n" -X DELETE -H "Authorization: Bearer $T" "$B/api/share?hash=$h"; done
rm -f $D/.tok
;;
esac
@@ -0,0 +1,10 @@
## 2026-10-16T12:17:12Z cleanup: the test user and the test share
delete user 200
delete share 200
users:
1 admin admin
shares:
count 0
settings:
sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok']
sha256 12a6170ac405fa72
@@ -0,0 +1,33 @@
## 2026-10-16T12:14:17Z step 1: state operator, password unknown; admin/admin and K1 before
login K1: 401
## reset to K1 with the CLI, server stopped
stopped
cli rc=0
successfully updated user:
started
health=healthy after 7s
login K1: 200
users:
Traceback (most recent call last):
File "<string>", line 1, in <module>
import json,sys;[print(" ",u.get("id"),u.get("username"),"admin" if u.get("permissions",{}).get("admin") else "-") for u in json.load(sys.stdin)]
^^^^^
AttributeError: 'str' object has no attribute 'get'
shares:
Traceback (most recent call last):
File "<string>", line 1, in <module>
import json,sys;d=json.load(sys.stdin);d=d if isinstance(d,list) else [];[print(" ",s.get("hash"),s.get("path"),s.get("source")) for s in d];print(" count",len(d))
~~~~~~~~~^^^^^^^^^^^
File "/usr/lib/python3.13/json/__init__.py", line 293, in load
return loads(fp.read(),
cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
File "/usr/lib/python3.13/json/__init__.py", line 346, in loads
return _default_decoder.decode(s)
~~~~~~~~~~~~~~~~~~~~~~~^^^
File "/usr/lib/python3.13/json/decoder.py", line 348, in decode
raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 5 (char 4)
settings sources:
keys ['message', 'status']
@@ -0,0 +1,22 @@
## 2026-10-16T12:16:26Z step 2: seed a user and a share as K1, inventory
create user 401
create share 200
users:
1 admin admin
shares:
aoSyMglz7IyGgR9e_WVg4Q /media/ NVME 1TB
count 1
settings:
sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok']
sha256 12a6170ac405fa72
## 2026-10-16T12:16:43Z step 2b: seed the user (with X-Password), inventory
create user 201
users:
1 admin admin
2 fbspike -
shares:
aoSyMglz7IyGgR9e_WVg4Q /media/ NVME 1TB
count 1
settings:
sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok']
sha256 12a6170ac405fa72
@@ -0,0 +1,19 @@
## 2026-10-16T12:16:51Z step 3: reset to K2 without K1 (CLI, server stopped)
stopped
cli rc=0
successfully updated user:
started
health=healthy after 7s
login K2: 200
login K1: 401
admin/admin:
login ADM: 401
users:
1 admin admin
2 fbspike -
shares:
aoSyMglz7IyGgR9e_WVg4Q /media/ NVME 1TB
count 1
settings:
sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok']
sha256 12a6170ac405fa72
Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@@ -0,0 +1,219 @@
after login url: /launcher
{
"/apps/filebrowser": {
"path": "/apps/filebrowser",
"title": "FileBrowser",
"tabs": [
"settings",
"guide",
"logs"
],
"install": false,
"loginAnswer": [
"initial"
],
"loginKind": [],
"fbOperator": false,
"fbReset": false,
"reveal": true,
"open": "https://files.enkisfelhom.hu/",
"files": null,
"folders": [],
"fbNote": false,
"scrollWidth": 1280,
"console": []
},
"/apps/filebrowser/guide": {
"path": "/apps/filebrowser/guide",
"title": "FileBrowser",
"tabs": [
"settings",
"guide",
"logs"
],
"install": false,
"loginAnswer": [],
"loginKind": [],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://files.enkisfelhom.hu/",
"files": null,
"folders": [],
"fbNote": false,
"scrollWidth": 1280,
"console": [
[
"log-error",
"Failed to load resource: the server responded with a status of 404 ()",
"https://felhom.enkisfelhom.hu/static/assets/filebrowser-screenshot-3.webp"
]
]
},
"/apps/romm": {
"path": "/apps/romm",
"title": "RomM",
"tabs": [
"settings",
"data",
"guide",
"logs"
],
"install": false,
"loginAnswer": [
"first_visit"
],
"loginKind": [
"first_visit"
],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://arcade.enkisfelhom.hu/",
"files": "https://files.enkisfelhom.hu/files/NVME%201TB/roms",
"folders": [],
"fbNote": false,
"scrollWidth": 1270,
"console": []
},
"/apps/romm/data": {
"path": "/apps/romm/data",
"title": "RomM",
"tabs": [
"settings",
"data",
"guide",
"logs"
],
"install": false,
"loginAnswer": [],
"loginKind": [],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://arcade.enkisfelhom.hu/",
"files": "https://files.enkisfelhom.hu/files/NVME%201TB/roms",
"folders": [
"https://files.enkisfelhom.hu/files/NVME%201TB/roms",
"https://files.enkisfelhom.hu/files/NVME%201TB/roms-bios"
],
"fbNote": true,
"scrollWidth": 1270,
"console": []
},
"/apps/calibre-web": {
"path": "/apps/calibre-web",
"title": "Calibre-Web Automated",
"tabs": [
"settings",
"data",
"guide",
"logs"
],
"install": false,
"loginAnswer": [
"fields"
],
"loginKind": [],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://books.enkisfelhom.hu/",
"files": "https://files.enkisfelhom.hu/files/NVME%201TB/media/books",
"folders": [],
"fbNote": false,
"scrollWidth": 1270,
"console": []
},
"/apps/calibre-web/data": {
"path": "/apps/calibre-web/data",
"title": "Calibre-Web Automated",
"tabs": [
"settings",
"data",
"guide",
"logs"
],
"install": false,
"loginAnswer": [],
"loginKind": [],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://books.enkisfelhom.hu/",
"files": "https://files.enkisfelhom.hu/files/NVME%201TB/media/books",
"folders": [
"https://files.enkisfelhom.hu/files/Beolvas%C3%A1s/calibre",
"https://files.enkisfelhom.hu/files/NVME%201TB/media/books"
],
"fbNote": true,
"scrollWidth": 1270,
"console": []
},
"/apps/plex": {
"path": "/apps/plex",
"title": "Plex",
"tabs": [
"settings",
"data",
"guide",
"logs"
],
"install": false,
"loginAnswer": [
"first_visit"
],
"loginKind": [
"first_visit"
],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://plex.enkisfelhom.hu/",
"files": "https://files.enkisfelhom.hu/files/NVME%201TB/media",
"folders": [],
"fbNote": false,
"scrollWidth": 1270,
"console": []
},
"/apps/plex/data": {
"path": "/apps/plex/data",
"title": "Plex",
"tabs": [
"settings",
"data",
"guide",
"logs"
],
"install": false,
"loginAnswer": [],
"loginKind": [],
"fbOperator": false,
"fbReset": false,
"reveal": false,
"open": "https://plex.enkisfelhom.hu/",
"files": "https://files.enkisfelhom.hu/files/NVME%201TB/media",
"folders": [
"https://files.enkisfelhom.hu/files/NVME%201TB/media/movies",
"https://files.enkisfelhom.hu/files/NVME%201TB/media/tv"
],
"fbNote": true,
"scrollWidth": 1270,
"console": []
},
"cold:romm": {
"target": "https://files.enkisfelhom.hu/files/NVME%201TB/roms",
"first": "/login?redirect=/files/NVME%25201TB/roms",
"after_login": "/files/NVME%201TB/roms"
},
"cold:calibre-web": {
"target": "https://files.enkisfelhom.hu/files/NVME%201TB/media/books",
"first": "/login?redirect=/files/NVME%25201TB/media/books",
"after_login": "/files/NVME%201TB/media/books"
},
"cold:plex": {
"target": "https://files.enkisfelhom.hu/files/NVME%201TB/media",
"first": "/login?redirect=/files/NVME%25201TB/media",
"after_login": "/files/NVME%201TB/media"
}
}
@@ -0,0 +1,16 @@
200 /apps/filebrowser :: 1 data-fb-operator 1 data-fb-reset 1 data-login-answer="initial" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings"
302 /apps/filebrowser /apps/filebrowser/install ::
302 /apps/filebrowser /apps/filebrowser/data ::
200 /apps/filebrowser/guide :: 1 data-infra-guide 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings"
200 /apps/romm :: 1 data-install-cta
302 /apps/romm /apps/romm/data ::
200 /apps/calibre-web :: 1 data-app-files="1" 1 data-login-answer="fields" 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media/books" target="_blank" rel="noopener" class="btn btn-outline" data-app-files
200 /apps/calibre-web/data :: 1 data-app-files="1" 1 data-fb-login-note 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media/books" target="_blank" rel="noopener" class="btn btn-outline" data-app-files
200 /apps/plex :: 1 data-app-files="1" 1 data-login-answer="first_visit" 1 data-login-kind="first_visit" 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media" target="_blank" rel="noopener" class="btn btn-outline" data-app-files
200 /apps/plex/data :: 1 data-app-files="1" 1 data-fb-login-note 2 data-media-folder 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings" href="https://files.enkisfelhom.hu/files/NVME%201TB/media" target="_blank" rel="noopener" class="btn btn-outline" data-app-files
200 /apps/immich :: 1 data-install-cta
200 /apps/bentopdf :: 1 data-login-answer="first_visit" 1 data-login-kind="first_visit" 1 data-tab="data" 1 data-tab="guide" 1 data-tab="logs" 1 data-tab="settings"
@@ -0,0 +1,12 @@
## 2026-10-16T13:26:04Z press reset (POST /apps/filebrowser/initial-credentials/reset)
{"data":null,"error":"","ok":true}
HTTP 200 in 3.362612s
## the page after
1 data-login-answer="initial"
1 id="initcred-reveal"
## reveal (value kept out of this file; length only)
HTTP 200
ok True len 16
## a second press (now generated) must be refused
{"data":null,"error":"A Fájlkezelő jelszavát a doboz már ismeri — a Megjelenítés gomb mutatja.","ok":false}
HTTP 409
@@ -0,0 +1,20 @@
login NEWPW: 200
login K2: 401
login ADM: 401
users:
1 admin admin
shares:
count 0
settings:
sources ['Beolvasás', 'NVME 1TB', 'Megőrzött adatok']
sha256 12a6170ac405fa72
e920c91f524e filebrowser filebrowser gtstef/filebrowser:1.5.6-stable
2026/10/16 13:20:19 handlers.go:3869: [INFO] [web] FileBrowser sync — no config/compose change, ensured running without recreate (1 storage path(s))
e920c91f524e filebrowser filebrowser gtstef/filebrowser:1.5.6-stable
2026/10/16 13:26:04 handlers.go:2742: [INFO] [web] filebrowser: new admin password requested from 37.191.56.193
2026/10/16 13:26:04 filebrowser_password.go:220: [INFO] [infra] filebrowser: the household asked for a new admin password — stopping the file browser for its CLI
2026/10/16 13:26:08 filebrowser_password.go:258: [INFO] [infra] filebrowser: admin password replaced at the household's press (value never logged); verified new=200 admin=refused in 3s
2026/10/16 13:26:08 handlers.go:2780: [INFO] [web] filebrowser admin password revealed from 37.191.56.193 (value never logged)
2026/10/16 13:26:08 handlers.go:2742: [INFO] [web] filebrowser: new admin password requested from 37.191.56.193
"filebrowser_admin_state": "generated"
filebrowser Up 19 seconds (healthy)
@@ -0,0 +1,5 @@
200 /apps/filebrowser :: data-fb-operator data-fb-reset data-login-answer="initial"
302 /apps/filebrowser/install ::
200 /apps/romm :: data-install-cta
200 /apps/plex :: data-install-cta
200 /apps/immich :: data-install-cta
@@ -0,0 +1,2 @@
{"ok":true,"message":"Telepítés elindítva – az állapot a kártyán követhető"}
HTTP 202
@@ -0,0 +1,50 @@
## infra counted as not installed (v0.315.0 line)
=== RUN TestAppPage_InfraAppHasNoInstall
logins_folders_test.go:193: FileBrowser offers Install — an infrastructure app is part of the box
--- FAIL: TestAppPage_InfraAppHasNoInstall (0.09s)
## no fallback login answer (v0.319.0 behaviour)
=== RUN TestAppPage_EveryInstalledAppHasOneLoginAnswer
logins_folders_test.go:256: immich: 0 login answers, want exactly 1
logins_folders_test.go:256: rallly: 0 login answers, want exactly 1
logins_folders_test.go:256: bentopdf: 0 login answers, want exactly 1
--- FAIL: TestAppPage_EveryInstalledAppHasOneLoginAnswer (0.10s)
## no library preference in the Files target
## absent drive still linked
=== RUN TestAppPage_UnknownSourceIsTextNotLink
logins_folders_test.go:315: romm links a folder on a drive that is not on the box
--- FAIL: TestAppPage_UnknownSourceIsTextNotLink (0.08s)
## plaintext stored
=== RUN TestFileBrowserReset_SetsVerifiesAndStoresEncrypted
filebrowser_reset_test.go:56: the stored value is the plaintext password (or empty)
--- FAIL: TestFileBrowserReset_SetsVerifiesAndStoresEncrypted (0.00s)
## verify of the new password removed
=== RUN TestFileBrowserReset_FailedVerifyKeepsOperator
filebrowser_reset_test.go:73: a reset whose new password does not log in reported success
--- FAIL: TestFileBrowserReset_FailedVerifyKeepsOperator (0.00s)
## state check removed
=== RUN TestFileBrowserReset_OnlyInOperatorState
filebrowser_reset_test.go:95: state "": err = <nil>, want ErrFileBrowserNotOperator
filebrowser_reset_test.go:98: state "": the apply ran
filebrowser_reset_test.go:101: state "" changed to "generated"
filebrowser_reset_test.go:95: state "generated": err = <nil>, want ErrFileBrowserNotOperator
filebrowser_reset_test.go:98: state "generated": the apply ran
## protected deploy guard removed
=== RUN TestDeployStack_RefusesProtectedStack
--- FAIL: TestDeployStack_RefusesProtectedStack (0.00s)
## no library preference in the Files target (rerun)
=== RUN TestAppPage_FilesButtonTarget
logins_folders_test.go:292: calibre-web: Files → https://files.example.hu/files/Beolvas%C3%A1s/calibre, want https://files.example.hu/files/M%C3%A9dia%20&amp;%20k%C3%B6nyvt%C3%A1r/media/books
--- FAIL: TestAppPage_FilesButtonTarget (0.08s)
## protected deploy guard removed (rerun)
=== RUN TestDeployStack_RefusesProtectedStack
--- FAIL: TestDeployStack_RefusesProtectedStack (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered, repanicked]
panic({0xac07c0?, 0x116c870?})
/usr/local/go/src/runtime/panic.go:860 +0x13a
filebrowser_reset_test.go:128: filebrowser (Protected=true): err = went on into the deploy (panicked on the bare manager), want ErrProtectedStack
filebrowser_reset_test.go:131: filebrowser: left Deploying set
filebrowser_reset_test.go:128: filebrowser (Protected=false): err = went on into the deploy (panicked on the bare manager), want ErrProtectedStack
filebrowser_reset_test.go:131: filebrowser: left Deploying set
filebrowser_reset_test.go:128: samba (Protected=false): err = went on into the deploy (panicked on the bare manager), want ErrProtectedStack
XX DECOY: a bad value only in the English block rc=1 (expected 0)
XX DECOY: the key under ANOTHER mapping rc=1 (expected 0)
@@ -0,0 +1,12 @@
== tester-1
unhealthy=0 restarting=0 exited=0 created=0
controller gitea.dooplex.hu/admin/felhom-controller:0.320.0 Up 13 minutes (healthy)
filebrowser Up 11 hours (healthy)
== demo-hp
unhealthy=0 restarting=0 exited=0 created=0
controller gitea.dooplex.hu/admin/felhom-controller:0.320.0 Up 34 seconds (healthy)
filebrowser Up 15 minutes (healthy)
== demo-felhom
unhealthy=0 restarting=0 exited=0 created=0
controller gitea.dooplex.hu/admin/felhom-controller:0.320.0 Up 13 minutes (healthy)
filebrowser Up 11 hours (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,3 @@
15:48:34
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,3 @@
('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')
customers: no such table: customers
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
"""v0.320.0 logins & folders browser check. 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 = {}
MARK = """(function(){ function all(sel,attr){return [].map.call(document.querySelectorAll(sel),function(e){return e.getAttribute(attr)})}
return {path: location.pathname, title: (document.querySelector('.app-hero-name')||{}).textContent,
tabs: all('[data-tab]','data-tab'), install: !!document.querySelector('[data-install-cta]'),
loginAnswer: all('[data-login-answer]','data-login-answer'), loginKind: all('[data-login-kind]','data-login-kind'),
fbOperator: !!document.querySelector('[data-fb-operator]'), fbReset: !!document.querySelector('[data-fb-reset]'),
reveal: !!document.querySelector('#initcred-reveal'), open: (document.querySelector('[data-app-open]')||{}).href || null,
files: (document.querySelector('[data-app-files]')||{}).href || null,
folders: [].map.call(document.querySelectorAll('.datapath-row'), function(r){var a=r.querySelector('a'); return (a?a.href:'TEXT:'+r.textContent.trim().slice(0,60))}),
fbNote: !!document.querySelector('[data-fb-login-note]'), scrollWidth: document.documentElement.scrollWidth};})()"""
for p in ['/apps/filebrowser', '/apps/filebrowser/guide', '/apps/romm', '/apps/romm/data', '/apps/calibre-web',
'/apps/calibre-web/data', '/apps/plex', '/apps/plex/data']:
n0 = len(console)
goto(BASE + p)
r = ev(MARK); r['console'] = console[n0:]
report[p] = r
shot(p.strip('/').replace('/', '-'))
# The cold „Fajlok" click: this browser has NO FileBrowser session. Follow each app's button target.
fbpw = open(FBPWFILE).read().strip()
for slug in ['romm', 'calibre-web', 'plex']:
target = report['/apps/' + slug].get('files')
if not target:
report['cold:' + slug] = 'no Files button'
continue
call('Network.clearBrowserCookies') # cold: no FileBrowser session (the controller one is re-made below)
goto(target)
first = ev('location.pathname + location.search')
shot('cold-' + slug + '-1-login')
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))
time.sleep(4)
after = ev('location.pathname')
shot('cold-' + slug + '-2-after-login')
report['cold:' + slug] = {'target': target, 'first': first, 'after_login': after}
del fbpw
print(json.dumps(report, indent=1, ensure_ascii=False))
finally:
proc.terminate()
subprocess.run(['rm', '-rf', prof])