From 5456d507741c49d7fdbc2fd3064eccf48c609cb4 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 4 Oct 2026 01:31:38 +0200 Subject: [PATCH] feat(hub): add three image acquisition sources (official repo, custom repo, manual provided) --- src/hub/Server.py | 25 ++++++++++------ src/hub/server_models.py | 2 +- src/hub/update_manager.py | 13 +++++--- src/hub/web_ui.py | 63 +++++++++++++++++++++++++++++---------- tests/test_updates.py | 26 +++++++++++----- 5 files changed, 93 insertions(+), 36 deletions(-) diff --git a/src/hub/Server.py b/src/hub/Server.py index b0ac936..1a42aef 100644 --- a/src/hub/Server.py +++ b/src/hub/Server.py @@ -1530,19 +1530,26 @@ def update_server_update_config(req: ServerUpdateConfigRequest, auth_user: str = config = SERVER_STATE.get("config", {}) mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER - mode = (req.update_mode or "auto").lower() - if mode not in ("auto", "manual"): - raise HTTPException(status_code=400, detail="Invalid update_mode. Must be 'auto' (fetch from upstream) or 'manual' (provided only).") + mode = (req.update_mode or "official").lower() + if mode not in ("official", "custom", "manual", "auto"): + raise HTTPException( + status_code=400, + detail="Invalid update_mode. Must be 'official' (Official production repo), 'custom' (Custom upstream repo), or 'manual' (Provided only)." + ) mgr.auto_update_enabled = bool(req.auto_update_enabled) + if mode == "auto": + mode = "official" if (not req.upstream_url or req.upstream_url == mgr.DEFAULT_GITEA_RELEASE_URL) else "custom" mgr.update_mode = mode - if req.upstream_url: + + if mode == "official": + mgr.upstream_url = mgr.DEFAULT_GITEA_RELEASE_URL + elif mode == "custom" and req.upstream_url: mgr.upstream_url = req.upstream_url.strip() config["auto_update_enabled"] = mgr.auto_update_enabled config["update_mode"] = mgr.update_mode - if req.upstream_url: - config["upstream_release_url"] = mgr.upstream_url + config["upstream_release_url"] = mgr.upstream_url cfg_file = config.get("config_file_path") or CONFIG_FILE_NAME try: @@ -1551,8 +1558,8 @@ def update_server_update_config(req: ServerUpdateConfigRequest, auth_user: str = except Exception as e: print(f"[!] Warning: Could not save config file: {e}") - # If enabled and in auto mode, trigger upstream check - if mgr.auto_update_enabled and mgr.update_mode == "auto": + # If enabled and in automatic fetch mode (official or custom), trigger upstream check + if mgr.auto_update_enabled and mgr.update_mode in ("official", "custom", "auto"): try: mgr.check_upstream_updates(force=False, auto_download_clients=True) except Exception: @@ -2372,7 +2379,7 @@ async def update_checker_loop(interval_hours: int = 1): try: mgr: ServerUpdateManager = SERVER_STATE.get("update_manager") or UPDATE_MANAGER loop = asyncio.get_running_loop() - should_dl = bool(mgr.auto_update_enabled and mgr.update_mode == "auto") + should_dl = bool(mgr.auto_update_enabled and mgr.update_mode in ("official", "custom", "auto")) await loop.run_in_executor(None, lambda: mgr.check_upstream_updates(force=False, auto_download_clients=should_dl)) except Exception as e: print(f"[!] Warning in update checker background loop: {e}") diff --git a/src/hub/server_models.py b/src/hub/server_models.py index 4c3a204..7b7574b 100644 --- a/src/hub/server_models.py +++ b/src/hub/server_models.py @@ -28,7 +28,7 @@ class ServerUpdateCheckRequest(BaseModel): class ServerUpdateConfigRequest(BaseModel): auto_update_enabled: bool = True - update_mode: str = "auto" # "auto" | "manual" + update_mode: str = "official" # "official" | "custom" | "manual" upstream_url: Optional[str] = None diff --git a/src/hub/update_manager.py b/src/hub/update_manager.py index 363f5f8..ba8453c 100644 --- a/src/hub/update_manager.py +++ b/src/hub/update_manager.py @@ -31,11 +31,16 @@ class ServerUpdateManager: update_mode: str = "auto" ): self.update_dir = update_dir or os.path.join(os.getcwd(), "updates") - os.makedirs(self.update_dir, exist_ok=True) self.upstream_url = upstream_url or self.DEFAULT_GITEA_RELEASE_URL self.check_enabled = check_enabled self.auto_update_enabled = auto_update_enabled - self.update_mode = update_mode.lower() if update_mode else "auto" + raw_mode = (update_mode or "official").lower() + if raw_mode in ("official", "custom", "manual"): + self.update_mode = raw_mode + elif raw_mode == "auto": + self.update_mode = "official" if (not upstream_url or upstream_url == self.DEFAULT_GITEA_RELEASE_URL) else "custom" + else: + self.update_mode = "official" # Cached upstream release state self.last_checked_utc: Optional[str] = None @@ -454,8 +459,8 @@ class ServerUpdateManager: if self.server_update_available: print(f"[!] 🔔 LOGAR Server Update Available: Current v{LOGAR_VERSION} -> Upstream v{upstream_ver}") - # Automatically download and cache client update packages on the Hub if in auto mode - should_auto_download = auto_download_clients and (self.update_mode == "auto" or force) + # Automatically download and cache client update packages on the Hub if in official or custom repo mode + should_auto_download = auto_download_clients and (self.update_mode in ("official", "custom", "auto") or force) if should_auto_download and assets: hosted = self.get_hosted_packages() needs_win = not hosted.get("windows", {}).get("exists") or is_newer_version(upstream_ver, hosted.get("windows", {}).get("version", "0.0.0")) diff --git a/src/hub/web_ui.py b/src/hub/web_ui.py index 5f6b711..5feeb33 100644 --- a/src/hub/web_ui.py +++ b/src/hub/web_ui.py @@ -193,8 +193,13 @@ class WebUIRenderer: hosted_client_ver = update_status.get("hosted_client_version") or LOGAR_VERSION hosted_packages = update_status.get("hosted_packages", {}) auto_update_enabled = update_status.get("auto_update_enabled", True) - update_mode = update_status.get("update_mode", "auto") + update_mode = (update_status.get("update_mode") or "official").lower() upstream_url = update_status.get("upstream_url") or "https://gitea.eibl.tech/api/v1/repos/me0nline/LOGAR/releases/latest" + is_mode_official = (update_mode in ("official", "auto")) and ("gitea.eibl.tech" in str(upstream_url)) + is_mode_custom = (update_mode == "custom") or (update_mode == "auto" and "gitea.eibl.tech" not in str(upstream_url)) + is_mode_manual = (update_mode == "manual") + if not is_mode_official and not is_mode_custom and not is_mode_manual: + is_mode_official = True # Fetch clients safely conn = sqlite3.connect(db_path) @@ -712,30 +717,44 @@ class WebUIRenderer: - +
- 📥 Image Acquisition & Hosting Mode + 📥 Image Acquisition Source & Hosting Mode
+ + + + + +
-
- - + + +
+ +
@@ -2017,11 +2036,25 @@ class WebUIRenderer: }} }} + function toggleUpdateModeUi() {{ + const isCustom = document.getElementById('modeCustom') && document.getElementById('modeCustom').checked; + const customSec = document.getElementById('customUpstreamSection'); + if (customSec) {{ + customSec.style.display = isCustom ? 'block' : 'none'; + }} + }} + async function saveUpdateConfig(e) {{ if (e) e.preventDefault(); const isEnabled = document.getElementById('autoUpdateOn') ? document.getElementById('autoUpdateOn').checked : true; - const modeAuto = document.getElementById('modeAuto') ? document.getElementById('modeAuto').checked : true; - const mode = modeAuto ? 'auto' : 'manual'; + + let mode = 'official'; + if (document.getElementById('modeCustom') && document.getElementById('modeCustom').checked) {{ + mode = 'custom'; + }} else if (document.getElementById('modeManual') && document.getElementById('modeManual').checked) {{ + mode = 'manual'; + }} + const upstreamUrl = document.getElementById('upstreamReleaseUrl') ? document.getElementById('upstreamReleaseUrl').value.trim() : ''; showAlert('Saving fleet auto-update settings...'); @@ -2032,7 +2065,7 @@ class WebUIRenderer: body: JSON.stringify({{ auto_update_enabled: isEnabled, update_mode: mode, - upstream_url: upstreamUrl || undefined + upstream_url: (mode === 'custom' && upstreamUrl) ? upstreamUrl : undefined }}) }}); const data = await res.json(); diff --git a/tests/test_updates.py b/tests/test_updates.py index 0227796..c9015cf 100644 --- a/tests/test_updates.py +++ b/tests/test_updates.py @@ -227,14 +227,25 @@ class TestUpdateSystem(unittest.TestCase): self.assertFalse(chk2["update_available"]) self.assertIn("disabled", chk2.get("reason", "")) - # 3. Toggle back ON with 'auto' mode - cfg_req2 = ServerUpdateConfigRequest(auto_update_enabled=True, update_mode="auto") + # 3. Toggle back ON with 'official' mode + cfg_req2 = ServerUpdateConfigRequest(auto_update_enabled=True, update_mode="official") res2 = Server.update_server_update_config(req=cfg_req2, auth_user="admin") self.assertEqual(res2["status"], "success") self.assertTrue(self.update_mgr.auto_update_enabled) - self.assertEqual(self.update_mgr.update_mode, "auto") + self.assertEqual(self.update_mgr.update_mode, "official") - # 4. Verify WebUI renders without download buttons in packages table + # 4. Configure 'custom' mode with custom upstream URL + cfg_req3 = ServerUpdateConfigRequest( + auto_update_enabled=True, + update_mode="custom", + upstream_url="https://git.corp.local/api/v1/releases/latest" + ) + res3 = Server.update_server_update_config(req=cfg_req3, auth_user="admin") + self.assertEqual(res3["status"], "success") + self.assertEqual(self.update_mgr.update_mode, "custom") + self.assertEqual(self.update_mgr.upstream_url, "https://git.corp.local/api/v1/releases/latest") + + # 5. Verify WebUI renders official, custom, manual mode switches and no download buttons html = Server.WebUIRenderer.render_dashboard_html( server_state=Server.SERVER_STATE, ingest_queue=Server.INGEST_QUEUE @@ -242,9 +253,10 @@ class TestUpdateSystem(unittest.TestCase): self.assertNotIn("/api/client/update/download?os=windows", html) self.assertNotIn("/api/client/update/download?os=linux", html) self.assertIn("Fleet Auto-Update Service", html) - self.assertIn("Image Acquisition & Hosting Mode", html) - self.assertIn("autoUpdateEnabledRadio", html) - self.assertIn("updateModeRadio", html) + self.assertIn("Image Acquisition Source & Hosting Mode", html) + self.assertIn("modeOfficial", html) + self.assertIn("modeCustom", html) + self.assertIn("modeManual", html) self.assertIn("Ready", html)