v1.8.17: Atomarer Config-Write + Backup-Fallback
Verhindert Datenverlust wenn HAOS den Container während eines Saves stoppt. Schreibt erst config.json.tmp, dann atomares os.replace(). Hält config.json.bak als Fallback für den nächsten Start. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
name: ShineBridge
|
name: ShineBridge
|
||||||
version: "1.8.16"
|
version: "1.8.17"
|
||||||
slug: shinebridge
|
slug: shinebridge
|
||||||
description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI
|
description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI
|
||||||
url: https://gitea.bitfire.work/retr0/shinebridge
|
url: https://gitea.bitfire.work/retr0/shinebridge
|
||||||
|
|||||||
+33
-15
@@ -110,25 +110,34 @@ def _defaults() -> Dict[str, Any]:
|
|||||||
"z2m_base": "zigbee2mqtt",
|
"z2m_base": "zigbee2mqtt",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _load_json_safe(path: str) -> Optional[Dict]:
|
||||||
|
"""Lädt eine JSON-Datei; gibt None zurück wenn fehlend oder korrupt."""
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
return json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("JSON-Ladefehler %s: %s", path, e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_config() -> Dict[str, Any]:
|
def load_config() -> Dict[str, Any]:
|
||||||
cfg = _defaults()
|
cfg = _defaults()
|
||||||
|
# MQTT-Grundeinstellungen aus HAOS-Options (überschreibbar durch config.json)
|
||||||
if os.path.exists(HA_OPTIONS_PATH):
|
if os.path.exists(HA_OPTIONS_PATH):
|
||||||
try:
|
ha = _load_json_safe(HA_OPTIONS_PATH) or {}
|
||||||
with open(HA_OPTIONS_PATH) as f:
|
for k in ("mqtt_broker", "mqtt_port", "mqtt_user", "mqtt_pass"):
|
||||||
ha = json.load(f)
|
if k in ha:
|
||||||
for k in ("mqtt_broker", "mqtt_port", "mqtt_user", "mqtt_pass"):
|
cfg[k] = ha[k]
|
||||||
if k in ha:
|
# Eigene persistente Config — Hauptdatei, dann Backup als Fallback
|
||||||
cfg[k] = ha[k]
|
loaded = _load_json_safe(CONFIG_PATH)
|
||||||
except Exception as e:
|
if loaded is None and os.path.exists(CONFIG_PATH + ".bak"):
|
||||||
log.warning("HA options Fehler: %s", e)
|
log.warning("config.json korrupt — lade Backup")
|
||||||
if os.path.exists(CONFIG_PATH):
|
loaded = _load_json_safe(CONFIG_PATH + ".bak")
|
||||||
try:
|
if loaded:
|
||||||
with open(CONFIG_PATH) as f:
|
cfg.update(loaded)
|
||||||
cfg.update(json.load(f))
|
|
||||||
except Exception as e:
|
|
||||||
log.warning("Config-Datei Fehler: %s", e)
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
def save_config():
|
def save_config():
|
||||||
data = {
|
data = {
|
||||||
"mqtt_broker": State.mqtt_cfg.get("mqtt_broker", ""),
|
"mqtt_broker": State.mqtt_cfg.get("mqtt_broker", ""),
|
||||||
@@ -150,8 +159,17 @@ def save_config():
|
|||||||
"surplus_devices": State.surplus_devices_cfg,
|
"surplus_devices": State.surplus_devices_cfg,
|
||||||
"z2m_base": State.z2m_base,
|
"z2m_base": State.z2m_base,
|
||||||
}
|
}
|
||||||
with open(CONFIG_PATH, "w") as f:
|
# Backup der letzten guten Config anlegen
|
||||||
|
if os.path.exists(CONFIG_PATH):
|
||||||
|
try:
|
||||||
|
os.replace(CONFIG_PATH, CONFIG_PATH + ".bak")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# Atomarer Write: erst .tmp schreiben, dann umbenennen
|
||||||
|
tmp = CONFIG_PATH + ".tmp"
|
||||||
|
with open(tmp, "w") as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
|
os.replace(tmp, CONFIG_PATH)
|
||||||
|
|
||||||
# ── Aggregation ───────────────────────────────────────────────
|
# ── Aggregation ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user