scheduler in python

This commit is contained in:
2026-08-04 22:54:10 +02:00
parent 03dbbfe20d
commit e5b9fb8a67
6 changed files with 120 additions and 54 deletions
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Einfacher Cron-Scheduler fuer Sync und Mailer.
Ersetzt supercronic komplett — keine externe Abhaengigkeit noetig.
Laeuft als PID 1 im Docker-Container und plant:
- Sync: alle 15 Minuten (Delta-Sync)
- Mailer: taeglich um MAIL_SEND_HOUR Uhr (falls MAILER_ENABLED=true)
Der Scheduler faengt SIGTERM/SIGINT ab und beendet sich sauber,
damit Docker den Container korrekt stoppen kann."""
import logging
import os
import signal
import subprocess
import sys
import time
from datetime import datetime, timedelta
logging.basicConfig(
level=os.environ.get("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("scheduler")
SYNC_INTERVAL_MINUTES = 15
MAIL_SEND_HOUR = int(os.environ.get("MAIL_SEND_HOUR", "7"))
MAILER_ENABLED = os.environ.get("MAILER_ENABLED", "false").lower() == "true"
_shutdown = False
def _handle_signal(signum, _frame):
global _shutdown
signame = signal.Signals(signum).name
logger.info("Signal %s empfangen, Scheduler faehrt herunter...", signame)
_shutdown = True
def run_script(name: str, script_path: str):
logger.info("--- %s-Lauf gestartet ---", name)
try:
result = subprocess.run(
[sys.executable, script_path],
cwd="/app",
timeout=300,
)
if result.returncode == 0:
logger.info("%s-Lauf erfolgreich beendet", name)
else:
logger.error("%s-Lauf fehlgeschlagen (exit code %d)", name, result.returncode)
except subprocess.TimeoutExpired:
logger.error("%s-Lauf hat 5 Minuten ueberschritten, abgebrochen", name)
except Exception:
logger.exception("Unerwarteter Fehler waehrend %s-Lauf", name)
def run_sync():
run_script("Sync", "/app/sync.py")
try:
with open("/tmp/last_sync_ok", "w") as f:
f.write(datetime.now().isoformat())
except OSError:
pass
def run_mailer():
if not MAILER_ENABLED:
return
run_script("Mailer", "/app/mailer.py")
def next_run_time(hour: int) -> datetime:
now = datetime.now()
target = now.replace(hour=hour, minute=0, second=0, microsecond=0)
if target <= now:
target += timedelta(days=1)
return target
def main():
global _shutdown
signal.signal(signal.SIGTERM, _handle_signal)
signal.signal(signal.SIGINT, _handle_signal)
logger.info("Scheduler gestartet")
logger.info("Sync: alle %d Minuten", SYNC_INTERVAL_MINUTES)
if MAILER_ENABLED:
logger.info("Mailer: taeglich um %d:00 Uhr", MAIL_SEND_HOUR)
else:
logger.info("Mailer: deaktiviert (MAILER_ENABLED=false)")
logger.info("Fuehre initialen Sync aus...")
run_sync()
last_sync = datetime.now()
next_mailer = next_run_time(MAIL_SEND_HOUR)
while not _shutdown:
now = datetime.now()
if now >= last_sync + timedelta(minutes=SYNC_INTERVAL_MINUTES):
run_sync()
last_sync = now
if MAILER_ENABLED and now >= next_mailer:
run_mailer()
next_mailer = next_run_time(MAIL_SEND_HOUR)
time.sleep(10)
logger.info("Scheduler beendet")
if __name__ == "__main__":
main()