Skip to content

Instantly share code, notes, and snippets.

@stypr
Created February 22, 2025 22:59
Show Gist options
  • Save stypr/b6fe79891f82e3b656c00bb7fa1df446 to your computer and use it in GitHub Desktop.
Save stypr/b6fe79891f82e3b656c00bb7fa1df446 to your computer and use it in GitHub Desktop.
Cloudflare 4.0.0 Automatic DDNS renewal
#!/usr/bin/python3 -u
# make sure to set crontab
# 0 0 * * * python3 /root/cf.py
import sys
import logging
from collections import Counter
import requests
import cloudflare
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
HEADERS = {"User-Agent": "curl/7.88.1"}
def get_ip_address(timeout=3):
"""
Retrieve the public IP address by querying multiple external services.
Returns the IP address that appears most frequently among the sources.
Raises a RuntimeError if no IP address can be obtained.
"""
url_list = [
"https://ifconfig.me/ip",
"https://ipinfo.io/ip",
"https://ipecho.net/plain",
"https://icanhazip.com/",
"https://ident.me/",
"https://checkip.amazonaws.com/",
"http://whatismyip.akamai.com/",
"https://myexternalip.com/raw"
]
ips = []
for url in url_list:
try:
response = requests.get(url, headers=HEADERS, timeout=timeout)
response.raise_for_status()
ip = response.text.strip()
ips.append(ip)
logging.info("Fetched IP from %s: %s", url, ip)
except requests.RequestException as e:
logging.warning("Error fetching IP from %s: %s", url, e)
if not ips:
raise RuntimeError("Failed to obtain IP address from all sources")
ip_counter = Counter(ips)
most_common_ip, _ = ip_counter.most_common(1)[0]
return most_common_ip
def set_ip_address(api_token, zone_name, target_domain_name, current_ip):
"""
Update the DNS record for the target domain using the Cloudflare API.
Parameters:
api_token (str): Cloudflare API token.
zone_name (str): The DNS zone name (e.g., 'stypr.com').
target_domain_name (str): The full domain name to update.
current_ip (str): The IP address to set.
Returns:
tuple: (status, message) where status 0 indicates success and -1 indicates an error.
"""
cf = cloudflare.Cloudflare(api_token=api_token)
# Find the target zone id
target_zone_id = None
for zone in cf.zones.list():
if zone.name == zone_name:
target_zone_id = zone.id
break
if not target_zone_id:
return (-1, f"Zone {zone_name} not found")
# Find the DNS record for the target domain
dns_records = cf.dns.records.list(zone_id=target_zone_id)
dns_records_found = None
for record in dns_records:
if record.name == target_domain_name:
dns_records_found = record
break
if not dns_records_found:
return(-1, f"DNS record for {target_domain_name} not found")
if dns_records_found.content == current_ip:
return(0, "IP not changed")
cf.dns.records.update(
zone_id=target_zone_id,
dns_record_id=dns_records_found.id,
type=dns_records_found.type,
name=dns_records_found.name,
content=current_ip
)
return (0, f"IP changed to {current_ip}")
def main(api_token, zone_name, target_domain):
"""
Runs main
"""
try:
current_ip = get_ip_address()
logging.info("Current IP: %s", current_ip)
except Exception as e:
logging.error("Failed to retrieve IP address: %s", e)
return 1
status, message = set_ip_address(
api_token,
zone_name,
target_domain,
current_ip
)
logging.info("DNS Update Result: %s - %s", status, message)
sys.exit(status)
if __name__ == "__main__":
# API Token can be retrieved from dash.cloudflare.com.
# Edit:DNS should be enough.
API_TOKEN = "___"
ZONE_NAME = "example.com"
TARGET_DOMAIN = "sub.example.com"
# Run Main
sys.exit(main(API_TOKEN, ZONE_NAME, TARGET_DOMAIN))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment