Files
felhom.eu/documentation/audits/fixes-2026-10-21/tools/offrestore.py
T

41 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""offrestore.py APP — the restore wizard's „Teljes visszaállítás" for one app, endpoint-level (the form POSTs the
wizard sends): 1) /backup/offbox/restore mode=full (size check), 2) the same with confirm=1 (download to the scratch),
3) /backup/offbox/reconstitute confirm=1 (files + database into the live app). Waits on /api/backup/restore-status
after 2 and 3. Prints redirects (flash keys) and the status lines only."""
import json, os, subprocess, sys, time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import ctl
app = sys.argv[1]
def say(*a): print(time.strftime("%H:%M:%SZ", time.gmtime()), *a, flush=True)
def post(path, fields):
if ctl._SESS is None:
ctl.login()
args = ["curl", "-sk", "-D", "-", "-o", "/dev/null", "-H", ctl.HOST, "-H", "Cookie: " + ctl._SESS, "-X", "POST",
"--data-urlencode", "_csrf=" + ctl._CSRF]
for k, v in fields.items():
args += ["--data-urlencode", f"{k}={v}"]
out = subprocess.run(args + [ctl.BASE + path], capture_output=True, text=True, timeout=300).stdout
loc = [l for l in out.splitlines() if l.lower().startswith("location")]
say("POST", path, fields, "->", out.splitlines()[0] if out else "?", loc[:1])
def wait():
t0, last = time.time(), None
while time.time() - t0 < 3600:
c, d = ctl.call("GET", "/api/backup/restore-status", timeout=90)
dd = (d.get("data") or {}) if isinstance(d, dict) else {}
s = json.dumps(dd, ensure_ascii=False)[:600]
if s != last:
say("status:", s); last = s
if not dd.get("running") and time.time() - t0 > 10:
return
time.sleep(10)
ack = "--ack-placement" in sys.argv # the wizard's second confirm: „yes, into a different place than the backup recorded"
if "--reconstitute-only" not in sys.argv:
post("/backup/offbox/restore", {"app": app, "mode": "full"})
post("/backup/offbox/restore", {"app": app, "mode": "full", "confirm": "1"})
wait()
post("/backup/offbox/reconstitute", dict({"app": app, "confirm": "1"}, **({"ack_placement": "1"} if ack else {})))
wait()
c, d = ctl.call("GET", f"/api/stacks/{app}")
say("app state:", (d.get("data") or {}).get("state"))