Soften updater stub messaging (no error)

This commit is contained in:
Aaron
2025-12-10 19:15:30 -05:00
parent c85df728b7
commit 47bd69a092
7 changed files with 445 additions and 4 deletions

View File

@@ -2,6 +2,8 @@
import json, os, subprocess, socket, shutil, pathlib, datetime
from http.server import BaseHTTPRequestHandler, HTTPServer
import re
import urllib.request
import hashlib
HOST = "127.0.0.1"
PORT = 4000
@@ -27,6 +29,17 @@ ALL_PATTERNS = [
*SECURITY_PATTERNS,
]
# Release updater constants
VERSION_FILE = pathlib.Path("/etc/pikit/version")
WEB_VERSION_FILE = pathlib.Path("/var/www/pikit-web/data/version.json")
UPDATE_STATE_DIR = pathlib.Path("/var/lib/pikit-update")
UPDATE_STATE = UPDATE_STATE_DIR / "state.json"
UPDATE_LOCK = pathlib.Path("/var/run/pikit-update.lock")
DEFAULT_MANIFEST_URL = os.environ.get(
"PIKIT_MANIFEST_URL",
"https://git.44r0n.cc/44r0n7/pi-kit/releases/latest/download/manifest.json",
)
class FirewallToolMissing(Exception):
"""Raised when ufw is unavailable but a firewall change was requested."""
@@ -459,6 +472,106 @@ def reset_firewall():
subprocess.run(["ufw", "--force", "enable"], check=False)
def read_current_version():
if VERSION_FILE.exists():
return VERSION_FILE.read_text().strip()
if WEB_VERSION_FILE.exists():
try:
return json.loads(WEB_VERSION_FILE.read_text()).get("version", "unknown")
except Exception:
return "unknown"
return "unknown"
def load_update_state():
UPDATE_STATE_DIR.mkdir(parents=True, exist_ok=True)
if UPDATE_STATE.exists():
try:
return json.loads(UPDATE_STATE.read_text())
except Exception:
pass
return {
"current_version": read_current_version(),
"latest_version": None,
"last_check": None,
"status": "unknown",
"message": "",
"auto_check": False,
"in_progress": False,
"progress": None,
}
def save_update_state(state: dict):
UPDATE_STATE_DIR.mkdir(parents=True, exist_ok=True)
UPDATE_STATE.write_text(json.dumps(state, indent=2))
def fetch_manifest(url: str = None):
target = url or DEFAULT_MANIFEST_URL
resp = urllib.request.urlopen(target, timeout=10)
data = resp.read()
manifest = json.loads(data.decode())
return manifest
def check_for_update():
state = load_update_state()
state["in_progress"] = True
state["progress"] = "Checking for updates…"
save_update_state(state)
try:
manifest = fetch_manifest()
latest = manifest.get("version") or manifest.get("latest_version")
state["latest_version"] = latest
state["last_check"] = datetime.datetime.utcnow().isoformat() + "Z"
if latest and latest != state.get("current_version"):
state["status"] = "update_available"
state["message"] = manifest.get("changelog", "Update available")
else:
state["status"] = "up_to_date"
state["message"] = "Up to date"
except Exception as e:
state["status"] = "error"
state["message"] = f"Check failed: {e}"
finally:
state["in_progress"] = False
state["progress"] = None
save_update_state(state)
return state
def apply_update_stub():
"""Placeholder apply that marks not implemented but keeps state coherent."""
state = load_update_state()
state["in_progress"] = True
state["progress"] = "Applying update…"
save_update_state(state)
try:
# TODO: implement download + install
# For now, report that nothing was changed to avoid alarming errors.
state["status"] = "up_to_date"
state["message"] = "Updater not implemented yet; no changes were made."
# Reset latest_version so the chip returns to neutral
state["latest_version"] = state.get("current_version")
except Exception as e:
state["status"] = "error"
state["message"] = f"Apply failed: {e}"
finally:
state["in_progress"] = False
state["progress"] = None
save_update_state(state)
return state
def rollback_update_stub():
state = load_update_state()
state["status"] = "up_to_date"
state["message"] = "Rollback not implemented yet; no changes were made."
save_update_state(state)
return state
class Handler(BaseHTTPRequestHandler):
"""Minimal JSON API for the dashboard (status, services, updates, reset)."""
def _send(self, code, data):
@@ -551,6 +664,10 @@ class Handler(BaseHTTPRequestHandler):
elif self.path.startswith("/api/updates/config"):
cfg = read_updates_config()
self._send(200, cfg)
elif self.path.startswith("/api/update/status"):
state = load_update_state()
state["current_version"] = read_current_version()
self._send(200, state)
else:
self._send(404, {"error": "not found"})
@@ -579,6 +696,20 @@ class Handler(BaseHTTPRequestHandler):
except Exception as e:
dbg(f"Failed to apply updates config: {e}")
return self._send(500, {"error": str(e)})
if self.path.startswith("/api/update/check"):
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)
if self.path.startswith("/api/update/rollback"):
state = rollback_update_stub()
return self._send(200, state)
if self.path.startswith("/api/update/auto"):
state = load_update_state()
state["auto_check"] = bool(payload.get("enable"))
save_update_state(state)
return self._send(200, state)
if self.path.startswith("/api/services/add"):
name = payload.get("name")
port = int(payload.get("port", 0))