From b2307e0b0b74c7265bc2081860c1bb877c2c210d Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 10 Dec 2025 19:55:44 -0500 Subject: [PATCH] Run apply/rollback in background via systemd-run and add CLI modes --- pikit-api.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/pikit-api.py b/pikit-api.py index 9f5c932..67459d7 100644 --- a/pikit-api.py +++ b/pikit-api.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -import json, os, subprocess, socket, shutil, pathlib, datetime, tarfile +import json, os, subprocess, socket, shutil, pathlib, datetime, tarfile, sys, argparse from http.server import BaseHTTPRequestHandler, HTTPServer import re import urllib.request @@ -741,6 +741,28 @@ def rollback_update_stub(): return state +def start_background_task(mode: str): + """ + Kick off a background update/rollback via systemd-run so nginx/API restarts + do not break the caller connection. + mode: "apply" or "rollback" + """ + assert mode in ("apply", "rollback"), "invalid mode" + unit = f"pikit-update-{mode}" + flag = f"--{mode}-update" + cmd = [ + "systemd-run", + "--unit", + unit, + "--quiet", + "/usr/bin/env", + "python3", + str(API_PATH), + flag, + ] + subprocess.run(cmd, check=False) + + def acquire_lock(): try: ensure_dir(UPDATE_LOCK.parent) @@ -899,11 +921,20 @@ class Handler(BaseHTTPRequestHandler): state = check_for_update() return self._send(200, state) if self.path.startswith("/api/update/apply"): - state = apply_update_stub() - return self._send(200, state) + # Start background apply to avoid breaking caller during service restart + start_background_task("apply") + state = load_update_state() + state["status"] = "in_progress" + state["message"] = "Starting background apply" + save_update_state(state) + return self._send(202, state) if self.path.startswith("/api/update/rollback"): - state = rollback_update_stub() - return self._send(200, state) + start_background_task("rollback") + state = load_update_state() + state["status"] = "in_progress" + state["message"] = "Starting rollback" + save_update_state(state) + return self._send(202, state) if self.path.startswith("/api/update/auto"): state = load_update_state() state["auto_check"] = bool(payload.get("enable")) @@ -1043,4 +1074,20 @@ def main(): if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Pi-Kit API / updater") + parser.add_argument("--apply-update", action="store_true", help="Apply latest release (non-HTTP mode)") + parser.add_argument("--check-update", action="store_true", help="Check for latest release (non-HTTP mode)") + parser.add_argument("--rollback-update", action="store_true", help="Rollback to last backup (non-HTTP mode)") + args = parser.parse_args() + + if args.apply_update: + apply_update_stub() + sys.exit(0) + if args.check_update: + check_for_update() + sys.exit(0) + if args.rollback_update: + rollback_update_stub() + sys.exit(0) + main()