Compare commits
5 Commits
95df5f98cb
...
4bb94bdb61
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bb94bdb61 | |||
| 4751f0e996 | |||
| 2800425740 | |||
| 7a08769359 | |||
| 8ac1ed00b6 |
1
Makefile
1
Makefile
@@ -2,6 +2,7 @@ install:
|
||||
cp hetzner-ddns.py /usr/local/bin
|
||||
cp etc/hetzner-ddns.conf.example /etc
|
||||
cp etc/systemd/hetzner-ddns.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
|
||||
uninstall:
|
||||
rm /usr/local/bin/hetzner-ddns.py
|
||||
|
||||
23
README.md
23
README.md
@@ -3,6 +3,8 @@
|
||||
This script finds this machine's hostname, public IPv4 and IPv6 addresses,
|
||||
then updates the corresponding DNS records on Hetzner.
|
||||
|
||||
**Note:** *This script is not developed, maintained or supported by Hetzner.*
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
@@ -28,30 +30,39 @@ Options:
|
||||
|
||||
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:
|
||||
|
||||
pip install docopt
|
||||
```
|
||||
sudo pip3 install docopt
|
||||
```
|
||||
|
||||
3. Clone the repository somewhere:
|
||||
|
||||
cd /opt
|
||||
```
|
||||
git clone https://github.com/iSoron/hetzner-ddns.git
|
||||
```
|
||||
|
||||
4. Install the script system-wide:
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/hetzner-ddns.py
|
||||
Type=simple
|
||||
ExecStart=python3 -u /usr/local/bin/hetzner-ddns.py
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -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__)
|
||||
@@ -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,13 +100,14 @@ 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:
|
||||
@@ -113,19 +118,22 @@ def get_addr(url,
|
||||
|
||||
|
||||
def get_all_records(zone):
|
||||
response = requests.get(url="https://dns.hetzner.com/api/v1/records",
|
||||
response = requests.get(
|
||||
url="https://dns.hetzner.com/api/v1/records",
|
||||
params={"zone_id": zone["id"]},
|
||||
headers={"Auth-API-Token": args["--token"]})
|
||||
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:
|
||||
@@ -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,6 +198,7 @@ def main():
|
||||
print("Finding DNS zone...")
|
||||
zone = find_zone(args["--zone"])
|
||||
|
||||
while True:
|
||||
for kind in kinds:
|
||||
if kind == "A":
|
||||
print("Finding public IPv4 address...")
|
||||
@@ -189,28 +210,30 @@ def main():
|
||||
print(" %s" % addr)
|
||||
|
||||
print("Finding existing %s record..." % kind)
|
||||
rec = find_record(zone=zone,
|
||||
kind=kind,
|
||||
name=args["--hostname"])
|
||||
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")
|
||||
|
||||
if rec is None:
|
||||
print(" not found")
|
||||
print("Creating new %s record..." % kind)
|
||||
create_record({
|
||||
create_record(
|
||||
{
|
||||
"value": addr,
|
||||
"type": kind,
|
||||
"name": args["--hostname"],
|
||||
"zone_id": args["--zone"],
|
||||
"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(f"Sleeping for {delay} seconds...")
|
||||
time.sleep(delay)
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user