#!/usr/bin/env python3 import fcntl import os import shutil import subprocess import time from datetime import datetime from pathlib import Path PYTHON = '/opt/intranet-venv/bin/python' ROOT = Path('/opt/intranet/worker') STATE_DIR = Path('/data/state') PDF_OUTPUT = Path(os.getenv('PDF_OUTPUT', '/data/pdf/terminuebersicht.pdf')) INTERVAL = int(os.getenv('CALENDAR_INTERVAL_SECONDS', '60')) def required(name: str) -> str: value = os.getenv(name, '').strip() if not value: raise RuntimeError(f'Missing environment variable: {name}') return value def run(cmd, cwd=None): print('+', ' '.join(map(str, cmd)), flush=True) subprocess.run(cmd, cwd=cwd, check=True, env=os.environ.copy()) def prepare_ssh_key() -> Path: source = Path(os.getenv('STRATO_SSH_KEY_FILE', '/run/secrets/strato_ed25519')) if not source.is_file(): raise RuntimeError(f'SSH key file missing: {source}') target = Path('/tmp/strato_ed25519') shutil.copyfile(source, target) target.chmod(0o600) return target def calendar_sync(): # CKalender-XLSX verarbeiten run( [PYTHON, str(ROOT / 'ckal_upload_client/client.py')], cwd=ROOT / 'ckal_upload_client' ) # PDF erzeugen run( [PYTHON, str(ROOT / 'pdf_python/create_pdf.py')], cwd=ROOT / 'pdf_python' ) # PDF in Dashboard-PNGs umwandeln run( ['/bin/bash', str(ROOT / 'pdf_python/pdf_to_png.sh')], cwd=ROOT / 'pdf_python' ) # STRATO-Synchronisation kann z. B. im DEV deaktiviert werden strato_sync_enabled = os.getenv( 'STRATO_SYNC_ENABLED', 'true' ).strip().lower() in ('1', 'true', 'yes', 'on') if not strato_sync_enabled: print('STRATO Sync deaktiviert', flush=True) print('Calendar Sync erfolgreich (ohne STRATO)', flush=True) return required('STRATO_SSH_USER') required('STRATO_REMOTE_PATH') host = os.getenv('STRATO_SSH_HOST', 'ssh.strato.de') port = os.getenv('STRATO_SSH_PORT', '22') known_hosts = Path( os.getenv( 'STRATO_KNOWN_HOSTS_FILE', '/run/secrets/known_hosts' ) ) if not known_hosts.is_file(): raise RuntimeError( f'known_hosts missing: {known_hosts}' ) key = prepare_ssh_key() if not PDF_OUTPUT.is_file() or PDF_OUTPUT.stat().st_size == 0: raise RuntimeError( f'PDF missing/empty: {PDF_OUTPUT}' ) run([ '/usr/bin/scp', '-O', '-q', '-P', port, '-i', str(key), '-o', 'IdentitiesOnly=yes', '-o', 'BatchMode=yes', '-o', 'StrictHostKeyChecking=yes', '-o', f'UserKnownHostsFile={known_hosts}', str(PDF_OUTPUT), f"{required('STRATO_SSH_USER')}@{host}:{required('STRATO_REMOTE_PATH')}", ]) print('Calendar Sync erfolgreich', flush=True) def brandschutz_once_per_month(): now = datetime.now() if now.day != 1 or now.hour < 8: return brandschutz_enabled = os.getenv( 'BRANDSCHUTZ_MAIL_ENABLED', 'true' ).strip().lower() in ('1', 'true', 'yes', 'on') if not brandschutz_enabled: print('Brandschutz-Mail deaktiviert', flush=True) return marker = STATE_DIR / 'brandschutz-last-attempt' month = now.strftime('%Y-%m') if marker.exists() and marker.read_text().strip() == month: return # At-most-once: erst markieren, dann ausführen marker.write_text(month + '\n') print(f'Brandschutz-Lauf {month}', flush=True) run([ '/usr/local/bin/php', '/var/www/html/intranet_repo/projects/brandschutz/brandschutz_cron.php' ]) def main(): STATE_DIR.mkdir(parents=True, exist_ok=True) lock_handle = (STATE_DIR / 'scheduler.lock').open('w') try: fcntl.flock(lock_handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError as exc: raise RuntimeError('Ein anderer Scheduler läuft bereits.') from exc if INTERVAL < 30: raise RuntimeError('CALENDAR_INTERVAL_SECONDS muss >= 30 sein.') for name in ('CKAL_USERNAME', 'CKAL_PASSWORD', 'CKAL_CALENDAR_URL'): required(name) PDF_OUTPUT.parent.mkdir(parents=True, exist_ok=True) next_run = 0.0 while True: try: brandschutz_once_per_month() except Exception as exc: print(f'FEHLER Brandschutz: {exc}', flush=True) now_mono = time.monotonic() if now_mono >= next_run: try: calendar_sync() except Exception as exc: print(f'FEHLER Calendar Sync: {exc}', flush=True) next_run = time.monotonic() + INTERVAL (STATE_DIR / 'heartbeat').write_text(str(int(time.time())) + '\n') time.sleep(1) if __name__ == '__main__': main()