|
|
|
@ -20,6 +20,7 @@ Options:
|
|
|
|
|
--config=<file> Read options from configuration file
|
|
|
|
|
--disable-v4 Do not update IPv4 address
|
|
|
|
|
--disable-v6 Do not update IPv6 address
|
|
|
|
|
--repeat=<s> Update DNS again every S seconds
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from docopt import docopt
|
|
|
|
@ -30,6 +31,7 @@ import requests
|
|
|
|
|
import socket
|
|
|
|
|
import sys
|
|
|
|
|
import configparser
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
args = docopt(__doc__)
|
|
|
|
@ -39,9 +41,9 @@ def merge_config_file():
|
|
|
|
|
global args
|
|
|
|
|
basepath = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
candidate_config_files = [
|
|
|
|
|
args["--config"],
|
|
|
|
|
os.path.join(basepath, "hetzner-ddns.conf"),
|
|
|
|
|
"/etc/hetzner-ddns.conf",
|
|
|
|
|
args["--config"],
|
|
|
|
|
os.path.join(basepath, "hetzner-ddns.conf"),
|
|
|
|
|
"/etc/hetzner-ddns.conf",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for filename in candidate_config_files:
|
|
|
|
@ -51,7 +53,7 @@ def merge_config_file():
|
|
|
|
|
print("Reading options from %s" % filename)
|
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
|
config.read(filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
section_name = config.sections()[0]
|
|
|
|
|
print(" %-20s %s" % ("--zone", section_name))
|
|
|
|
|
args["--zone"] = section_name
|
|
|
|
@ -65,6 +67,7 @@ def merge_config_file():
|
|
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def merge_defaults():
|
|
|
|
|
global args
|
|
|
|
|
default_args = {
|
|
|
|
@ -74,6 +77,7 @@ def merge_defaults():
|
|
|
|
|
"--retry-attempts": 12,
|
|
|
|
|
"--retry-delay": 5,
|
|
|
|
|
"--hostname": socket.gethostname(),
|
|
|
|
|
"--repeat": 3600,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("Applying default options:")
|
|
|
|
@ -96,36 +100,40 @@ if args["--zone"] is None:
|
|
|
|
|
merge_defaults()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_addr(url,
|
|
|
|
|
retry=int(args["--retry-attempts"]),
|
|
|
|
|
delay=int(args["--retry-delay"])):
|
|
|
|
|
def get_addr(
|
|
|
|
|
url, retry=int(args["--retry-attempts"]), delay=int(args["--retry-delay"])
|
|
|
|
|
):
|
|
|
|
|
exception = None
|
|
|
|
|
for i in range(retry):
|
|
|
|
|
try:
|
|
|
|
|
import urllib
|
|
|
|
|
|
|
|
|
|
txt = urllib.request.urlopen(url).read()
|
|
|
|
|
return txt.decode("utf-8")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
exception = e
|
|
|
|
|
print(" connection failed, retrying in %d seconds..." % delay)
|
|
|
|
|
sleep(delay)
|
|
|
|
|
raise(exception)
|
|
|
|
|
raise (exception)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_all_records(zone):
|
|
|
|
|
response = requests.get(url="https://dns.hetzner.com/api/v1/records",
|
|
|
|
|
params={"zone_id": zone["id"]},
|
|
|
|
|
headers={"Auth-API-Token": args["--token"]})
|
|
|
|
|
response = requests.get(
|
|
|
|
|
url="https://dns.hetzner.com/api/v1/records",
|
|
|
|
|
params={"zone_id": zone["id"]},
|
|
|
|
|
headers={"Auth-API-Token": args["--token"]},
|
|
|
|
|
)
|
|
|
|
|
return response.json()["records"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_all_zones():
|
|
|
|
|
response = requests.get(url="https://dns.hetzner.com/api/v1/zones",
|
|
|
|
|
headers={"Auth-API-Token": args["--token"]})
|
|
|
|
|
response = requests.get(
|
|
|
|
|
url="https://dns.hetzner.com/api/v1/zones",
|
|
|
|
|
headers={"Auth-API-Token": args["--token"]},
|
|
|
|
|
)
|
|
|
|
|
return response.json()["zones"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_record(zone, name, kind="AAAA"):
|
|
|
|
|
all_records = get_all_records(zone)
|
|
|
|
|
for r in all_records:
|
|
|
|
@ -139,7 +147,7 @@ def find_zone(name):
|
|
|
|
|
for z in all_zones:
|
|
|
|
|
if z["name"] == name:
|
|
|
|
|
return z
|
|
|
|
|
raise(Exception("Zone not found: %s" % name))
|
|
|
|
|
raise (Exception("Zone not found: %s" % name))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_record(record):
|
|
|
|
@ -149,7 +157,7 @@ def update_record(record):
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Auth-API-Token": args["--token"],
|
|
|
|
|
},
|
|
|
|
|
data=json.dumps(record)
|
|
|
|
|
data=json.dumps(record),
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
|
@ -161,13 +169,25 @@ def create_record(record):
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Auth-API-Token": args["--token"],
|
|
|
|
|
},
|
|
|
|
|
data=json.dumps(record)
|
|
|
|
|
data=json.dumps(record),
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delete_record(rid):
|
|
|
|
|
response = requests.delete(
|
|
|
|
|
url=f"https://dns.hetzner.com/api/v1/records/{rid}",
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Auth-API-Token": args["--token"],
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
kinds = []
|
|
|
|
|
delay = int(args["--repeat"])
|
|
|
|
|
|
|
|
|
|
if not bool(args["--disable-v4"]):
|
|
|
|
|
kinds += ["A"]
|
|
|
|
@ -178,39 +198,42 @@ def main():
|
|
|
|
|
print("Finding DNS zone...")
|
|
|
|
|
zone = find_zone(args["--zone"])
|
|
|
|
|
|
|
|
|
|
for kind in kinds:
|
|
|
|
|
if kind == "A":
|
|
|
|
|
print("Finding public IPv4 address...")
|
|
|
|
|
addr = get_addr(args["--v4-api"])
|
|
|
|
|
print(" %s" % addr)
|
|
|
|
|
else:
|
|
|
|
|
print("Finding public IPv6 address...")
|
|
|
|
|
addr = get_addr(args["--v6-api"])
|
|
|
|
|
print(" %s" % addr)
|
|
|
|
|
|
|
|
|
|
print("Finding existing %s record..." % kind)
|
|
|
|
|
rec = find_record(zone=zone,
|
|
|
|
|
kind=kind,
|
|
|
|
|
name=args["--hostname"])
|
|
|
|
|
|
|
|
|
|
if rec is None:
|
|
|
|
|
print(" not found")
|
|
|
|
|
while True:
|
|
|
|
|
for kind in kinds:
|
|
|
|
|
if kind == "A":
|
|
|
|
|
print("Finding public IPv4 address...")
|
|
|
|
|
addr = get_addr(args["--v4-api"])
|
|
|
|
|
print(" %s" % addr)
|
|
|
|
|
else:
|
|
|
|
|
print("Finding public IPv6 address...")
|
|
|
|
|
addr = get_addr(args["--v6-api"])
|
|
|
|
|
print(" %s" % addr)
|
|
|
|
|
|
|
|
|
|
print("Finding existing %s record..." % kind)
|
|
|
|
|
rec = find_record(zone=zone, kind=kind, name=args["--hostname"])
|
|
|
|
|
|
|
|
|
|
if rec is not None:
|
|
|
|
|
if rec["value"] == addr:
|
|
|
|
|
print("Existing record is up-to-date")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
print("Deleting existing %s record..." % kind)
|
|
|
|
|
delete_record(rec["id"])
|
|
|
|
|
print(" done")
|
|
|
|
|
|
|
|
|
|
print("Creating new %s record..." % kind)
|
|
|
|
|
create_record({
|
|
|
|
|
"value": addr,
|
|
|
|
|
"type": kind,
|
|
|
|
|
"name": args["--hostname"],
|
|
|
|
|
"zone_id": args["--zone"],
|
|
|
|
|
"ttl": args["--ttl"],
|
|
|
|
|
})
|
|
|
|
|
print(" done")
|
|
|
|
|
else:
|
|
|
|
|
print(" found")
|
|
|
|
|
print("Updating existing %s record..." % kind)
|
|
|
|
|
rec["value"] = addr
|
|
|
|
|
rec["ttl"] = args["--ttl"]
|
|
|
|
|
update_record(rec)
|
|
|
|
|
create_record(
|
|
|
|
|
{
|
|
|
|
|
"value": addr,
|
|
|
|
|
"type": kind,
|
|
|
|
|
"name": args["--hostname"],
|
|
|
|
|
"zone_id": zone["id"],
|
|
|
|
|
"ttl": args["--ttl"],
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
print(" done")
|
|
|
|
|
|
|
|
|
|
print(f"Sleeping for {delay} seconds...")
|
|
|
|
|
time.sleep(delay)
|
|
|
|
|
|
|
|
|
|
main()
|
|
|
|
|