Skip to content

Instantly share code, notes, and snippets.

@cjuroz
Last active November 27, 2025 12:15
Show Gist options
  • Select an option

  • Save cjuroz/bc55b1673f9063c6b22eceb7edcf6750 to your computer and use it in GitHub Desktop.

Select an option

Save cjuroz/bc55b1673f9063c6b22eceb7edcf6750 to your computer and use it in GitHub Desktop.
DDNS service running on AWS (r53, lambda, api gateway)
import requests
import sys
from pathlib import Path
# --- Configuration ---
# The API Gateway URL you copied from Step 3
API_GATEWAY_URL = "AWS_API_GATEWAY_URL"
# The API Key you created and copied in Step 3
API_KEY = "AWS_API_GATEWAY_API_KEY"
# --- End of Configuration ---
def get_public_ip():
"""Fetches the current public IP from an external service."""
try:
response = requests.get("https://checkip.amazonaws.com", timeout=10)
response.raise_for_status()
return response.text.strip()
except requests.RequestException as e:
print(f"Error fetching public IP: {e}")
return None
def get_last_ip(domain_name):
"""Reads the last known IP for a specific domain from its local file."""
ip_file = Path.home() / f".ddns_current_ip_{domain_name}"
try:
if ip_file.exists():
return ip_file.read_text().strip()
except IOError as e:
print(f"Error reading IP file: {e}")
return None
def update_ddns(ip, domain_name):
"""Calls the API Gateway to update the DNS record for a specific domain."""
headers = {
"x-api-key": API_KEY,
"Content-Type": "application/json"
}
payload = {
"domain_name": domain_name,
"ip_address": ip # Sending the IP address in the payload
}
try:
print(f"Attempting to update DDNS for {domain_name} with new IP: {ip}")
response = requests.post(API_GATEWAY_URL, headers=headers, json=payload)
if response.status_code == 200:
print("Successfully updated IP via API.")
# If the update was successful, save the new IP
save_last_ip(ip, domain_name)
else:
print(f"Error updating IP. Status: {response.status_code}, Response: {response.text}")
except requests.RequestException as e:
print(f"Error calling API Gateway: {e}")
def save_last_ip(ip, domain_name):
"""Saves the given IP to the local file for a specific domain."""
ip_file = Path.home() / f".ddns_current_ip_{domain_name}"
try:
ip_file.write_text(ip)
print(f"Saved new IP {ip} to {ip_file}")
except IOError as e:
print(f"Error writing to IP file: {e}")
if __name__ == "__main__":
# --- Argument Check ---
if len(sys.argv) != 2:
print("Usage: python ddns_client.py <your.domain.com>")
exit(1)
domain_name = sys.argv[1]
# --- End of Argument Check ---
print(f"--- Starting DDNS Check for {domain_name} ---")
current_ip = get_public_ip()
if not current_ip:
exit(1)
last_ip = get_last_ip(domain_name)
print(f"Current public IP: {current_ip}")
print(f"Last known IP for {domain_name}: {last_ip}")
if current_ip != last_ip:
print("IP address has changed. Triggering DDNS update...")
update_ddns(current_ip, domain_name)
else:
print("IP address has not changed. No update needed.")
print("--- DDNS Check Finished ---")
import os
import boto3
import json
# Initialize the Route 53 client
route53 = boto3.client('route53')
# Fetch the Hosted Zone ID from environment variables
# We still need this, as all domains are in the same zone.
HOSTED_ZONE_ID = os.environ['HOSTED_ZONE_ID']
def lambda_handler(event, context):
try:
# The client now sends data in the request body. We need to parse it.
body = json.loads(event['body'])
# Extract the domain name and IP address from the parsed body
domain_name = body['domain_name']
ip_address = body['ip_address']
print(f"Received request to update {domain_name} to {ip_address}")
# Prepare the record set change using the provided data
change_batch = {
'Comment': 'Automated DDNS update',
'Changes': [
{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': domain_name,
'Type': 'A',
'TTL': 60, # A short TTL is good for DDNS
'ResourceRecords': [{'Value': ip_address}]
}
}
]
}
# Execute the change in Route 53
response = route53.change_resource_record_sets(
HostedZoneId=HOSTED_ZONE_ID,
ChangeBatch=change_batch
)
print(f"Successfully updated DNS record. Change ID: {response['ChangeInfo']['Id']}")
return {
'statusCode': 200,
'body': json.dumps(f'Successfully updated {domain_name} to {ip_address}')
}
except KeyError as e:
# This error occurs if 'domain_name' or 'ip_address' is missing from the request
error_message = f"Missing required key in request body: {e}"
print(error_message)
return {
'statusCode': 400, # Bad Request
'body': json.dumps(error_message)
}
except Exception as e:
print(f"Error updating DNS record: {e}")
return {
'statusCode': 500,
'body': json.dumps(f'Error updating DNS record: {str(e)}')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment