From b701a9e07a53a4ae062c5a2dfbd5f6d479260232 Mon Sep 17 00:00:00 2001 From: ske087 Date: Fri, 26 Jun 2026 11:14:18 +0300 Subject: [PATCH] Updated for auto update client wnen a new version is on the server --- app.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 91c5a48..bfc06fb 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -#App version 2.9 - Added configuration mode for "notconfig" devices +#App version 3.0 - Auto-update from server + notconfig/TestTemplate workflow import os import sys import subprocess @@ -9,6 +9,9 @@ import grp from dependency_utils import install_package_from_wheel, check_and_install_dependencies +# Application version (compared against server release to decide whether to auto-update) +APP_VERSION = "3.0" + # Global configuration mode flag CONFIGURATION_MODE = False @@ -396,10 +399,85 @@ if TEMPLATE_IMAGE: print("Clone and configure this image before deploying.") print("=" * 60) +# --------------------------------------------------------------------------- +# Auto-update from server release +# --------------------------------------------------------------------------- + +def _parse_version(v: str): + """Convert '3.0' or '2.10' to a comparable tuple of ints.""" + try: + return tuple(int(x) for x in str(v).strip().split('.')) + except Exception: + return (0,) + + +def check_and_apply_update(): + """ + Called once at startup (after config sync, before heavy init). + 1. Ask the server for the latest available client version. + 2. If the server version is newer, download the zip, extract app.py, + replace the running script, and restart the process. + Template images (TEMPLATE_IMAGE=True) are never updated this way. + """ + import urllib.parse as _up + import zipfile as _zf + import tempfile as _tf + try: + parsed = _up.urlparse(APP_CONFIG.get("server_log_url", "http://rpi-ansible.sibiusb.harting.intra:80/logs")) + server_base = f"{parsed.scheme}://{parsed.netloc}" + + import requests as _req + resp = _req.get(f"{server_base}/api/wmt/client/version", timeout=5) + if resp.status_code != 200: + print(f"Update check: server returned {resp.status_code} – skipping.") + return + + meta = resp.json() + server_version = meta.get("version", "0") + if _parse_version(server_version) <= _parse_version(APP_VERSION): + print(f"✅ App is up to date (v{APP_VERSION}).") + return + + print(f"🔄 New version available: v{server_version} (current: v{APP_VERSION}). Downloading...") + dl_resp = _req.get(f"{server_base}/api/wmt/client/download", timeout=30, stream=True) + dl_resp.raise_for_status() + + # Write zip to a temp file + with _tf.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_zip: + for chunk in dl_resp.iter_content(chunk_size=65536): + tmp_zip.write(chunk) + tmp_zip_path = tmp_zip.name + + # Extract ONLY app.py from the zip – config, data, and every other + # file on the device are intentionally left untouched. + # We never call zf.extractall() to avoid accidentally overwriting anything. + with _zf.ZipFile(tmp_zip_path, 'r') as zf: + if 'app.py' not in zf.namelist(): + print("❌ Update zip does not contain app.py – aborting.") + os.unlink(tmp_zip_path) + return + with _tf.NamedTemporaryFile(suffix='.py', delete=False, dir='.') as tmp_app: + tmp_app.write(zf.read('app.py')) # read only 'app.py' by name + tmp_app_path = tmp_app.name + + os.unlink(tmp_zip_path) + + # Atomically replace app.py and restart + current_script = os.path.abspath(__file__) + os.replace(tmp_app_path, current_script) + print(f"✅ Updated to v{server_version}. Restarting...") + os.execv(sys.executable, [sys.executable] + sys.argv) + # execv replaces the process – code below is never reached + + except Exception as e: + print(f"Update check failed (non-fatal): {e}") + + # Attempt to sync with server at startup (non-blocking – failures are logged and ignored) # Skipped for the base template image. if not TEMPLATE_IMAGE: sync_config_with_server() + check_and_apply_update() # --------------------------------------------------------------------------- # Card presence flag – controls whether RFID functions are started