Compare commits

1 Commits

Author SHA1 Message Date
f1bfab969c build: sources for v0.9.13 2026-05-20 15:14:37 +02:00
4 changed files with 73 additions and 17 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -1 +1 @@
0.9.12
0.9.13

View File

@@ -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()
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:
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:
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")
# _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, script_path)
if new_tag:
os.replace(tmp, path)
self._write_version(new_tag.lstrip("v"))
log.info(f"Update auf {new_tag} installiert, starte neu …")
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"})