Custom Cloudflare DNS Update
- Workflow runs every 30 min via self-hosted github runner, octorunner
- GitHub secrets passed through the workflow to the script via environment vars
- environment vars configured in github
- secrets held in 1Password and used in GitHub Actions
- API Token NOT API Key is used to initialize the client in the script
- Token permissions:
All zones - DNS:Read, DNS:Edit
Workflow
name: Monitor IP and Update Cloudflare
on:
schedule:
# Run the workflow every 30 minutes
- cron: "*/30 * * * *"
workflow_dispatch:
jobs:
monitor-ip:
runs-on: self-hosted
steps:
# Checkout the repository
- name: Checkout repository
uses: actions/checkout@v4
# Set up Python
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
# Install dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install python-dotenv requests cloudflare
# Run the script
- name: Run monitor script
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_API_EMAIL: ${{ secrets.CLOUDFLARE_API_EMAIL }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
CLOUDFLARE_DNS_RECORD_ID: ${{ secrets.CLOUDFLARE_DNS_RECORD_ID }}
run: |
python automation/ddns/cloudflare-update.py
Script
import sys
import requests
from cloudflare import Cloudflare
import os
from dotenv import load_dotenv
def get_public_ip():
"""Fetch the public IP address."""
try:
response = requests.get("https://api.ipify.org", timeout=10)
response.raise_for_status()
return response.text
except requests.RequestException as e:
print(f"Error fetching public IP: {e}")
sys.exit(1)
def update_cloudflare_record(ip, api_token, zone_id, dns_record_id):
"""Monitor IP changes and edit Cloudflare."""
client = Cloudflare(
api_token=api_token
)
"""Edit the Cloudflare DNS record."""
try:
# Edit the DNS record
response = client.dns.records.edit(
dns_record_id=dns_record_id,
zone_id=zone_id,
content=ip
)
print(f"Successfully updated DNS record to IP")
except Exception as e:
print(f"Error editing Cloudflare DNS record: {e}")
sys.exit(1)
def main():
# Check for a flag to determine how to load environment variables
if len(sys.argv) > 1 and sys.argv[1] == "--use-env-file":
print("Loading environment variables from .env file...")
load_dotenv()
# Configuration from environment variables
CLOUDFLARE_API_TOKEN = os.getenv("CLOUDFLARE_API_TOKEN")
CLOUDFLARE_API_EMAIL = os.getenv("CLOUDFLARE_API_EMAIL")
CLOUDFLARE_ZONE_ID = os.getenv("CLOUDFLARE_ZONE_ID")
CLOUDFLARE_DNS_RECORD_ID = os.getenv("CLOUDFLARE_DNS_RECORD_ID")
if not CLOUDFLARE_API_TOKEN or not CLOUDFLARE_ZONE_ID or not CLOUDFLARE_DNS_RECORD_ID:
print("Missing required environment variables.")
sys.exit(1)
# Fetch the current public IP address
current_ip = get_public_ip()
if current_ip:
print(f"Detected IP")
# Update the Cloudflare DNS record
update_cloudflare_record(
current_ip,
CLOUDFLARE_API_TOKEN,
CLOUDFLARE_ZONE_ID,
CLOUDFLARE_DNS_RECORD_ID
)
if __name__ == "__main__":
main()