mirror of
https://github.com/skoelle/dyndns-updater.git
synced 2026-09-17 16:10:25 +00:00
118 lines
3.5 KiB
Python
118 lines
3.5 KiB
Python
import concurrent.futures
|
|
import logging
|
|
import threading
|
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
from flask import Flask
|
|
|
|
from app import cloudflare, freedns, healthcheck, state
|
|
from app.config import config
|
|
from app.fritzbox import FritzBoxError, get_external_ip
|
|
|
|
log = logging.getLogger("main")
|
|
|
|
app = Flask(__name__)
|
|
|
|
_lock = threading.Lock()
|
|
_executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)
|
|
|
|
|
|
def run_cycle(trigger: str = "unknown"):
|
|
with _lock:
|
|
log.info("Starte Update-Zyklus (Trigger=%s)", trigger)
|
|
try:
|
|
current_ip = get_external_ip(
|
|
config.fritzbox_host,
|
|
config.fritzbox_port,
|
|
config.fritzbox_user,
|
|
config.fritzbox_password,
|
|
)
|
|
except FritzBoxError as exc:
|
|
log.error("IP-Ermittlung fehlgeschlagen: %s", exc)
|
|
healthcheck.ping(config.healthcheck_ping_url, status="fail", message=str(exc))
|
|
return
|
|
|
|
last_ip = state.load(config.state_path)
|
|
|
|
if last_ip is None:
|
|
log.info("Erstlauf - IP wird gesetzt: %s", current_ip)
|
|
elif current_ip == last_ip:
|
|
log.info("IP unveraendert (%s) - kein DNS-Update noetig.", current_ip)
|
|
if trigger == "poll":
|
|
healthcheck.ping(
|
|
config.healthcheck_ping_url,
|
|
status="success",
|
|
message=f"alive, ip unchanged ({current_ip})",
|
|
)
|
|
return
|
|
|
|
log.info("IP-Aenderung erkannt: %s -> %s", last_ip, current_ip)
|
|
|
|
cf_results = cloudflare.update_records(
|
|
config.cloudflare_zone_id,
|
|
config.cloudflare_api_token,
|
|
config.cloudflare_records,
|
|
current_ip,
|
|
)
|
|
cf_ok = all(v == "ok" for v in cf_results.values())
|
|
|
|
freedns_ok = freedns.update(config.freedns_update_url)
|
|
|
|
if cf_ok and freedns_ok:
|
|
state.save(config.state_path, current_ip)
|
|
healthcheck.ping(
|
|
config.healthcheck_ping_url,
|
|
status="success",
|
|
message=f"IP updated to {current_ip}",
|
|
)
|
|
else:
|
|
failed = {k: v for k, v in cf_results.items() if v != "ok"}
|
|
log.error(
|
|
"Update unvollstaendig - state wird NICHT gespeichert. "
|
|
"Cloudflare-Fehler: %s, FreeDNS ok=%s",
|
|
failed,
|
|
freedns_ok,
|
|
)
|
|
healthcheck.ping(
|
|
config.healthcheck_ping_url,
|
|
status="partial-fail",
|
|
message=f"partial failure: cf_errors={failed} freedns_ok={freedns_ok}",
|
|
)
|
|
|
|
|
|
@app.route("/webhook/update", methods=["GET"])
|
|
def webhook_update():
|
|
_executor.submit(run_cycle, trigger="webhook")
|
|
return {"status": "triggered"}, 202
|
|
|
|
|
|
@app.route("/healthz", methods=["GET"])
|
|
def healthz():
|
|
return {"status": "ok"}, 200
|
|
|
|
|
|
def main():
|
|
config.validate()
|
|
|
|
log.info("Initial-Update-Zyklus beim Start...")
|
|
run_cycle(trigger="startup")
|
|
|
|
scheduler = BackgroundScheduler()
|
|
scheduler.add_job(
|
|
run_cycle,
|
|
"interval",
|
|
minutes=config.poll_interval_minutes,
|
|
kwargs={"trigger": "poll"},
|
|
id="fallback-poll",
|
|
)
|
|
scheduler.start()
|
|
log.info("Fallback-Polling gestartet (alle %d Minuten).", config.poll_interval_minutes)
|
|
|
|
log.info("Webhook-Server startet auf Port 8090 (GET /webhook/update, kein Auth).")
|
|
logging.getLogger("werkzeug").setLevel(logging.ERROR)
|
|
app.run(host="0.0.0.0", port=8090)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|