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:
retr0
2026-05-05 13:40:56 +02:00
parent 2456f356b4
commit bbfb11fb9c
2 changed files with 34 additions and 16 deletions
+33 -15
View File
@@ -110,25 +110,34 @@ def _defaults() -> Dict[str, Any]:
"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]:
cfg = _defaults()
# MQTT-Grundeinstellungen aus HAOS-Options (überschreibbar durch config.json)
if os.path.exists(HA_OPTIONS_PATH):
try:
with open(HA_OPTIONS_PATH) as f:
ha = json.load(f)
for k in ("mqtt_broker", "mqtt_port", "mqtt_user", "mqtt_pass"):
if k in ha:
cfg[k] = ha[k]
except Exception as e:
log.warning("HA options Fehler: %s", e)
if os.path.exists(CONFIG_PATH):
try:
with open(CONFIG_PATH) as f:
cfg.update(json.load(f))
except Exception as e:
log.warning("Config-Datei Fehler: %s", e)
ha = _load_json_safe(HA_OPTIONS_PATH) or {}
for k in ("mqtt_broker", "mqtt_port", "mqtt_user", "mqtt_pass"):
if k in ha:
cfg[k] = ha[k]
# Eigene persistente Config — Hauptdatei, dann Backup als Fallback
loaded = _load_json_safe(CONFIG_PATH)
if loaded is None and os.path.exists(CONFIG_PATH + ".bak"):
log.warning("config.json korrupt — lade Backup")
loaded = _load_json_safe(CONFIG_PATH + ".bak")
if loaded:
cfg.update(loaded)
return cfg
def save_config():
data = {
"mqtt_broker": State.mqtt_cfg.get("mqtt_broker", ""),
@@ -150,8 +159,17 @@ def save_config():
"surplus_devices": State.surplus_devices_cfg,
"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)
os.replace(tmp, CONFIG_PATH)
# ── Aggregation ───────────────────────────────────────────────