- Dockerfile (web) + Dockerfile.scan (scan; sertakan tzdata) + .dockerignore
- scan_loop.py: jadwal skan SCAN_HOURS (0,8,16 lokal via TZ) + skan-awal SCAN_RUN_ON_START; loop tak keluar; tunggu DB siap
- docker-compose.yml → STACK SWARM produksi (mariadb bind mount /data/db/mariadb/databisnisid/data + placement node.hostname == server5.saltis.id; web :5000; scan); image dari registry git.proit.id/proitlab/databisnisid-{web,scan}
- docker-compose.dev.yml: mariadb uji lokal (127.0.0.1:3306) — pengganti docker-compose.yml lama
- SPEC.md §V18/T18; AGENTS.md runbook swarm + dev compose
- .env.example: TZ/SCAN_HOURS/SCAN_RUN_ON_START/MARIADB_*
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
"""Jadwal skan berkala: jalankan get_voters.py pada jam SCAN_HOURS (waktu lokal).
|
|
|
|
Menunggu hingga batas jam berikutnya dalam daftar `SCAN_HOURS` (default 0,8,16)
|
|
lalu memanggil `get_voters.main()`. Loop tak pernah keluar — service dijaga
|
|
hidup oleh restart_policy swarm. Gagal skan dicatat lalu lanjut ke batas
|
|
berikutnya (tidak membunuh loop). `TZ` env menentukan jam lokal via
|
|
`datetime.now()` (libc); image Dockerfile.scan sudah menyertakan tzdata.
|
|
|
|
`SCAN_RUN_ON_START` (default 1): skan sekali saat container start setelah
|
|
basis data siap — penting agar tabel voters langsung ada (dashboard ⊥ 500
|
|
sebelum batas jam pertama).
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
|
|
import db
|
|
import get_voters
|
|
|
|
|
|
def parse_hours(raw):
|
|
"""Daftar jam integer dari string CSV, diurutkan naik."""
|
|
hours = sorted({int(h) for h in raw.split(',') if h.strip()})
|
|
if not hours or not all(0 <= h <= 23 for h in hours):
|
|
raise ValueError(f'SCAN_HOURS tak valid: {raw!r}')
|
|
return hours
|
|
|
|
|
|
def next_boundary(now, hours):
|
|
"""datetime berikutnya pada jam-jam `hours` yang lebih besar dari `now`."""
|
|
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 skan awal."""
|
|
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_scan(label):
|
|
print(f'[{label}] memulai skan...', flush=True)
|
|
try:
|
|
rc = get_voters.main()
|
|
except Exception as exc:
|
|
print(f'[Error] Skan gagal: {exc}', flush=True)
|
|
return
|
|
if rc != 0:
|
|
print(f'[Peringatan] get_voters keluar dengan kode {rc}', flush=True)
|
|
|
|
|
|
def main():
|
|
hours = parse_hours(os.getenv('SCAN_HOURS', '0,8,16'))
|
|
run_on_start = os.getenv('SCAN_RUN_ON_START', '1').lower() not in ('0', 'false', 'no')
|
|
print(f'Jadwal skan: jam {", ".join(str(h) for h in hours)} (waktu lokal); '
|
|
f'skan-awal: {"ya" if run_on_start else "tidak"}')
|
|
if run_on_start:
|
|
_wait_db()
|
|
_run_scan('skan awal')
|
|
while True:
|
|
now = datetime.now()
|
|
target = next_boundary(now, hours)
|
|
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_scan('skan terjadwal')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|