a7971c4822
gates / gates (push) Successful in 25s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
119 lines
5.6 KiB
Python
119 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Night 2026-09-25 Part C SETUP (adapted from night-2026-09-24 liveC2setup.py). Adds: navidrome's FIRST step marked
|
|
needs_person, wishlist's step marked files_may_change. Original doc:
|
|
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()
|
|
|
|
|
|
def wait_box_catalog(want):
|
|
"""R-607: the sync may answer "no change" while the catalog moved — wait until the BOX's own catalog
|
|
copy names the old images (measured the hard way at 22:10: four apps installed at the head)."""
|
|
t0 = time.time()
|
|
for i in range(40):
|
|
got = w.guest("cat /var/lib/docker/volumes/felhom-controller-data/_data/catalog-cache/templates/*/docker-compose.yml /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache/templates/*/.felhom.yml "
|
|
"/var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache/templates/*/docker-compose.yml 2>/dev/null")
|
|
if all(ref in got for ref in want):
|
|
w.say(f"box catalog caught up in {round(time.time()-t0)} s ({i+1} rounds)")
|
|
return
|
|
w.sync_rescan()
|
|
time.sleep(10)
|
|
raise SystemExit("the box catalog never caught up: " + str(want))
|
|
|
|
|
|
wait_box_catalog([r for a in APPS for r in out["apps"][a]["installed_at"].values()])
|
|
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)
|
|
# decision 13's marks, set on the FIRST ladder entry (the step the leg would take tonight)
|
|
for app, mark, val in (("navidrome", "needs_person", "drill: a person must check the library scan after this step"),
|
|
("wishlist", "files_may_change", True)):
|
|
body = open(f"{T(app)}/.felhom.yml").read()
|
|
lines = body.split("\n")
|
|
i = lines.index("update_ladder:") + 1
|
|
e = json.loads(lines[i][4:])
|
|
e.setdefault("marks", {})[mark] = val
|
|
lines[i] = " - " + json.dumps(e, ensure_ascii=False)
|
|
open(f"{T(app)}/.felhom.yml", "w").write("\n".join(lines))
|
|
out["apps"][app]["mark"] = {mark: val}
|
|
|
|
|
|
drill(to_head, "heads back; vikunja's head step probes port 8999 (the failing step); navidrome step 1 needs_person; wishlist step files_may_change")
|
|
wait_box_catalog([ladder.images_in(HEAD[a])[svc] for a in APPS for svc in ladder.images_in(HEAD[a])] + ["8999"])
|
|
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/C1-setup.json", "w"), indent=2, ensure_ascii=False, default=str)
|
|
open("../C/C1-setup.log", "w").write("\n".join(w.LOG) + "\n")
|