#!/usr/bin/env python3 import requests import json import logging import sys import os import time # --- KONFIGURACJA --- BASE_DIR = os.path.dirname(os.path.abspath(__file__)) CONFIG_FILE = os.path.join(BASE_DIR, "config.json") DOMAINS_FILE = os.path.join(BASE_DIR, "domains.txt") LOG_FILE = "/var/log/ionos-ddns.log" INTERVAL = 600 # Czas w sekundach (10 minut) # --- LOGOWANIE --- logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S', handlers=[ logging.FileHandler(LOG_FILE), logging.StreamHandler(sys.stdout) ] ) class IonosClient: def __init__(self, prefix, secret): self.base_url = "https://api.hosting.ionos.com/dns/v1" self.headers = { "X-API-Key": f"{prefix}.{secret}", "Content-Type": "application/json", "Accept": "application/json" } def get_zones(self): try: resp = requests.get(f"{self.base_url}/zones", headers=self.headers) if resp.status_code == 200: return resp.json() logging.error(f"API Error (get_zones): {resp.status_code} {resp.text}") except Exception as e: logging.error(f"Connection Error (get_zones): {e}") return [] def get_zone_records(self, zone_id): try: resp = requests.get(f"{self.base_url}/zones/{zone_id}", headers=self.headers) if resp.status_code == 200: return resp.json().get('records', []) except Exception as e: logging.error(f"Connection Error (get_zone_records): {e}") return [] def create_record(self, zone_id, name, ip): data = [{"name": name, "type": "A", "content": ip, "ttl": 3600}] logging.info(f" [ACTION] CREATING: {name} -> {ip}") try: resp = requests.post(f"{self.base_url}/zones/{zone_id}/records", headers=self.headers, json=data) if resp.status_code == 201: logging.info(" [SUCCESS] Record created.") else: logging.error(f" [ERROR] API: {resp.text}") except Exception as e: logging.error(f" [ERROR] Connection: {e}") def update_record(self, zone_id, record_id, name, ip): data = {"name": name, "type": "A", "content": ip, "ttl": 3600} logging.info(f" [ACTION] UPDATING: {name} -> {ip}") try: resp = requests.put(f"{self.base_url}/zones/{zone_id}/records/{record_id}", headers=self.headers, json=data) if resp.status_code == 200: logging.info(" [SUCCESS] Updated.") else: logging.error(f" [ERROR] API: {resp.text}") except Exception as e: logging.error(f" [ERROR] Connection: {e}") def load_config(): if not os.path.exists(CONFIG_FILE): logging.error(f"CRITICAL: Config file missing: {CONFIG_FILE}") return None try: with open(CONFIG_FILE, 'r') as f: return json.load(f) except Exception as e: logging.error(f"CRITICAL: Config read error: {e}") return None def get_public_ip(url): try: return requests.get(url, timeout=10).text.strip() except Exception as e: logging.error(f"Public IP fetch error: {e}") return None def find_zone_for_domain(full_domain, zones): best_match = None best_len = 0 for zone in zones: zone_name = zone['name'] if full_domain == zone_name or full_domain.endswith("." + zone_name): if len(zone_name) > best_len: best_len = len(zone_name) best_match = zone return best_match def main(): logging.info("=== STARTING IONOS DDNS SERVICE ===") config = load_config() if not config: sys.exit(1) ionos = IonosClient(config['api_prefix'], config['api_secret']) while True: try: if not os.path.exists(DOMAINS_FILE): logging.error(f"Domains file missing: {DOMAINS_FILE}") else: with open(DOMAINS_FILE, 'r') as f: target_domains = [line.strip() for line in f if line.strip()] public_ip = get_public_ip(config['check_ip_url']) if public_ip and target_domains: zones = ionos.get_zones() if zones: for full_domain in target_domains: zone = find_zone_for_domain(full_domain, zones) if not zone: logging.warning(f" No zone found for: {full_domain}") continue records = ionos.get_zone_records(zone['id']) target_record = next((r for r in records if r['name'] == full_domain and r['type'] == 'A'), None) if target_record: if target_record['content'] != public_ip: ionos.update_record(zone['id'], target_record['id'], full_domain, public_ip) else: # Opcjonalnie można zakomentować, żeby nie spamowało logów co 10 min logging.info(f" [OK] {full_domain} is up to date.") else: ionos.create_record(zone['id'], full_domain, public_ip) except Exception as e: logging.critical(f"UNEXPECTED LOOP ERROR: {e}") time.sleep(INTERVAL) if __name__ == "__main__": main()