Compare commits

..

5 Commits

Author SHA1 Message Date
4bb94bdb61 Use zone ID instead of name 2021-04-29 10:48:48 -05:00
4751f0e996 Update DNS repeatedly 2020-12-17 10:36:24 -06:00
2800425740 Reformat file 2020-12-17 09:59:18 -06:00
7a08769359 Update README.md 2020-06-14 08:58:32 -05:00
8ac1ed00b6 Update README.md 2020-06-14 08:52:52 -05:00
4 changed files with 93 additions and 58 deletions

View File

@@ -2,6 +2,7 @@ install:
cp hetzner-ddns.py /usr/local/bin cp hetzner-ddns.py /usr/local/bin
cp etc/hetzner-ddns.conf.example /etc cp etc/hetzner-ddns.conf.example /etc
cp etc/systemd/hetzner-ddns.service /etc/systemd/system/ cp etc/systemd/hetzner-ddns.service /etc/systemd/system/
systemctl daemon-reload
uninstall: uninstall:
rm /usr/local/bin/hetzner-ddns.py rm /usr/local/bin/hetzner-ddns.py

View File

@@ -3,6 +3,8 @@
This script finds this machine's hostname, public IPv4 and IPv6 addresses, This script finds this machine's hostname, public IPv4 and IPv6 addresses,
then updates the corresponding DNS records on Hetzner. then updates the corresponding DNS records on Hetzner.
**Note:** *This script is not developed, maintained or supported by Hetzner.*
## Usage ## Usage
```text ```text
@@ -28,30 +30,39 @@ Options:
The instructions below were tested on Ubuntu Linux 20.04 LTS. The instructions below were tested on Ubuntu Linux 20.04 LTS.
1. Install `python3` and `pip`: 1. Install Python and pip:
apt install python3 ```
sudo apt install python3 python3-pip
```
2. Install package dependencies: 2. Install package dependencies:
pip install docopt ```
sudo pip3 install docopt
```
3. Clone the repository somewhere: 3. Clone the repository somewhere:
cd /opt ```
git clone https://github.com/iSoron/hetzner-ddns.git git clone https://github.com/iSoron/hetzner-ddns.git
```
4. Install the script system-wide: 4. Install the script system-wide:
cd hetzner-ddns ```
sudo make install cd hetzner-ddns
sudo make install
```
5. Create a configuration file in `/etc/hetzner-ddns.conf` with you API token. See `/etc/hetzner-ddns.conf.example` for an example. 5. Create a configuration file in `/etc/hetzner-ddns.conf` with you API token. See `/etc/hetzner-ddns.conf.example` for an example.
6. Run script and enable it during boot: 6. Run script and enable it during boot:
sudo systemctl start hetzner-ddns ```
sudo systemctl enable hetzner-ddns sudo make start
sudo make enable
```
## License ## License

View File

@@ -4,8 +4,8 @@ After=network-online.target
Wants=network-online.target Wants=network-online.target
[Service] [Service]
Type=oneshot Type=simple
ExecStart=/usr/local/bin/hetzner-ddns.py ExecStart=python3 -u /usr/local/bin/hetzner-ddns.py
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

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