"""Jadwal distribusi harian: jalankan distribute.py pada jam DISTRIBUTE_HOUR. Menunggu hingga batas `DISTRIBUTE_HOUR` berikutnya (waktu lokal via `TZ`) lalu memanggil `distribute.main()`. Loop tak pernah keluar — service dijaga hidup oleh restart_policy swarm. Gagal distribusi dicatat lalu lanjut ke hari berikutnya (⊥ membunuh loop). Tanpa `VEX_BP_PRIVATE_KEY` run nyata raise RuntimeError yang jelas — ditangkap, dicetak, dicoba lagi besok. Berbeda dengan scan: ⊥ ada "distribusi-awal" saat start (SCAN_RUN_ON_START) — membayar di container start berisiko memakai snapshot basi; distribusi hanya berjalan pada jam terjadwal. Scheduler helper (`next_boundary`, `_wait_db`) sengaja diduplikasi dari `scan_loop.py` (bukan di-import) karena kedua image Docker terpisah: `Dockerfile.dist` ⊥ membawa `scan_loop.py`/`get_voters.py`. """ import os import time from datetime import datetime, timedelta import db import distribute def next_boundary(now, hours): """datetime berikutnya pada jam-jam `hours` yang lebih besar dari `now`. Sama dengan `scan_loop.next_boundary` (mirror agar image dist mandiri). """ for h in hours: cand = now.replace(hour=h, minute=0, second=0, microsecond=0) if cand > now: return cand # semua jam hari ini sudah lewat → besok jam pertama nxt = now + timedelta(days=1) return nxt.replace(hour=hours[0], minute=0, second=0, microsecond=0) def _wait_db(timeout=120): """Tunggu basis data siap (mariadb baru boot) sebelum loop.""" start = time.monotonic() while time.monotonic() - start < timeout: try: db.queryone('SELECT 1') return except Exception: time.sleep(3) raise RuntimeError('basis data tak siap setelah menunggu') def _run_distribute(label): print(f'[{label}] memulai distribusi...', flush=True) try: distribute.main() except Exception as exc: print(f'[Error] Distribusi gagal: {exc}', flush=True) def main(): hour = int(os.getenv('DISTRIBUTE_HOUR', '10')) if not 0 <= hour <= 23: raise ValueError(f'DISTRIBUTE_HOUR tak valid: {hour!r}') print(f'Jadwal distribusi: jam {hour} (waktu lokal)') _wait_db() while True: now = datetime.now() target = next_boundary(now, [hour]) wait = (target - now).total_seconds() print(f'{now.isoformat(timespec="seconds")} tidur {wait / 3600:.2f} jam ' f'sampai {target.isoformat(timespec="seconds")}', flush=True) time.sleep(max(wait, 0)) _run_distribute('distribusi terjadwal') if __name__ == '__main__': main()