Files
felhom.eu/documentation/audits/night-2026-09-24/tools/liveC2setup.py
T
2026-09-24 13:30:30 +02:00

89 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Part C step 2, SETUP on 9202: four apps installed through the product, each at the ladder's first `from`,
then the drill catalog put back at the head — so wishlist is 1 step behind, navidrome 2, romm 3, and vikunja 1
with a FAILING step (its head .felhom.yml probe points at a closed port AFTER the install, so the install's
applied record keeps the good probe and the undo can judge the old version correctly).
The drill also receives the live catalog's step .felhom.yml files (catalog cf7cf84) the reset predates."""
import json, os, re, shutil, sys, time
sys.path.insert(0, ".")
import walk as w
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import ladder
LIVE = "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/templates"
APPS = {"wishlist": "c-wish", "navidrome": "c-navi", "romm": "c-romm", "vikunja": "c-vik"}
T = lambda a: f"{w.DRILL}/templates/{a}"
out = {"controller": w.guest("cat /etc/felhom-controller-image").strip(), "apps": {}}
w.login()
def drill(edit, msg):
import fcntl
with open(f"{w.SC}/drill.lock", "w") as lk:
fcntl.flock(lk, fcntl.LOCK_EX)
w.sh(["git", "-C", w.DRILL, "pull", "-q", "--rebase", "origin", "main"], timeout=120)
edit()
w.sh(["git", "-C", w.DRILL, "add", "-A", "templates"])
w.sh(["git", "-C", w.DRILL, "commit", "-qm", "NIGHT-C " + msg])
w.sh(["git", "-C", w.DRILL, "push", "-q", "origin", "main"], timeout=120)
w.say("drill: " + msg)
HEAD = {a: open(f"{T(a)}/docker-compose.yml").read() for a in APPS}
FY = {a: open(f"{T(a)}/.felhom.yml").read() for a in APPS}
def old_compose(a):
E, _, _ = ladder.parse(FY[a])
frm, head = E[0]["from"], ladder.images_in(HEAD[a])
c = HEAD[a]
for svc, ref in head.items():
if frm.get(svc) and frm[svc] != ref:
c = c.replace(f"image: {ref}", f"image: {frm[svc]}", 1)
assert ladder.images_in(c) == frm, (a, ladder.images_in(c), frm)
return c, len(E)
def to_old():
for a in APPS:
c, n = old_compose(a)
open(f"{T(a)}/docker-compose.yml", "w").write(c)
out["apps"][a] = {"ladder_len": n, "installed_at": ladder.images_in(c)}
for a in ("navidrome", "romm"):
for f in os.listdir(f"{LIVE}/{a}/steps"):
if f.endswith(".felhom.yml"):
shutil.copy(f"{LIVE}/{a}/steps/{f}", f"{T(a)}/steps/{f}")
drill(to_old, "wishlist/navidrome/romm/vikunja at the ladder's first from (+ step .felhom.yml files from live cf7cf84)")
w.sync_rescan()
for a, sub in APPS.items():
t0 = time.time()
ok = w.deploy(a, sub)
st = w.stack(a)
out["apps"][a]["deploy_ok"] = ok
out["apps"][a]["deploy_s"] = round(time.time() - t0)
out["apps"][a]["pinned"] = (st.get("app_config") or {}).get("pinned_images")
w.say(f"{a}: deployed={ok} in {out['apps'][a]['deploy_s']} s pinned={out['apps'][a]['pinned']}")
def to_head():
for a in APPS:
open(f"{T(a)}/docker-compose.yml", "w").write(HEAD[a])
f = re.sub(r"(healthcheck:\n\s+checks:\n\s+- type: api\n\s+port: )3456\b", r"\g<1>8999", FY["vikunja"], count=1)
assert f != FY["vikunja"]
open(f"{T('vikunja')}/.felhom.yml", "w").write(f)
drill(to_head, "heads back; vikunja's head step probes port 8999 (the failing step)")
w.sync_rescan()
time.sleep(5)
w.sync_rescan()
for a in APPS:
st = w.stack(a)
out["apps"][a]["steps_left"] = st.get("ladder_steps_left")
out["apps"][a]["badges"] = w.badges(a)
w.say(f"{a}: steps_left={st.get('ladder_steps_left')} badge={out['apps'][a]['badges']['hu'][:1]}")
json.dump(out, open("../C/20-setup.json", "w"), indent=2, ensure_ascii=False, default=str)
open("../C/20-setup.log", "w").write("\n".join(w.LOG) + "\n")