diff --git a/src/hub/Server.py b/src/hub/Server.py index bb2b515..b0ac936 100644 --- a/src/hub/Server.py +++ b/src/hub/Server.py @@ -1497,6 +1497,13 @@ def sync_upstream_packages_api(auth_user: str = Depends(require_admin_auth)): """ Downloads latest client binary packages from upstream release assets to local Hub storage. """ + db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE) + if not is_feature_licensed("auto_update", db_path): + raise HTTPException( + status_code=403, + detail="Fleet package acquisition and distribution requires an active license with the 'auto_update' capability." + ) + mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER results = mgr.sync_upstream_packages(force=True) return { @@ -1513,6 +1520,13 @@ def update_server_update_config(req: ServerUpdateConfigRequest, auth_user: str = """ Updates fleet auto-update settings (on/off switch and automatic vs manual acquisition mode). """ + db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE) + if not is_feature_licensed("auto_update", db_path): + raise HTTPException( + status_code=403, + detail="Fleet auto-update settings configuration requires an active license with the 'auto_update' capability." + ) + config = SERVER_STATE.get("config", {}) mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER @@ -1562,12 +1576,10 @@ def check_client_update( """ Invoked by edge forwarders to check whether a newer client binary package is available. """ - import os as _os target_os = os_name or os or "windows" - mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER + db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE) # Record check timestamp in database - db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE) if target_os: now_sql = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") try: @@ -1581,6 +1593,15 @@ def check_client_update( except Exception: pass + if not is_feature_licensed("auto_update", db_path): + return { + "update_available": False, + "latest_version": LOGAR_VERSION, + "current_version": current_version, + "reason": "Fleet auto-update is not authorized by the server license." + } + + mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER return mgr.check_client_update(client_id=client_id, os_type=target_os, current_version=current_version, arch=arch) @@ -1597,6 +1618,13 @@ def download_client_update_package( import os as _os import email.utils target_os = os_name or os or "windows" + db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE) + if not is_feature_licensed("auto_update", db_path): + raise HTTPException( + status_code=403, + detail="Client binary package fleet distribution requires an active license with the 'auto_update' capability." + ) + mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER pkg_tuple = mgr.get_client_package_path(os_type=target_os) if not pkg_tuple: diff --git a/src/hub/web_ui.py b/src/hub/web_ui.py index 703ac3c..5f6b711 100644 --- a/src/hub/web_ui.py +++ b/src/hub/web_ui.py @@ -680,6 +680,8 @@ class WebUIRenderer: + {auto_update_lock_banner} +
@@ -688,43 +690,43 @@ class WebUIRenderer:
Configure hot-upgrade execution and choose how client binary images are acquired & hosted
- {('🔄 FLEET AUTO-UPDATE LICENSED' if is_auto_update_unlocked else 'COMMUNITY / UNRESTRICTED')} + {('🔄 FLEET AUTO-UPDATE LICENSED' if is_auto_update_unlocked else '🔒 ENTERPRISE LICENSE REQUIRED')}
-
+
⚡ Fleet Auto-Update Service
-
+
📥 Image Acquisition & Hosting Mode
- - + +
diff --git a/tests/test_license_authority.py b/tests/test_license_authority.py index 4477e84..b3cec2c 100644 --- a/tests/test_license_authority.py +++ b/tests/test_license_authority.py @@ -323,12 +323,12 @@ class TestServerLicensingAndWebUI(unittest.TestCase): conn.commit() conn.close() - # WebUI should render update controls and community status + # WebUI should render locked banner and disabled update controls resp = Server.serve_webui() html = resp.body.decode("utf-8") - self.assertIn("Fleet Distribution & Auto-Update Policy", html) - self.assertIn("COMMUNITY / UNRESTRICTED", html) - self.assertIn("autoUpdateEnabledRadio", html) + self.assertIn("Fleet Auto-Update is Inactive & Locked", html) + self.assertIn("ENTERPRISE LICENSE REQUIRED", html) + self.assertIn("disabled", html) def test_json_lic_package_import(self): """Verifies importing an activated .lic JSON package via /api/license/import.""" diff --git a/tests/test_updates.py b/tests/test_updates.py index fe835bd..0227796 100644 --- a/tests/test_updates.py +++ b/tests/test_updates.py @@ -130,14 +130,14 @@ class TestUpdateSystem(unittest.TestCase): f.write(b"WIN_CLIENT_V0360") self.update_mgr.register_client_package("windows", dummy_pkg, version="0.3.6.0") - # 3. Client update check endpoint (Allowed regardless of license) + # 3. Client update check endpoint (Unlicensed blocks client auto_update) chk_unlicensed = Server.check_client_update( client_id="win-client-01", os="windows", current_version="0.3.5.1" ) - self.assertTrue(chk_unlicensed["update_available"]) - self.assertEqual(chk_unlicensed["latest_version"], "0.3.6.0") + self.assertFalse(chk_unlicensed["update_available"]) + self.assertIn("not authorized", chk_unlicensed.get("reason", "")) # 4. Enable auto_update feature flag in license config import sqlite3