gates / gates (push) Successful in 13m17s
Every catalog app installed, used, backed up, removed keeping data, reinstalled, restored and removed with data on a fresh box from the public ISO (demo-hp VM 9360, Tester4), endpoint-level. Worst: a reinstall's capture rewrites the kept unit so its restore breaks the app (R-1269, P2), the restore still reports success (R-1270, P2), and „use my kept data" loaded another box's future-dated database (R-1271, P2). Drill catalog fixes for the morning list (n8n usage data; OpenGist 256M, proven by a cgroup probe). VM 9360 and LXC 9361 destroyed; hub host Tester4-aa9466 left for the operator. REPORT-catalog-sweep-2026-10-24.md.
109 lines
8.9 KiB
Python
109 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
||
"""table.py — the README's 65-row table: the measured facts from each <app>/record.json, and the VERDICT set by hand
|
||
after reading that app's evidence (VERDICTS below — the reviewer's call, not the summarize.py hint)."""
|
||
import json, os, sys
|
||
|
||
EV = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
W, P, B, N, T = "works", "works with a problem", "broken", "not installed", "not testable on this box"
|
||
|
||
# app: (verdict, one line, row/fix)
|
||
VERDICTS = {
|
||
"actualbudget": (W, "the seeded item back after the restore", ""),
|
||
"adventurelog": (P, "works; but the keep-backups → reinstall → restore path breaks it (DB password from the new install over the old data) — reproduced in its re-run", "R-1269, R-1270"),
|
||
"audiobookshelf": (W, "the seeded item back after the restore", "drive names: R-1275"),
|
||
"bentopdf": (W, "stateless (nothing to keep); installs, answers, removes clean", ""),
|
||
"bookstack": (W, "the seeded item back after the restore", ""),
|
||
"calcom": (W, "the seeded item back after the restore; 2 start-up messages of the image itself", ""),
|
||
"calibre-web": (W, "the book (its own upload) is listed again after the keep-data reinstall; the fixture's download check gets 401 (its sign-in for /download, not the app); the admin name is the box's generated one (re-run 2)", "R-1277"),
|
||
"claper": (W, "the seeded item back after the restore", ""),
|
||
"code-server": (W, "first install refused by the registry's rate limit, told as „exit code 1”; the re-run installed and worked (no seed: the editor has no API)", "R-1273"),
|
||
"crafty-controller": (W, "the seeded item back after the restore", ""),
|
||
"dawarich": (W, "the seeded item back after the restore (every-5th: restore also before the reinstall)", ""),
|
||
"docmost": (W, "the seeded item back after the restore", ""),
|
||
"emby": (W, "installs, answers, removes clean; no seed (no fixture for its wizard)", ""),
|
||
"ghost": (W, "the seeded item back after the restore", ""),
|
||
"gitea": (W, "the seeded item back after the restore", ""),
|
||
"glance": (W, "family gate holds a stranger; config-only (nothing to keep); the app's own headings are English", ""),
|
||
"gokapi": (W, "installs, own login, removes clean; no seed (API key only in its UI)", ""),
|
||
"grafana": (W, "the seeded item back after the restore", ""),
|
||
"gramps-web": (W, "installs, answers, removes clean; no seed fixture", ""),
|
||
"grimmory": (P, "first admin, library and an EPUB upload work; setup gate then family gate work as designed; the read-back through the family gate stayed inconclusive (the harness's gate logic, R-1277); remove-with-data leaves the empty parent folder", "R-1276, R-1277"),
|
||
"grocy": (W, "the seeded item back after the restore", ""),
|
||
"home-assistant": (W, "the seeded item back after the restore", ""),
|
||
"homebox": (W, "the seeded item back after the restore", ""),
|
||
"homepage": (P, "no gate at all: a stranger sees the box's CPU/RAM/disk (and later the family's links); still the upstream sample page", "R-1272"),
|
||
"immich": (P, "album (new fixture) in; „use my kept data” loaded the rehearsal's future-dated database and Immich never answered again (404, 20 min)", "R-1271"),
|
||
"jellyfin": (W, "first user (new fixture, its wizard API) back after the restore", ""),
|
||
"karakeep": (W, "the seeded item back after the restore", ""),
|
||
"kimai": (W, "own login; a Hungarian browser lands on /hu/ (curl without a language gets /en/)", ""),
|
||
"komga": (W, "the seeded item back after the restore", "drive names: R-1275"),
|
||
"lubelogger": (W, "the seeded item back after the restore", ""),
|
||
"mealie": (W, "the seeded item back after the restore (the fixture raced after_install on the first run)", ""),
|
||
"metube": (W, "family gate holds a stranger; download queue entry back", ""),
|
||
"n8n": (P, "the owner back after the restore; on the live template it tries to send usage data out ([Rudder] lines, 6 in the live re-run) — the drill fix: the variable in the running container, 0 error lines", "drill a601950 (morning list)"),
|
||
"navidrome": (W, "the seeded item back after the restore", "drive names: R-1275"),
|
||
"nextcloud": (P, "user in; „use my kept data” loaded the rehearsal's future-dated database beside today's files — the user was not there", "R-1271"),
|
||
"nzbget": (W, "the seeded item back after the restore; „no news server” until the family adds one (the guide says so)", ""),
|
||
"onlyoffice": (W, "document server (no household data to keep); installs, answers, removes clean (every-5th restore)", ""),
|
||
"opengist": (P, "the gist owner back after the restore; but 128M is too small: one sign-up + sign-in hit the limit 443 times and swapped 98 MiB (memprobe-128M.txt); at 256M (drill) anon 216 MiB", "drill 405077f (morning list)"),
|
||
"outline": (W, "the seeded item back after the restore", ""),
|
||
"paperless-ngx": (P, "account in; „use my kept data” loaded the rehearsal's future-dated database — the account was not there", "R-1271, R-1276"),
|
||
"papra": (W, "the seeded item back after the restore (Papra 26.6 needs an Origin header — a browser sends it)", ""),
|
||
"plant-it": (N, "lifecycle: abandoned — not offered for a new install (by design)", ""),
|
||
"plex": (W, "installs, answers, removes clean; no seed (a server is claimed with a plex.tv account)", ""),
|
||
"privatebin": (W, "the seeded item back after the restore; D22 put-back PASS", ""),
|
||
"prowlarr": (W, "the seeded item back after the restore", ""),
|
||
"qbittorrent": (W, "the seeded item back after the restore", ""),
|
||
"radarr": (W, "the seeded item back after the restore", ""),
|
||
"radicale": (W, "the seeded item back after the restore", ""),
|
||
"rallly": (W, "installs, answers, removes clean; no seed: Rallly 4.15 closed password sign-up (login by e-mail code); the guide's polls need no account", "R-1277"),
|
||
"recipe-importer": (W, "stateless helper; installs, answers, removes clean (every-5th restore)", ""),
|
||
"romm": (P, "item back through „use my kept data” (its own copy); remove-with-data left the empty parent folder", "R-1276"),
|
||
"sabnzbd": (W, "the seeded item back after the restore", ""),
|
||
"seerr": (W, "installs, answers (307 to its setup), removes clean; no seed (needs a media server)", ""),
|
||
"sonarr": (W, "the seeded item back after the restore", ""),
|
||
"sparkyfitness": (W, "the seeded item back after the restore", ""),
|
||
"tandoor": (P, "works; but the keep-backups → reinstall → restore path breaks it (DB password from the new install over the old data), and the restore still said success — reproduced on its own", "R-1269, R-1270"),
|
||
"termix": (W, "installs, answers, removes clean (every-5th restore); no seed fixture", ""),
|
||
"transmission": (W, "the seeded item back after the restore", ""),
|
||
"uptime-kuma": (W, "the seeded item back after the restore", ""),
|
||
"vaultwarden": (W, "installs in 25 s, its own login page, sign-up closed (404); the box admin's invite works (run-live-2), but 1.37.4 has no route for the fixture's invited sign-up, so no item was seeded", "R-1277"),
|
||
"vikunja": (W, "the seeded item back after the restore; D22 put-back PASS", ""),
|
||
"wanderer": (T, "not testable on this box: its web part calls its database at the PUBLIC name, which tester4.test cannot resolve (known, R-739) — unhealthy, 404", "R-739"),
|
||
"wger": (N, "lifecycle: hidden — not offered for a new install (by design)", ""),
|
||
"wishlist": (W, "the seeded item back after the restore", ""),
|
||
"zipline": (W, "the seeded item back after the restore", ""),
|
||
}
|
||
|
||
|
||
def mem(r):
|
||
anon = r.get("mem_anon_peak_mib") or {}
|
||
src = anon if anon else (r.get("mem_peak_mib") or {})
|
||
lim = sum((c.get("limit_mib") or 0) for c in (r.get("containers") or {}).values())
|
||
return f"{round(sum(src.values()))}{'' if anon else '*'} / {lim}" if src else "–"
|
||
|
||
|
||
def main():
|
||
rows = ["| App | Verdict | Peak memory / limit (MiB) | Install → answering (s) | What we saw | Row / fix |", "|---|---|---|---|---|---|"]
|
||
for app in sorted(VERDICTS):
|
||
v, line, ref = VERDICTS[app]
|
||
f = os.path.join(EV, app, "record.json")
|
||
r = json.load(open(f)) if os.path.exists(f) else {}
|
||
inst = (r.get("install") or {}).get("seconds_to_front_door")
|
||
rows.append(f"| {app} | **{v or 'PENDING'}** | {mem(r) if r else '–'} | {inst if inst is not None else '–'} | {line} | {ref} |")
|
||
c = {}
|
||
for v, _, _ in VERDICTS.values():
|
||
c[v or "PENDING"] = c.get(v or "PENDING", 0) + 1
|
||
rows.append("\n" + ", ".join(f"{k}: {n}" for k, n in c.items()) + f" (of {len(VERDICTS)})")
|
||
out = "\n".join(rows)
|
||
if "--write" in sys.argv: # replace the block between the markers in ../README.md
|
||
p = os.path.join(EV, "README.md")
|
||
s = open(p).read()
|
||
a, b = s.index("<!-- TABLE START -->") + len("<!-- TABLE START -->"), s.index("<!-- TABLE END -->")
|
||
open(p, "w").write(s[:a] + "\n" + out + "\n" + s[b:])
|
||
print(out)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|