From 9487784876de300512c2a3e5088acab88fe0e7c4 Mon Sep 17 00:00:00 2001 From: Tim Miller Date: Sun, 29 Sep 2019 15:33:25 -0400 Subject: [PATCH] Public release Added removal of stable, duplicate records per subdomain. --- README.md | 59 ++++++++++++++++++++++++++++ cloudflare-ddns.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++ config.json | 14 +++++++ sync | 10 +++++ 4 files changed, 181 insertions(+) create mode 100755 README.md create mode 100755 cloudflare-ddns.py create mode 100755 config.json create mode 100755 sync diff --git a/README.md b/README.md new file mode 100755 index 0000000..25a2dfd --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# :rocket: Cloudflare DDNS + +Dynamic DNS service based on Cloudflare! Access your home network remotely via a custom domain name without a static IP! + +## :us: Origin + +This script was written for the Raspberry Pi platform to enable low cost, simple self hosting to promote a more decentralized internet. On execution, the script fetches public IPv4 and IPv6 addresses and creates/updates DNS records for the subdomains in Cloudflare. Stale, duplicate DNS records are removed for housekeeping. + +## :vertical_traffic_light: Getting Started + +Edit config.json and replace the values with your own. + +Values explained: + +```json +"api_key": "Your cloudflare API Key", +"account_email": "The email address you use to sign in to cloudflare", +"zone_id": "The ID of the zone that will get the records. From your dashboard click into the zone. Under the overview tab, scroll down and the zone ID is listed in the right rail", +"subdomains": "Array of subdomaind you want to assign the IP(s) to (e.g. pi.example.com)", +"proxied": false (defaults to false. Make it true if you want CDN/SSL benefits from cloudflare. This usually disables SSH) +``` + +## :running: Running + +This script requires Python 3.5+, which comes preinstalled on the latest version of Raspbian. Download/clone this repo and execute `./sync`, which will set up a virtualenv, pull in any dependencies, and fire the script. + +## :alarm_clock: Scheduling + +This script was written with the intention of cron managing it. + +## :penguin: Linux instructions (all distros) + +1. Upload the cloudflare-ddns folder to your home directory /home/your_username_here/ + +2. Run the following code in terminal + +```bash +crontab -e +``` + +3. Add the following lines to sync your DNS records every 15 minutes + +```bash +*/15 * * * * /home/your_username_here/cloudflare-ddns/sync +``` + +## License + +This Template is licensed under the GNU General Public License, version 3 (GPLv3) and is distributed free of charge. + +## Author + +Timothy Miller + +GitHub: https://github.com/timothymiller 💡 + +Website: https://timknowsbest.com 💻 + +Donation: https://timknowsbest.com/donate 💸 diff --git a/cloudflare-ddns.py b/cloudflare-ddns.py new file mode 100755 index 0000000..2b16e2f --- /dev/null +++ b/cloudflare-ddns.py @@ -0,0 +1,98 @@ +import requests +import json +import sys +import os + +PATH = os.getcwd() + "/" +version = float(str(sys.version_info[0]) + "." + str(sys.version_info[1])) + +if(version < 3.5): + raise Exception("This script requires Python 3.5+") + +with open(PATH + "config.json") as config_file: + config = json.loads(config_file.read()) + + +def getIPs(): + a = requests.get("https://api.ipify.org?format=json").json().get("ip") + aaaa = requests.get("https://api6.ipify.org?format=json").json().get("ip") + ips = [] + + if(a.find(".") > -1): + ips.append({ + "type": "A", + "ip": a + }) + + if(aaaa.find(":") > -1): + ips.append({ + "type": "AAAA", + "ip": aaaa + }) + + return ips + + +def commitRecord(ip): + stale_record_ids = [] + for c in config["cloudflare"]: + subdomains = c["subdomains"] + response = cf_api("zones/" + c['zone_id'], "GET", c) + base_domain_name = response["result"]["name"] + for subdomain in subdomains: + exists = False + record = { + "type": ip["type"], + "name": subdomain, + "content": ip["ip"], + "proxied": c["proxied"] + } + list = cf_api("zones/" + c['zone_id'] + "/dns_records&per_page=100?type=" + ip["type"], "GET", c) + full_subdomain = subdomain + "." + base_domain_name + dns_id = "" + for r in list["result"]: + if (r["name"] == full_subdomain): + exists = True + if (r["content"] != ip["ip"]): + if (dns_id == ""): + dns_id = r["id"] + else: + stale_record_ids.append(r["id"]) + if(exists == False): + print("Adding new record " + str(record)) + response = cf_api( + "zones/" + c['zone_id'] + "/dns_records", "POST", c, {}, record) + elif(dns_id != ""): + # Only update if the record content is different + print("Updating record " + str(record)) + response = cf_api( + "zones/" + c['zone_id'] + "/dns_records/" + dns_id, "PUT", c, {}, record) + + # Delete duplicate, stale records + for identifier in stale_record_ids: + print("Deleting stale record " + str(identifier)) + response = cf_api("zones/" + c['zone_id'] + "/dns_records/" + identifier, "DELETE", c) + + return True + + +def cf_api(endpoint, method, config, headers={}, data=False): + headers = { + "X-Auth-Email": config['account_email'], + "X-Auth-Key": config['api_key'], + **headers + } + + if(data == False): + response = requests.request( + method, "https://api.cloudflare.com/client/v4/" + endpoint, headers=headers) + else: + response = requests.request( + method, "https://api.cloudflare.com/client/v4/" + endpoint, headers=headers, json=data) + + return response.json() + + +for ip in getIPs(): + print("Checking " + ip["type"] + " records") + commitRecord(ip) diff --git a/config.json b/config.json new file mode 100755 index 0000000..d62a13a --- /dev/null +++ b/config.json @@ -0,0 +1,14 @@ +{ + "cloudflare": [ + { + "api_key": "api_key_here", + "account_email": "your_email_here", + "zone_id": "your_zone_id_here", + "subdomains": [ + "one.example.com", + "two.example.com" + ], + "proxied": false + } + ] +} \ No newline at end of file diff --git a/sync b/sync new file mode 100755 index 0000000..7c01cff --- /dev/null +++ b/sync @@ -0,0 +1,10 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +python3 -m venv venv +source ./venv/bin/activate + +pip install requests + +cd $DIR +python cloudflare-ddns.py