Updated for auto update client wnen a new version is on the server

This commit is contained in:
2026-06-26 11:14:18 +03:00
parent a4f84bbbfa
commit b701a9e07a
+79 -1
View File
@@ -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 os
import sys import sys
import subprocess import subprocess
@@ -9,6 +9,9 @@ import grp
from dependency_utils import install_package_from_wheel, check_and_install_dependencies 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 # Global configuration mode flag
CONFIGURATION_MODE = False CONFIGURATION_MODE = False
@@ -396,10 +399,85 @@ if TEMPLATE_IMAGE:
print("Clone and configure this image before deploying.") print("Clone and configure this image before deploying.")
print("=" * 60) 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) # Attempt to sync with server at startup (non-blocking failures are logged and ignored)
# Skipped for the base template image. # Skipped for the base template image.
if not TEMPLATE_IMAGE: if not TEMPLATE_IMAGE:
sync_config_with_server() sync_config_with_server()
check_and_apply_update()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Card presence flag controls whether RFID functions are started # Card presence flag controls whether RFID functions are started