122 lines
6.1 KiB
Python
122 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""pullprobe.py — D48 and R-1270 live, on a throwaway app this run installs (never a household's unit).
|
|
|
|
1. install <app>, seed one item through the front door, „Mentés most";
|
|
2. keep a byte copy of the app's unit beside it (backups/d48-test-original/<app>, outside backups/primary);
|
|
3. D48: the unit's pin edited to a tag that does not exist (compose, manifest, data stamps — the same ref everywhere,
|
|
so the version check passes and the pull is what decides) → „Visszaállítás": refused, the app's containers never
|
|
restarted (StartedAt the same before and after), the household's sentence recorded;
|
|
4. the original unit back (sha256 of every file equal), a normal restore → success, the item reads back;
|
|
5. R-1270: the unit's pin edited to an image that pulls but exits at once (busybox) → „Visszaállítás": the record is
|
|
NOT ok, `not_started`, the household's sentence; the undo copy kept; then the original back and a normal restore;
|
|
6. remove with data; the test copy removed.
|
|
Endpoint-level for every household act; the unit edits are the only hand-made change (`pct exec`, files only).
|
|
|
|
usage: SC=… SWEEP_EV=… TARGET=9202 DRIVE_ROOT=… python3 pullprobe.py <app> <image-repo> [<busybox-ref>]
|
|
"""
|
|
import json, os, sys, time
|
|
|
|
TOOLS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools")
|
|
sys.path.insert(0, TOOLS)
|
|
import sweep as sw # noqa: E402
|
|
|
|
w, fxm = sw.w, sw.fxm
|
|
NS = "/mnt/sys_drive/felhom-data"
|
|
MISSING_TAG = "0.0.0-felhom-d48-missing"
|
|
|
|
|
|
def started(app):
|
|
return sw.g(f"for c in $(docker ps -a --filter label=com.docker.compose.project={app} -q); do "
|
|
"docker inspect -f '{{.Name}} {{.State.StartedAt}} {{.State.Status}}' $c; done").strip()
|
|
|
|
|
|
def sums(path):
|
|
return sw.g(f"cd {path} 2>/dev/null && find . -type f | sort | xargs -r sha256sum")
|
|
|
|
|
|
def restore(app, rec, key, say):
|
|
sw.restore_unit(app, rec, say)
|
|
rec[key] = rec.pop("restore")
|
|
say(f" {key}: " + rec[key].get("status", "")[:400])
|
|
|
|
|
|
def main(app, repo, exiting):
|
|
d = os.path.join(sw.EV, "_pullprobe", app)
|
|
os.makedirs(d, exist_ok=True)
|
|
log = open(os.path.join(d, "walk.log"), "a")
|
|
|
|
def say(*a):
|
|
line = time.strftime("%H:%M:%SZ", time.gmtime()) + " " + " ".join(str(x) for x in a)
|
|
print(line, flush=True)
|
|
log.write(sw.scrub(line) + "\n")
|
|
log.flush()
|
|
|
|
w.say = say
|
|
fx = fxm.FIXTURES[app]
|
|
sub = getattr(fx, "sub", app)
|
|
rec = {"app": app}
|
|
unit = f"{NS}/backups/primary/{app}"
|
|
orig = f"{NS}/backups/d48-test-original/{app}"
|
|
w.login()
|
|
t_start = int(time.time())
|
|
try:
|
|
rec["install"] = w.deploy(app, sub) and w.wait_app(sub, "/", tries=90, delay=5)
|
|
tok = fx.seed(w, sub, say)
|
|
rec["seed"] = bool(tok)
|
|
sw.backup_now(rec, say)
|
|
sw.g(f"rm -rf {orig}; mkdir -p {NS}/backups/d48-test-original && cp -a {unit} {orig}")
|
|
s0 = sums(unit)
|
|
rec["unit_files"] = len(s0.splitlines())
|
|
|
|
def edit(to_ref):
|
|
# the same ref in the definition, the manifest (pins + data block) and the data stamps — files only
|
|
return sw.g(f"cd {unit} && sed -i -E 's#{repo}:[^\"[:space:]]*#{to_ref}#g' compose/docker-compose.yml manifest.json data-stamps.json "
|
|
f"&& grep -c '{to_ref}' compose/docker-compose.yml manifest.json data-stamps.json")
|
|
|
|
def put_original():
|
|
sw.g(f"rm -rf {unit} && cp -a {orig} {unit}")
|
|
same = sums(unit) == s0
|
|
say(f" the original unit is back byte for byte: {same}")
|
|
return same
|
|
|
|
# 3. D48
|
|
say(" [D48] the unit's pin → " + repo + ":" + MISSING_TAG + " :: " + edit(f"{repo}:{MISSING_TAG}").replace("\n", " "))
|
|
before = started(app)
|
|
restore(app, rec, "d48_restore", say)
|
|
after = started(app)
|
|
rec["d48_started_before"], rec["d48_started_after"] = before, after
|
|
rec["d48_app_untouched"] = bool(before) and before == after
|
|
say(f" [D48] containers never restarted: {rec['d48_app_untouched']} ({before})")
|
|
rec["d48_item_still_there"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
|
|
# 4. normal restore
|
|
rec["original_back_1"] = put_original()
|
|
restore(app, rec, "normal_restore_1", say)
|
|
rec["item_after_normal_restore_1"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
|
|
say(f" normal restore: item back {rec['item_after_normal_restore_1']}, state {w.stack(app).get('state')}")
|
|
# 5. R-1270
|
|
if exiting:
|
|
sw.backup_now(rec, say) # the unit again after the restore (its data is the app's now)
|
|
sw.g(f"rm -rf {orig}; cp -a {unit} {orig}")
|
|
s0 = sums(unit)
|
|
say(" [R-1270] the unit's pin → " + exiting + " (pulls, exits at once) :: " + edit(exiting).replace("\n", " "))
|
|
restore(app, rec, "r1270_restore", say)
|
|
rec["r1270_state"] = w.stack(app).get("state")
|
|
rec["r1270_undo_copies"] = sw.g(f"ls -1 {NS}/backups/restore-undo/{app} 2>&1").strip()
|
|
say(f" [R-1270] state {rec['r1270_state']}, kept undo copy: {rec['r1270_undo_copies']}")
|
|
rec["original_back_2"] = put_original()
|
|
restore(app, rec, "normal_restore_2", say)
|
|
rec["item_after_normal_restore_2"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
|
|
say(f" normal restore 2: item back {rec['item_after_normal_restore_2']}, state {w.stack(app).get('state')}")
|
|
finally:
|
|
rec["controller_lines"] = sw.scrub(sw.g(
|
|
f"docker logs --since {t_start} felhom-controller 2>&1 | grep -E '{app}' | grep -iE 'D48|R-1270|refused|downloaded|restor|not start|late' | cut -c1-320"))
|
|
sw.remove(app, keep=False, say=say)
|
|
rec["after_remove"] = sw.leftovers(app, sub)
|
|
rec["test_copy_removed"] = sw.g(f"rm -rf {orig}; rmdir {NS}/backups/d48-test-original 2>/dev/null; ls -d {orig} 2>&1").strip()
|
|
rec["restore_undo_left"] = sw.g(f"ls -1 {NS}/backups/restore-undo/{app} 2>&1").strip()
|
|
json.dump(rec, open(os.path.join(d, "record.json"), "w"), ensure_ascii=False, indent=1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "")
|