fix(sync): Guard gegen Datenverlust — reset --hard nur bei sauberem Baum

sync_state.py bricht den git-Sync ab + alarmiert per Telegram, wenn der
Live-Arbeitsbaum uncommittete Aenderungen oder ungepushte Commits hat,
statt sie per reset --hard FETCH_HEAD zu vernichten. Watchdog laeuft
unabhaengig weiter. Behebt die Ursache von Issue #123.
This commit is contained in:
Homelab Cursor 2026-06-22 20:38:35 +02:00
parent 38b91136c2
commit 4f61172468

View file

@ -387,12 +387,45 @@ def git_sync(cfg: config.HomelabConfig):
["git", "-C", str(REPO), "fetch", forgejo_url, "main", "--quiet"],
capture_output=True, timeout=30,
)
# -- Schutz gegen Datenverlust (Ref Issue #123) --------------------
# Kein blindes 'reset --hard', wenn der Live-Arbeitsbaum nicht-committete
# Aenderungen oder lokale, noch nicht gepushte Commits hat -- sonst wuerde
# echte Arbeit vernichtet (das Beinahe-Desaster vom Juni 2026).
# Stattdessen: git-Sync ueberspringen + Telegram-Alarm; Mensch loest auf.
dirty = subprocess.run(
["git", "-C", str(REPO), "status", "--porcelain", "--untracked-files=no"],
capture_output=True, text=True, timeout=15,
).stdout.strip()
ahead = subprocess.run(
["git", "-C", str(REPO), "rev-list", "--count", "FETCH_HEAD..HEAD"],
capture_output=True, text=True, timeout=15,
).stdout.strip()
if dirty or (ahead.isdigit() and int(ahead) > 0):
n_dirty = len(dirty.splitlines()) if dirty else 0
n_ahead = ahead if ahead.isdigit() else "?"
log("GUARD: git-Sync abgebrochen -- Arbeitsbaum nicht sauber "
"(%s geaenderte Datei(en), %s ungepushte Commit(s)). "
"reset --hard uebersprungen (Schutz vor Datenverlust, Ref #123)."
% (n_dirty, n_ahead))
tg_alert(
cfg, "sync_guard",
"*Homelab git-Sync ABGEBROCHEN*%0A%0A"
"Live-Repo (CT116) ist nicht sauber:%0A"
"- " + str(n_dirty) + " uncommittete Datei(en)%0A"
"- " + str(n_ahead) + " ungepushte Commit(s)%0A%0A"
"reset --hard FETCH_HEAD wurde UEBERSPRUNGEN, um Arbeit nicht zu "
"vernichten (Ref Issue #123).%0ABitte manuell aufloesen: committen + "
"pushen oder gezielt verwerfen. Watchdog laeuft normal weiter."
)
return forgejo_url, False
subprocess.run(
["git", "-C", str(REPO), "reset", "--hard", "FETCH_HEAD"],
capture_output=True, timeout=15,
)
return forgejo_url
return forgejo_url, True
def git_commit_and_push(cfg: config.HomelabConfig, forgejo_url: str):
@ -436,33 +469,35 @@ def main():
log("Sync startet...")
cfg = config.parse_config(REPO / "homelab.conf")
forgejo_url = git_sync(cfg)
forgejo_url, repo_clean = git_sync(cfg)
cfg = config.parse_config(REPO / "homelab.conf")
# Watchdog
log("Watchdog läuft...")
# Watchdog (laeuft immer, unabhaengig vom git-Zustand)
log("Watchdog laeuft...")
check_service(cfg, 109, "rss-manager", "RSS Manager")
check_docker(cfg, 101, "wordpress-app", "WordPress Docker")
# STATE.md Dateien generieren
states = {
"arakava-news/STATE.md": generate_arakava_state(cfg),
"infrastructure/STATE.md": generate_infra_state(cfg),
"smart-home/STATE.md": generate_smarthome_state(cfg),
}
if repo_clean:
states = {
"arakava-news/STATE.md": generate_arakava_state(cfg),
"infrastructure/STATE.md": generate_infra_state(cfg),
"smart-home/STATE.md": generate_smarthome_state(cfg),
}
for path, content in states.items():
full_path = REPO / path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
CHANGED = True
log(f"{path} aktualisiert")
for path, content in states.items():
full_path = REPO / path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
CHANGED = True
log("%s aktualisiert" % path)
if CHANGED:
git_commit_and_push(cfg, forgejo_url)
if CHANGED:
git_commit_and_push(cfg, forgejo_url)
else:
log("Keine Aenderungen")
else:
log("Keine Änderungen")
log("git-Sync uebersprungen (Guard aktiv) -- STATE-Regen + Push ausgelassen")
log("Sync abgeschlossen")