Skip to content

Instantly share code, notes, and snippets.

@lumapu
Last active March 8, 2024 23:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lumapu/f3901dcb0a5dc5a4a45b0a8f17009318 to your computer and use it in GitHub Desktop.
Save lumapu/f3901dcb0a5dc5a4a45b0a8f17009318 to your computer and use it in GitHub Desktop.
Hetzner DNS records updater using DNS API (dyndns)
import requests
import json
import re
import sys
import os
from datetime import datetime
# --- CONFIGURATION --------------------------------
auth_token = "YOUR-TOKEN"
zone_name = "YOUR_TLD"
record_names = ["@"]
# path to temporarily file which holds the last ip
# note: /dev/shm is a virtual filesystem in RAM
tmp_file = "/dev/shm/public_ip"
# sample crontab which checks public IP each minute
# * * * * * python3 /home/user/hetzner.dyndns.py >> /home/user/dyndns.log 2>&1
# --- END CONFIGURATION ----------------------------
def getPublicIp():
try:
response = requests.get(url="http://checkip.dyndns.com/")
if response.status_code == 200:
return re.findall(r"[0-9.]+", response.content.decode('utf-8'))[0]
except requests.exceptions.RequestException:
print('HTTP Request failed')
def hetznerApi(param, data = None):
try:
response = None
body = {
"url": "https://dns.hetzner.com/api/v1/"+param,
"headers": {
"Auth-API-Token": auth_token,
"Content-Type": "application/json; charset=utf-8"
},
"data": data
}
if data == None:
response = requests.get(**body)
else:
response = requests.put(**body)
if response.status_code == 200:
return response.content
else:
sys.exit(str(response.status_code)
+ ": " + response.content.decode('utf-8'))
except requests.exceptions.RequestException:
print('HTTP Request failed')
def getZoneByName(data, name):
for zone in data["zones"]:
if zone["name"] == name:
return zone["id"]
sys.exit("zone '" + name + "' not found")
def getARecordZoneIdByName(data, name):
for record in data["records"]:
if (record["name"] == name) and (record["type"] == "A"):
return record["id"]
sys.exit("record '" + name + "' not found")
def updateRecord(zoneId, recordId, name, ip, type="A"):
data = json.loads(hetznerApi("records/"+recordId, json.dumps({
"type": type,
"name": name,
"value": ip,
"zone_id": zoneId
})))["record"]
if data["value"] == str(ip):
print(
name + "." + zone_name + " successfully updated to " + str(ip)
+ "\n -> last modified: " + data["modified"]
)
# check if public IP has changed
lastIp = None
pubIp = getPublicIp()
if os.path.exists(tmp_file):
with open(tmp_file, "r") as f:
lastIp = f.read()
if lastIp != pubIp:
with open(tmp_file, "w") as f:
f.write(pubIp)
print("-----------------------------------------------")
print("update: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# get zones and find requested zone
data = json.loads(hetznerApi("zones"))
zoneId = getZoneByName(data, zone_name)
# get records of zone
data = json.loads(hetznerApi("records?zone_id="+zoneId))
# loop through records and update them
for record in record_names:
recordId = getARecordZoneIdByName(data, record)
updateRecord(zoneId, recordId, record, pubIp)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment