Files
felhom.eu/documentation/audits/sizing-2026-10-13/tools/run_action.py
T
admin c885b5acf4 Audit: what a Felhom box needs — floor, 64 apps' memory and CPU, power (N100 bench); R-993..R-995; R-231 due check read green
Measured on demo-felhom (N100, 16 GB): 61 apps deployed and removed through the
product, sampled every 5 s; heavy apps with one real action (Immich 200 photos,
Jellyfin transcode, Paperless OCR, Nextcloud 500 MB, ...). sizing.json for the
estimator (tools/build_sizing.py), SIZING.md with the proposed minimum machine,
METHOD.md. Power is RAPL CPU-package only (no meter). R-993 asks the operator to
approve the minimum machine; R-994 lists catalog limits/requests that are too low;
R-995 records leftover unnamed volumes after removal. R-231's dated check measured:
dooplex-backup.service success, journal green.
2026-10-13 13:23:51 +02:00

29 lines
1.3 KiB
Python

#!/usr/bin/env python3
"""run_action.py TAG APP — run actions.sh APP inside the guest; record its @ts lines as events.
The password the action needs is the app's own generated admin password (from the `up` step's
0600 scratch file) or, for apps whose first user the action creates, a fresh one. It travels inside
the piped script, never on a command line, and is never written to evidence.
"""
import json, os, re, secrets, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import bench
tag, app = sys.argv[1], sys.argv[2]
gen = {}
f = os.path.join(bench.SC, f".generated-{tag}.json")
if os.path.exists(f):
gen = json.load(open(f)).get(app, {})
pw = gen.get("PAPERLESS_ADMIN_PASSWORD") or gen.get("NEXTCLOUD_ADMIN_PASSWORD") or ("Sz-" + secrets.token_hex(12))
script = f"export PW='{pw}'\n" + open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "actions.sh")).read().replace(
'APP=${1:?app}', f'APP={app}')
out = bench.guest(script, timeout=6 * 3600)
assert pw not in out, "secret in output"
d = os.path.join(bench.AUDIT, "evidence", tag); os.makedirs(d, exist_ok=True)
open(os.path.join(d, f"action-{app}.txt"), "a").write(out)
bench._load(tag)
for m in re.finditer(r"^@(\d+) (\S+)(.*)$", out, re.M):
bench.EVENTS.append(dict(ts=int(m.group(1)), kind=m.group(2), app=app, note=m.group(3).strip()))
bench._save(tag)
print(out)