ShineBridge v1.11.0 — Feature: automatisches tägliches Config-Backup nach /share
Neuer Daemon-Thread (gleiches Muster wie der History-Cleanup-Thread) schreibt täglich einen Config-Export nach /share/shinebridge-backups/ — bewusst nicht nach /data, da genau dieser Ordner bei Add-on-Uninstall+Reinstall gelöscht wird (siehe Vorfall 2026-08-25). Export sofort beim Start plus alle 24h, 14 Tage Aufbewahrung mit automatischem Cleanup alter Dateien. Export-Datenaufbau aus /api/export-config in _build_export_data() extrahiert, von Endpoint und Scheduler gemeinsam genutzt. Neue Berechtigung map: share:rw in config.yaml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+43
-3
@@ -808,10 +808,9 @@ def api_new_id():
|
||||
return jsonify({"id": uuid.uuid4().hex[:8]})
|
||||
|
||||
|
||||
@app.get("/api/export-config")
|
||||
def api_export_config():
|
||||
def _build_export_data() -> Dict[str, Any]:
|
||||
with State.lock:
|
||||
data = {
|
||||
return {
|
||||
"shinebridge_export": True,
|
||||
"version": 1,
|
||||
"exported_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
@@ -822,7 +821,47 @@ def api_export_config():
|
||||
},
|
||||
"inverters": State.inverters_cfg,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_BACKUP_DIR = "/share/shinebridge-backups"
|
||||
CONFIG_BACKUP_RETENTION_DAYS = 14
|
||||
|
||||
|
||||
def _export_config_backup():
|
||||
"""Schreibt einen taeglichen Config-Export nach /share — bewusst NICHT nach
|
||||
/data, weil genau das der Ordner ist, der bei Add-on-Uninstall+Reinstall
|
||||
geloescht wird (siehe Vorfall 2026-08-25). /share uebersteht das."""
|
||||
try:
|
||||
os.makedirs(CONFIG_BACKUP_DIR, exist_ok=True)
|
||||
filename = f"config-{time.strftime('%Y-%m-%d')}.json"
|
||||
path = os.path.join(CONFIG_BACKUP_DIR, filename)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(_build_export_data(), f, indent=2, ensure_ascii=False)
|
||||
log.info("Config-Backup geschrieben: %s", path)
|
||||
|
||||
cutoff = time.time() - CONFIG_BACKUP_RETENTION_DAYS * 86400
|
||||
for name in os.listdir(CONFIG_BACKUP_DIR):
|
||||
fpath = os.path.join(CONFIG_BACKUP_DIR, name)
|
||||
if name.startswith("config-") and name.endswith(".json") and os.path.getmtime(fpath) < cutoff:
|
||||
os.remove(fpath)
|
||||
log.info("Altes Config-Backup gelöscht: %s", fpath)
|
||||
except OSError as e:
|
||||
log.error("Config-Backup fehlgeschlagen: %s", e)
|
||||
|
||||
|
||||
def _start_config_backup_scheduler():
|
||||
def _loop():
|
||||
_export_config_backup() # sofort beim Start, nicht erst nach 24h
|
||||
while True:
|
||||
time.sleep(86400)
|
||||
_export_config_backup()
|
||||
threading.Thread(target=_loop, daemon=True, name="config-backup").start()
|
||||
|
||||
|
||||
@app.get("/api/export-config")
|
||||
def api_export_config():
|
||||
from flask import Response
|
||||
data = _build_export_data()
|
||||
filename = f"shinebridge-config-{time.strftime('%Y%m%d-%H%M%S')}.json"
|
||||
return Response(
|
||||
json.dumps(data, indent=2, ensure_ascii=False),
|
||||
@@ -955,6 +994,7 @@ if __name__ == "__main__":
|
||||
save_config()
|
||||
|
||||
_restart_all()
|
||||
_start_config_backup_scheduler()
|
||||
port = int(os.environ.get("INGRESS_PORT", "8099"))
|
||||
log.info("Web UI startet auf Port %d", port)
|
||||
app.run(host="0.0.0.0", port=port, threaded=True)
|
||||
|
||||
Reference in New Issue
Block a user