Add watchdog for missed launchd runs, fix backup path bug + retention
Watchdog (scripts/utils/watchdog.py, com.lotto.watchdog.plist, daily 21:30) checks whether the latest draw or the trained models are older than the launchd schedule allows and sends a Telegram alert - this would have caught the Aug 15 update run that silently never fired (the Mac was presumably asleep; launchd does not retry missed StartCalendarInterval jobs), instead of that only surfacing on the next manual check. Also fixes a real path bug in LottoAPIUpdater.create_backup(): since self.data_file already points into data/, joining "data","backups" wrote backups to data/data/backups/ instead of data/backups/ - 73 backups (6.3MB) had been piling up there undetected for months, with data/data/ only ever gitignored rather than the root cause fixed. Merges the misplaced backups into the correct location and adds retention (keep last 30) so backups no longer grow unbounded; applied the same retention to Eurojackpot's updater in a matching commit there, which didn't have the path bug but had the same unbounded growth (85 backups, 2.4MB). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
427e704f3c
commit
612334dc1a
8 files changed
+178
-9899
No files matched your search
@@ -91,7 +91,7 @@ class LottoAPIUpdater:
|
||||
return False
|
||||
|
||||
def create_backup(self) -> bool:
|
||||
"""Erstellt Backup."""
|
||||
"""Erstellt Backup und behält nur die letzten KEEP_BACKUPS Stück."""
|
||||
if not os.path.exists(self.data_file):
|
||||
return True
|
||||
|
||||
@@ -99,7 +99,10 @@ class LottoAPIUpdater:
|
||||
print("\n💾 Erstelle Backup...")
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_dir = os.path.join(os.path.dirname(self.data_file), "data", "backups")
|
||||
# self.data_file liegt bereits unter .../data/ - vorher wurde hier
|
||||
# zusätzlich "data" angehängt, was seit Monaten ungenutzt nach
|
||||
# .../data/data/backups/ statt .../data/backups/ geschrieben hat.
|
||||
backup_dir = os.path.join(os.path.dirname(self.data_file), "backups")
|
||||
os.makedirs(backup_dir, exist_ok=True)
|
||||
|
||||
filename = os.path.basename(self.data_file)
|
||||
@@ -108,12 +111,28 @@ class LottoAPIUpdater:
|
||||
shutil.copy2(self.data_file, self.backup_file)
|
||||
print(f" ✅ Backup: {os.path.basename(self.backup_file)}")
|
||||
|
||||
self._prune_old_backups(backup_dir, filename)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Backup-Fehler: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _prune_old_backups(backup_dir: str, filename: str, keep: int = 30) -> None:
|
||||
"""Behält nur die KEEP neuesten Backups, löscht den Rest."""
|
||||
pattern = f"{filename}.backup_"
|
||||
backups = sorted(
|
||||
(f for f in os.listdir(backup_dir) if f.startswith(pattern)),
|
||||
reverse=True,
|
||||
)
|
||||
for old in backups[keep:]:
|
||||
try:
|
||||
os.remove(os.path.join(backup_dir, old))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def fetch_from_api(self, api_name: str) -> List[Dict]:
|
||||
"""
|
||||
Holt Daten von spezifischer API.
|
||||
|
||||
Reference in new issue
Block a user