Compare commits
3 Commits
v0.9.12
...
api-restar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31913d4a43 | ||
| 534ea41816 | |||
| f1bfab969c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ dist/
|
||||
releases/*/kx-bridge
|
||||
releases/*/extract_credentials
|
||||
releases/*/extract_credentials.exe
|
||||
config/config.ini
|
||||
data/
|
||||
@@ -1,5 +1,49 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.13] – 2026-05-20
|
||||
|
||||
============================================================
|
||||
STOPP — VOR DEM DRÜCKEN VON "UPDATE" LESEN
|
||||
============================================================
|
||||
|
||||
Der "Update"-Button ist in 0.9.11 und 0.9.12 KAPUTT.
|
||||
NICHT benutzen. Stattdessen einmalig manuell updaten —
|
||||
ab 0.9.13 funktioniert er wieder.
|
||||
|
||||
>> WINDOWS-.EXE / LINUX-BINARY-Nutzer — GEFAHR:
|
||||
Update ÜBERSCHREIBT deine kx-bridge.exe / kx-bridge mit
|
||||
einer Textdatei. Das Programm STARTET DANN NICHT MEHR
|
||||
und kann sich nicht selbst reparieren.
|
||||
--> Manuell updaten: die 0.9.13
|
||||
kx-bridge-windows.zip / kx-bridge-linux.zip von der
|
||||
Releases-Seite laden und die alte Datei ersetzen.
|
||||
Deine config/- und data/-Ordner bleiben erhalten.
|
||||
|
||||
>> DOCKER-Nutzer:
|
||||
Update führt zur Crash-Loop des Containers
|
||||
(ModuleNotFoundError: No module named '_web_assets').
|
||||
--> Manuell updaten:
|
||||
docker compose pull (oder docker compose up -d --build)
|
||||
config- + data-Volumes bleiben erhalten.
|
||||
|
||||
Ab 0.9.13 ist der In-App-Updater repariert und wieder sicher.
|
||||
============================================================
|
||||
|
||||
### Fixes
|
||||
- **Self-Update war in 0.9.11 und 0.9.12 kaputt (kritisch):** Der In-App-Updater
|
||||
ersetzte nur `kobrax_moonraker_bridge.py`. Zwei Probleme:
|
||||
- **Binary/EXE-Modus:** Er überschrieb die laufende Programmdatei
|
||||
(`sys.executable`) mit einer Python-Textdatei — übrig blieb ein nicht mehr
|
||||
startbares Programm, das sich nicht selbst reparieren kann (manueller
|
||||
Re-Download nötig).
|
||||
- **Python/Docker-Modus:** Seit 0.9.12 importiert die Hauptdatei das
|
||||
ausgelagerte `_web_assets.py` (gebündeltes Frontend), das der Updater nicht
|
||||
mitlud → `ModuleNotFoundError: No module named '_web_assets'` → Crash-Loop.
|
||||
Der Updater lädt jetzt **alle** Bridge-Module (Hauptdatei + `_web_assets.py` +
|
||||
Client + Loader) erst vollständig herunter, ersetzt sie dann atomar und
|
||||
**verweigert das Self-Update im Binary-Modus** (mit Verweis auf den manuellen
|
||||
Download).
|
||||
|
||||
## [0.9.12] – 2026-05-20
|
||||
|
||||
### Fixes
|
||||
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.13] – 2026-05-20
|
||||
|
||||
============================================================
|
||||
STOP — READ THIS BEFORE PRESSING "UPDATE"
|
||||
============================================================
|
||||
|
||||
The in-app "Update" button is BROKEN in 0.9.11 and 0.9.12.
|
||||
Do NOT use it. Update manually instead (one time), then it
|
||||
works again from 0.9.13 onward.
|
||||
|
||||
>> WINDOWS .EXE / LINUX BINARY users — DANGER:
|
||||
Pressing Update OVERWRITES your kx-bridge.exe / kx-bridge
|
||||
with a text file. The program will NOT start anymore.
|
||||
It cannot repair itself.
|
||||
--> Update manually: download the 0.9.13
|
||||
kx-bridge-windows.zip / kx-bridge-linux.zip from the
|
||||
Releases page and replace your old file.
|
||||
Your config/ and data/ folders are kept.
|
||||
|
||||
>> DOCKER users:
|
||||
Pressing Update makes the container crash-loop
|
||||
(ModuleNotFoundError: No module named '_web_assets').
|
||||
--> Update manually:
|
||||
docker compose pull (or docker compose up -d --build)
|
||||
Your config + data volumes are kept.
|
||||
|
||||
From 0.9.13 on, the in-app updater is fixed and safe again.
|
||||
============================================================
|
||||
|
||||
### Fixes
|
||||
- **Self-update was broken in 0.9.11 and 0.9.12 (critical):** the in-app updater
|
||||
only replaced `kobrax_moonraker_bridge.py`. Two problems:
|
||||
- **Binary/EXE mode:** it overwrote the running executable (`sys.executable`)
|
||||
with a Python text file, leaving an unstartable program that can't recover
|
||||
itself — manual re-download required.
|
||||
- **Python/Docker mode:** since 0.9.12 the main file imports the extracted
|
||||
`_web_assets.py` (bundled frontend), which the updater didn't fetch →
|
||||
`ModuleNotFoundError: No module named '_web_assets'` → crash loop.
|
||||
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** (pointing you to the manual download instead).
|
||||
|
||||
## [0.9.12] – 2026-05-20
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -2033,6 +2033,12 @@ class KobraXBridge:
|
||||
log.info("Manuell getrennt")
|
||||
return web.json_response({"result": "disconnected"})
|
||||
|
||||
async def handle_api_restart(self, request):
|
||||
log.info("Neustart über API angefordert")
|
||||
response = web.json_response({"status": "restarting"})
|
||||
asyncio.get_event_loop().call_later(0.3, self._restart_bridge)
|
||||
return response
|
||||
|
||||
async def handle_api_speed(self, request):
|
||||
try:
|
||||
body = await request.json()
|
||||
@@ -2877,26 +2883,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"})
|
||||
@@ -3188,6 +3219,7 @@ def build_app(bridge: KobraXBridge) -> web.Application:
|
||||
r.add_post("/api/fan", bridge.handle_api_fan)
|
||||
r.add_post("/api/connect", bridge.handle_api_connect)
|
||||
r.add_post("/api/disconnect", bridge.handle_api_disconnect)
|
||||
r.add_post("/api/restart", bridge.handle_api_restart)
|
||||
r.add_post("/api/speed", bridge.handle_api_speed)
|
||||
r.add_post("/api/ams/feed", bridge.handle_api_ams_feed)
|
||||
r.add_post("/api/ams/set_slot", bridge.handle_api_ams_set_slot)
|
||||
|
||||
Reference in New Issue
Block a user