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.
93 lines
4.9 KiB
Python
93 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""summarize.py — read every <app>/record.json of the sweep and print one review line per app: the measured facts and
|
|
a FIRST verdict hint. The hint is a filter for the reader, not the verdict: README.md carries the verdict after each
|
|
app's evidence was read."""
|
|
import glob, json, os, re, sys
|
|
|
|
EV = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
EN_IN_HU = re.compile(r"(?i)\b(the|and|your|you|please|click|with|this|password|settings|open|first|login)\b")
|
|
|
|
|
|
def hint(r):
|
|
probs, broken = [], []
|
|
inst = r.get("install") or {}
|
|
if not inst.get("ok"):
|
|
broken.append("install failed")
|
|
elif not inst.get("front_door"):
|
|
broken.append("front door never answered")
|
|
if r.get("exception"):
|
|
broken.append("cycle raised: " + r["exception"][:80])
|
|
if r.get("fixture") and r.get("seed") is False:
|
|
probs.append("seed failed")
|
|
if r.get("seed") and r.get("seed_read_now") is False:
|
|
probs.append("item not read back at once")
|
|
back = r.get("seed_read_after_reinstall")
|
|
if r.get("seed") and back is False and not r.get("seed_read_after_unit_restore"):
|
|
probs.append("item NOT back after keep-data reinstall + unit restore")
|
|
if r.get("seed_read_after_restore") is False:
|
|
probs.append("item not back after the unit restore")
|
|
if (r.get("kept") or {}).get("offered") and (r.get("kept") or {}).get("use_ok") is False:
|
|
probs.append("use-my-kept-data failed")
|
|
left = r.get("after_final_remove") or {}
|
|
if any(left.get(k) for k in ("containers", "volumes", "appyaml", "appdata", "kept")):
|
|
probs.append("leftovers after remove: " + json.dumps({k: v for k, v in left.items() if v and k != "subdomain"}))
|
|
if left.get("subdomain") and not left["subdomain"].startswith("404"):
|
|
probs.append("subdomain after remove: " + left["subdomain"][:40])
|
|
for c, v in (r.get("containers") or {}).items():
|
|
anon = (r.get("mem_anon_peak_mib") or {}).get(c)
|
|
lim, peak = v.get("limit_mib"), anon if anon is not None else (r.get("mem_peak_mib") or {}).get(c)
|
|
if lim and peak and peak > 0.9 * lim:
|
|
probs.append(f"memory {c} {peak}/{lim} MiB ({'anon' if anon is not None else 'docker stats, may include cache'})")
|
|
if v.get("restarts"):
|
|
probs.append(f"restarts {c} {v['restarts']}")
|
|
if v.get("health") == "unhealthy":
|
|
probs.append(f"unhealthy {c}")
|
|
errs = [l for l in (r.get("errors") or {}).get("sample", [])
|
|
if not re.search(r"(?i)wrong (username|password)|invalid password|failed login|login fail|unauthori[sz]ed|status=40[13]|bad credentials", l)]
|
|
n = (r.get("errors") or {}).get("count", 0) - (len((r.get("errors") or {}).get("sample", [])) - len(errs))
|
|
if n > 0: # the negative controls' own wrong-password logins are expected and not counted
|
|
probs.append(f"error lines {n}")
|
|
if r.get("http_port_80") and not r["http_port_80"].startswith(("301", "308")):
|
|
probs.append("http :80 → " + r["http_port_80"][:30])
|
|
for tab, fname in (("install_page", "install"), ("guide", "guide")):
|
|
t = r.get(tab) or {}
|
|
try: # English OUTSIDE quotes: a quoted „Settings” is the app's own label, glossed in Hungarian — correct
|
|
txt = open(os.path.join(EV, r["app"], f"page-{fname}-hu.txt")).read()
|
|
bare = re.sub(r"„[^”]*”|“[^”]*”|\"[^\"]*\"|\([^)]*\)", " ", txt)
|
|
words = sorted(set(m.lower() for m in EN_IN_HU.findall(bare)))
|
|
except OSError:
|
|
words = t.get("english_words_in_hu") or []
|
|
if words:
|
|
probs.append(f"{tab} EN words in HU {words[:5]}")
|
|
if t.get("accented_in_en"):
|
|
probs.append(f"{tab} HU letters in EN {t['accented_in_en']}")
|
|
if t.get("same_text"):
|
|
probs.append(f"{tab} same text HU/EN")
|
|
for s in (r.get("shots_install") or []) + (r.get("shots_after") or []):
|
|
if "hscroll=True" in s:
|
|
probs.append("sideways scroll in a screenshot")
|
|
if "STEP FAILED" in s:
|
|
probs.append("a screenshot step failed")
|
|
v = "broken" if broken else ("problem?" if probs else "works")
|
|
return v, broken + probs
|
|
|
|
|
|
def main():
|
|
rows = []
|
|
for f in sorted(glob.glob(os.path.join(EV, "*", "record.json"))):
|
|
r = json.load(open(f))
|
|
v, why = hint(r)
|
|
inst = r.get("install") or {}
|
|
peak = sum((r.get("mem_peak_mib") or {}).values())
|
|
lim = sum((c.get("limit_mib") or 0) for c in (r.get("containers") or {}).values())
|
|
rows.append((r["app"], v, round(peak), lim, inst.get("seconds_to_front_door"), r.get("fixture") or "-",
|
|
r.get("stranger", "")[:40], "; ".join(why)))
|
|
for row in rows:
|
|
print("\t".join(str(x) for x in row))
|
|
print(f"# {len(rows)} records; " + ", ".join(f"{k} {sum(1 for r in rows if r[1] == k)}" for k in ("works", "problem?", "broken")),
|
|
file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|