Add DDNS service to the Pi
This commit is contained in:
parent
04c8038733
commit
04671f7d31
10 changed files with 96 additions and 12 deletions
|
|
@ -0,0 +1,48 @@
|
|||
import requests
|
||||
import logging
|
||||
|
||||
class CloudflareAPIException(Exception):
|
||||
def __init__(self, message, response) -> None:
|
||||
full_message = f'Cloudflare request failed (code: {response.status_code}): {message}\n\nServer body:\n{response.text}'
|
||||
super().__init__(full_message)
|
||||
|
||||
class CloudFlare:
|
||||
def __init__(self, api_token):
|
||||
self.headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_token}'}
|
||||
|
||||
def get_zone_id(self, domain):
|
||||
response = requests.get(f'https://api.cloudflare.com/client/v4/zones?name={domain}', headers=self.headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise CloudflareAPIException('Something went wrong requesting the zone of the domain on Cloudflare...', response)
|
||||
|
||||
zone_id = response.json()['result'][0]['id']
|
||||
return zone_id
|
||||
|
||||
def get_record_id(self, zoneId, record):
|
||||
logging.debug('Getting record id...')
|
||||
response = requests.get(f'https://api.cloudflare.com/client/v4/zones/{zoneId}/dns_records?name={record}', headers=self.headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise CloudflareAPIException('Something went wrong requesting the record id of the domain name on Cloudflare...', response)
|
||||
|
||||
return response.json()['result'][0]['id']
|
||||
|
||||
|
||||
def change_record(self, subdomain, zoneId, recordId, targetIp):
|
||||
logging.info(f'Changing record for {subdomain} to point to {targetIp}...')
|
||||
response = requests.put(f'https://api.cloudflare.com/client/v4/zones/{zoneId}/dns_records/{recordId}',
|
||||
headers=self.headers,
|
||||
json={
|
||||
'content': targetIp,
|
||||
'name': subdomain,
|
||||
'proxied': False,
|
||||
'type': 'A',
|
||||
'comment': 'Is set to change with my DDNS script (on the Raspberry Pi).',
|
||||
'ttl': 1 # Meaning "Automatic", see: https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-update-dns-record
|
||||
})
|
||||
|
||||
if response.status_code != 200:
|
||||
raise CloudflareAPIException('Something went wrong updating the dns record...', response)
|
||||
|
||||
logging.info('Succesfully updated the record ✅!')
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/python3
|
||||
# Fetches the public IP and updates the record on Cloudflare which is provided as an argument
|
||||
# to match this public record.
|
||||
import logging
|
||||
import sys
|
||||
import configparser
|
||||
import argparse
|
||||
from cloudflare_api import CloudFlare
|
||||
from ip_helpers import get_public_IP, resolve_name
|
||||
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('subdomains', nargs='+')
|
||||
parser.add_argument('-c', '--config-file', dest='config')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
config_file_path = args.config
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_file_path)
|
||||
|
||||
cloudflare_api_credentials = config['credentials']['dns_cloudflare_token']
|
||||
cloudflare = CloudFlare(cloudflare_api_credentials)
|
||||
|
||||
log_path = config.get('log_changes', 'log_path', fallback=None)
|
||||
if log_path is not None:
|
||||
log_path = config['log_changes']['log_path']
|
||||
logging.info(f'Logging DNS name changes to {log_path} on IP updates.')
|
||||
|
||||
publicIP = get_public_IP()
|
||||
subdomains = args.subdomains
|
||||
fixedTopLevelDomain = 'kleinendorst.info'
|
||||
|
||||
for subdomain in subdomains:
|
||||
fullDomainName = f'{subdomain}.{fixedTopLevelDomain}'
|
||||
|
||||
resolvedIP = resolve_name(fullDomainName)
|
||||
if resolvedIP == publicIP:
|
||||
logging.info(f'Currently resolved name already matches the public ip ({publicIP}), skipping this name...')
|
||||
continue
|
||||
|
||||
zoneId = cloudflare.get_zone_id(fixedTopLevelDomain)
|
||||
recordId = cloudflare.get_record_id(zoneId, fullDomainName)
|
||||
cloudflare.change_record(subdomain, zoneId, recordId, publicIP)
|
||||
|
||||
with open(log_path, 'a+') as log_file:
|
||||
msg = f'Address for FQDN {fullDomainName} altered from: {resolvedIP} - {publicIP}.'
|
||||
logging.info(f'Writing: "{msg}" to log file at {log_path}...')
|
||||
log_file.write(msg + '\n')
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/bash
|
||||
source ~/bin/cloudflare_ddns/venv/bin/activate
|
||||
python3 ~/bin/cloudflare_ddns/cloudflare_ddns.py --config-file ~/cloudflare_ddns/ddns_config.ini $@
|
||||
27
roles/cloudflare-ddns/files/cloudflare_ddns/ip_helpers.py
Normal file
27
roles/cloudflare-ddns/files/cloudflare_ddns/ip_helpers.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import logging
|
||||
import requests
|
||||
import dns.resolver
|
||||
|
||||
|
||||
def get_public_IP():
|
||||
logging.info('Retrieving the public IP for this machine...')
|
||||
response = requests.get('https://api.ipify.org?format=json')
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception('Something went wrong requesting the public ip, exiting...')
|
||||
|
||||
public_ip = response.json()['ip']
|
||||
logging.info(f'The public IP address for this machine is: {public_ip}.')
|
||||
return public_ip
|
||||
|
||||
def resolve_name(domain):
|
||||
logging.info(f'Resolving {domain}...')
|
||||
try:
|
||||
result = dns.resolver.resolve(domain, 'A')
|
||||
except dns.resolver.NXDOMAIN:
|
||||
logging.error(f'No DNS record exists for {domain}, configure it first before using this ddns script. Exiting...')
|
||||
exit(1)
|
||||
|
||||
resolved_address = result[0].address
|
||||
logging.info(f'Resolved {domain} to: {resolved_address}.')
|
||||
return resolved_address
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
requests==2.31.0
|
||||
dnspython==2.6.1
|
||||
Loading…
Add table
Add a link
Reference in a new issue