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:
retr0
2026-08-26 10:52:52 +02:00
co-authored by Claude Sonnet 5
parent c924955be9
commit 3318d1344d
4 changed files with 48 additions and 6 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ Wechselrichter / Energiezähler
- **Sparklines** — Live-Graphen der letzten 5 Minuten pro Sensor - **Sparklines** — Live-Graphen der letzten 5 Minuten pro Sensor
- **EMS-Controller** — PV-Überschussladen der Kathrein Wallbox, Zwangsladen-Fallback mit konfigurierbarem Max-Ladestrom - **EMS-Controller** — PV-Überschussladen der Kathrein Wallbox, Zwangsladen-Fallback mit konfigurierbarem Max-Ladestrom
- **Überschuss-Geräte** — Zigbee2MQTT-Geräte bei PV-Überschuss automatisch einschalten - **Überschuss-Geräte** — Zigbee2MQTT-Geräte bei PV-Überschuss automatisch einschalten
- **Konfig-Export/Import** — JSON im Einstellungen-Tab - **Konfig-Export/Import** — JSON im Einstellungen-Tab, zusätzlich automatisches tägliches Backup nach `/share/shinebridge-backups/` (übersteht auch Add-on-Uninstall+Reinstall, 14 Tage Aufbewahrung)
- **Port-Sicherheit** — `/api/*` nur über HAOS-Ingress erreichbar, kein Direktzugriff von außen - **Port-Sicherheit** — `/api/*` nur über HAOS-Ingress erreichbar, kein Direktzugriff von außen
- **Kein Cloud-Zwang** — vollständig lokal - **Kein Cloud-Zwang** — vollständig lokal
+1 -1
View File
@@ -14,7 +14,7 @@
Reihenfolge nach Aufwand/Abhängigkeit, nicht nach Wichtigkeit. Reihenfolge nach Aufwand/Abhängigkeit, nicht nach Wichtigkeit.
1. **Automatischer täglicher Config-Export** — nach dem Datenverlust-Vorfall vom 25.08. (Uninstall+Reinstall löschte `/data`) eine zusätzliche Absicherung neben den HAOS-eigenen Backups. Export-Endpunkt (`/api/export-config`) existiert schon, braucht nur einen Cron-Trigger + Ablagepfad. 1. ~~**Automatischer täglicher Config-Export**~~ — erledigt (v1.11.0): täglicher Export nach `/share/shinebridge-backups/`, 14 Tage Aufbewahrung, sofort beim Start plus alle 24h.
2. **Discord-Alerts** — bei Zwangsladen-Start, Fehlerzuständen (unbekannter Ladezustand), besonders günstigen Spot-Preisfenstern. Nutzt den bestehenden Homelab-Discord-Bot. 2. **Discord-Alerts** — bei Zwangsladen-Start, Fehlerzuständen (unbekannter Ladezustand), besonders günstigen Spot-Preisfenstern. Nutzt den bestehenden Homelab-Discord-Bot.
3. **Sessions pro Fahrzeug trennen** — falls mehrere Fahrer/Autos an einer Wallbox laden, aktuell nur eine Gesamt-Statistik. 3. **Sessions pro Fahrzeug trennen** — falls mehrere Fahrer/Autos an einer Wallbox laden, aktuell nur eine Gesamt-Statistik.
4. **Lastspitzenkappung (Peak Shaving)** — Ladestrom drosseln, wenn Netzbezug (mehrere Autos + Haushalt gleichzeitig) einen Grenzwert überschreitet. Eigenes Sicherheits-Design nötig (was passiert bei Messfehler/Ausfall — nie ungebremst weiterladen). 4. **Lastspitzenkappung (Peak Shaving)** — Ladestrom drosseln, wenn Netzbezug (mehrere Autos + Haushalt gleichzeitig) einen Grenzwert überschreitet. Eigenes Sicherheits-Design nötig (was passiert bei Messfehler/Ausfall — nie ungebremst weiterladen).
+3 -1
View File
@@ -1,5 +1,5 @@
name: ShineBridge name: ShineBridge
version: "1.10.3" version: "1.11.0"
slug: shinebridge slug: shinebridge
description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte description: Energiemanagement für Growatt/Goodwe-Wechselrichter — PV-Überschussladen, Wallbox-EMS, Tarifvergleich, Zigbee2MQTT-Überschussgeräte
url: https://gitea.bitfire.work/retr0/shinebridge url: https://gitea.bitfire.work/retr0/shinebridge
@@ -13,6 +13,8 @@ host_network: true
ingress: true ingress: true
ingress_port: 8099 ingress_port: 8099
panel_icon: mdi:solar-power panel_icon: mdi:solar-power
map:
- share:rw
options: options:
mqtt_broker: "core-mosquitto" mqtt_broker: "core-mosquitto"
mqtt_port: 1883 mqtt_port: 1883
+43 -3
View File
@@ -808,10 +808,9 @@ def api_new_id():
return jsonify({"id": uuid.uuid4().hex[:8]}) return jsonify({"id": uuid.uuid4().hex[:8]})
@app.get("/api/export-config") def _build_export_data() -> Dict[str, Any]:
def api_export_config():
with State.lock: with State.lock:
data = { return {
"shinebridge_export": True, "shinebridge_export": True,
"version": 1, "version": 1,
"exported_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "exported_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
@@ -822,7 +821,47 @@ def api_export_config():
}, },
"inverters": State.inverters_cfg, "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 from flask import Response
data = _build_export_data()
filename = f"shinebridge-config-{time.strftime('%Y%m%d-%H%M%S')}.json" filename = f"shinebridge-config-{time.strftime('%Y%m%d-%H%M%S')}.json"
return Response( return Response(
json.dumps(data, indent=2, ensure_ascii=False), json.dumps(data, indent=2, ensure_ascii=False),
@@ -955,6 +994,7 @@ if __name__ == "__main__":
save_config() save_config()
_restart_all() _restart_all()
_start_config_backup_scheduler()
port = int(os.environ.get("INGRESS_PORT", "8099")) port = int(os.environ.get("INGRESS_PORT", "8099"))
log.info("Web UI startet auf Port %d", port) log.info("Web UI startet auf Port %d", port)
app.run(host="0.0.0.0", port=port, threaded=True) app.run(host="0.0.0.0", port=port, threaded=True)