Updated for auto update client wnen a new version is on the server
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user