From f1bfab969cd86e09e1004ec5fad395f36fe53073 Mon Sep 17 00:00:00 2001 From: viewit Date: Wed, 20 May 2026 15:14:37 +0200 Subject: [PATCH] build: sources for v0.9.13 --- CHANGELOG.de.md | 16 +++++++++++ CHANGELOG.md | 15 ++++++++++ VERSION | 2 +- kobrax_moonraker_bridge.py | 57 +++++++++++++++++++++++++++----------- 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.de.md b/CHANGELOG.de.md index cb5a952..9ddae1b 100644 --- a/CHANGELOG.de.md +++ b/CHANGELOG.de.md @@ -1,5 +1,21 @@ # Changelog +## [0.9.13] – 2026-05-20 + +### Fixes +- **Self-Update war in 0.9.12 kaputt (wichtig):** Der In-App-Updater ersetzte + nur `kobrax_moonraker_bridge.py`, aber seit 0.9.12 importiert diese Datei das + ausgelagerte `_web_assets.py` (gebündeltes Frontend). Ein Update auf 0.9.12 + crashte daher mit `ModuleNotFoundError: No module named '_web_assets'` und die + Bridge kam nicht wieder hoch. Der Updater lädt jetzt **alle** Bridge-Module + (Hauptdatei + `_web_assets.py` + Client + Loader) erst vollständig herunter + und ersetzt sie dann atomar — und verweigert das Self-Update im Binary-Modus + (stattdessen neue Binary/neues Docker-Image laden). + +> Falls du nach dem Update auf 0.9.12 hängengeblieben bist: einmalig das +> Docker-Image neu bauen/deployen oder die 0.9.13-Binary holen, danach +> funktioniert das Self-Update wieder. + ## [0.9.12] – 2026-05-20 ### Fixes diff --git a/CHANGELOG.md b/CHANGELOG.md index bcd7d82..51773f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [0.9.13] – 2026-05-20 + +### Fixes +- **Self-update was broken for 0.9.12 (important):** the in-app updater only + replaced `kobrax_moonraker_bridge.py`, but since 0.9.12 that file imports the + extracted `_web_assets.py` (bundled frontend). Updating to 0.9.12 therefore + crashed with `ModuleNotFoundError: No module named '_web_assets'` and the + bridge wouldn't come back up. The updater now downloads **all** bridge modules + (main file + `_web_assets.py` + client + loaders), fully, then swaps them + atomically — and refuses to self-update in binary mode (use the new + binary/Docker image instead). + +> If you got stuck on 0.9.12 after pressing update: rebuild/redeploy the Docker +> image or grab the 0.9.13 binary once, then self-update works again. + ## [0.9.12] – 2026-05-20 ### Fixes diff --git a/VERSION b/VERSION index 583b27a..62ea259 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.12 +0.9.13 diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 1135b6c..42d6c5e 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -2877,26 +2877,51 @@ class KobraXBridge: except Exception as e: return web.json_response({"error": str(e)}, status=502) + # Bridge-Python-Module, die das Self-Update mitziehen muss. Die Hauptdatei + # importiert _web_assets (gebündeltes Frontend) etc. – wird nur die Hauptdatei + # ersetzt, crasht die neue Version mit ModuleNotFoundError. Daher alle laden. + _UPDATE_FILES = [ + "kobrax_moonraker_bridge.py", + "_web_assets.py", + "kobrax_client.py", + "config_loader.py", + "env_loader.py", + ] + async def handle_api_update_apply(self, request): data = await request.json() - download_url = data.get("download_url", "") - new_tag = data.get("tag", "") - if not download_url: - return web.json_response({"error": "download_url fehlt"}, status=400) - script_path = pathlib.Path(sys.executable if getattr(sys, "frozen", False) else __file__).resolve() + new_tag = data.get("tag", "") + if getattr(sys, "frozen", False): + return web.json_response( + {"error": "Self-Update wird im Binary-Modus nicht unterstützt – " + "bitte neue Binary/Docker-Image laden."}, status=400) + if not new_tag: + return web.json_response({"error": "tag fehlt"}, status=400) + + app_dir = pathlib.Path(__file__).resolve().parent try: + # Phase 1: ALLE Dateien herunterladen (in .new), nichts ersetzen. + downloaded: list[tuple[pathlib.Path, bytes]] = [] async with aiohttp.ClientSession() as session: - async with session.get(download_url, timeout=aiohttp.ClientTimeout(total=30)) as resp: - if resp.status != 200: - return web.json_response({"error": f"Download HTTP {resp.status}"}, status=502) - content = await resp.read() - # Atomisch ersetzen - tmp = script_path.with_suffix(".py.new") - tmp.write_bytes(content) - os.replace(tmp, script_path) - if new_tag: - self._write_version(new_tag.lstrip("v")) - log.info(f"Update auf {new_tag} installiert, starte neu …") + for fname in self._UPDATE_FILES: + url = f"{self.GITEA_RAW_BASE}/{new_tag}/{fname}" + async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as resp: + if resp.status != 200: + # _web_assets.py o.ä. existiert evtl. in älteren Tags nicht – + # Hauptdatei ist Pflicht, optionale dürfen fehlen. + if fname == "kobrax_moonraker_bridge.py": + return web.json_response( + {"error": f"Download {fname}: HTTP {resp.status}"}, status=502) + log.warning(f"Update: {fname} nicht im Release ({resp.status}) – übersprungen") + continue + downloaded.append((app_dir / fname, await resp.read())) + # Phase 2: atomar ersetzen (erst nach komplettem, erfolgreichem Download) + for path, content in downloaded: + tmp = path.with_suffix(path.suffix + ".new") + tmp.write_bytes(content) + os.replace(tmp, path) + self._write_version(new_tag.lstrip("v")) + log.info(f"Update auf {new_tag} installiert ({len(downloaded)} Dateien), starte neu …") except Exception as e: return web.json_response({"error": str(e)}, status=502) response = web.json_response({"status": "updating"})