172 lines
5.6 KiB
Python
Executable File
172 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# encoding: utf-8
|
|
'''
|
|
Gandi v5 LiveDNS - DynDNS Update via REST API and CURL/requests
|
|
|
|
@author: cave
|
|
@author: dvdme
|
|
License GPLv3
|
|
https://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
Created on 13 Aug 2017
|
|
Forked on 08 Dec 2019
|
|
http://doc.livedns.gandi.net/
|
|
http://doc.livedns.gandi.net/#api-endpoint -> https://dns.gandi.net/api/v5/
|
|
'''
|
|
|
|
import json
|
|
import requests
|
|
import ipaddress
|
|
import config
|
|
import argparse
|
|
import threading as th
|
|
from pprint import pprint
|
|
|
|
|
|
def check_is_ipv6(ip_address, verbose=False):
|
|
return ipaddress.ip_address(ip_address).version == 6
|
|
|
|
|
|
def get_dynip(ifconfig_provider, verbose=False):
|
|
''' find out own IPv4 at home <-- this is the dynamic IP which changes more or less frequently
|
|
similar to curl ifconfig.me/ip, see example.config.py for details to ifconfig providers
|
|
'''
|
|
if verbose:
|
|
print(f'Using {ifconfig_provider}')
|
|
r = requests.get(ifconfig_provider)
|
|
print (f'Checking dynamic IP: {r.text.strip()}')
|
|
return r.text.strip()
|
|
|
|
def get_uuid(verbose=False):
|
|
'''
|
|
find out ZONE UUID from domain
|
|
Info on domain "DOMAIN"
|
|
GET /domains/<DOMAIN>:
|
|
'''
|
|
url = config.api_endpoint + '/domains/' + config.domain
|
|
u = requests.get(url, headers={"X-Api-Key":config.api_secret})
|
|
json_object = u.json()
|
|
if verbose:
|
|
pprint(json_object)
|
|
if u.status_code == 200:
|
|
return json_object['zone_uuid']
|
|
else:
|
|
print(f'Error: HTTP Status Code {u.status_code} when trying to get Zone UUID')
|
|
pprint(u.json())
|
|
exit()
|
|
|
|
def get_dnsip(uuid, is_ipv6=False, verbose=False):
|
|
''' find out IP from first Subdomain DNS-Record
|
|
List all records with name "NAME" and type "TYPE" in the zone UUID
|
|
GET /zones/<UUID>/records/<NAME>/<TYPE>:
|
|
|
|
The first subdomain from config.subdomain will be used to get
|
|
the actual DNS Record IP
|
|
'''
|
|
if is_ipv6:
|
|
record_type = '/AAAA'
|
|
subdomain = config.subdomains6[0]
|
|
else:
|
|
record_type = '/A'
|
|
subdomain = config.subdomains[0]
|
|
|
|
url = config.api_endpoint+ '/zones/' + uuid + '/records/' + subdomain + record_type
|
|
headers = {'X-Api-Key':config.api_secret}
|
|
u = requests.get(url, headers=headers)
|
|
if u.status_code == 200:
|
|
json_object = u.json()
|
|
if verbose:
|
|
pprint(json_object)
|
|
dnsip = json_object['rrset_values'][0].strip()
|
|
print (f'Checking IP from DNS Record {subdomain}: {dnsip}')
|
|
return dnsip
|
|
else:
|
|
print('Error: HTTP Status Code ', u.status_code, 'when trying to get IP from subdomain', subdomain)
|
|
pprint(u.json())
|
|
exit()
|
|
|
|
def update_records(uuid, dynIP, subdomain, is_ipv6=False, verbose=False):
|
|
''' update DNS Records for Subdomains
|
|
Change the "NAME"/"TYPE" record from the zone UUID
|
|
PUT /zones/<UUID>/records/<NAME>/<TYPE>:
|
|
curl -X PUT -H "Content-Type: application/json" \
|
|
-H 'X-Api-Key: XXX' \
|
|
-d '{"rrset_ttl": 10800,
|
|
"rrset_values": ["<VALUE>"]}' \
|
|
https://dns.gandi.net/api/v5/zones/<UUID>/records/<NAME>/<TYPE>
|
|
'''
|
|
if is_ipv6:
|
|
record_type = '/AAAA'
|
|
else:
|
|
record_type = '/A'
|
|
url = config.api_endpoint+ '/zones/' + uuid + '/records/' + subdomain + record_type
|
|
payload = {'rrset_ttl': config.ttl, "rrset_values": [dynIP]}
|
|
headers = {'Content-Type': 'application/json', 'X-Api-Key':config.api_secret}
|
|
u = requests.put(url, data=json.dumps(payload), headers=headers)
|
|
json_object = u.json()
|
|
if u.status_code == 201:
|
|
print (f'Status Code: {u.status_code}, {json_object["message"]}, IP updated for {subdomain}')
|
|
return True
|
|
else:
|
|
print (f'Error: HTTP Status Code {u.status_code} when trying to update IP from subdomain {subdomain}')
|
|
print (json_object['message'])
|
|
exit()
|
|
|
|
|
|
|
|
def main(force_update, verbosity, repeat):
|
|
|
|
if verbosity:
|
|
print('verbosity turned on')
|
|
verbose = True
|
|
else:
|
|
verbose =False
|
|
|
|
if repeat and verbose:
|
|
print(f'repeat turned on, will repeat every {repeat} seconds')
|
|
|
|
#get zone ID from Account
|
|
uuid = get_uuid()
|
|
|
|
#compare dynIP and DNS IP
|
|
dynIP = get_dynip(config.ifconfig, verbose)
|
|
|
|
if check_is_ipv6(dynIP, verbose):
|
|
subdomains = config.subdomains6
|
|
is_ipv6 = True
|
|
print('Detected ipv6')
|
|
else:
|
|
print('Detected ipv4')
|
|
is_ipv6 = False
|
|
subdomains = config.subdomains
|
|
|
|
dnsIP = get_dnsip(uuid, is_ipv6, verbose)
|
|
|
|
if force_update:
|
|
print ('Going to update/create the DNS Records for the subdomains')
|
|
for sub in subdomains:
|
|
update_records(uuid, dynIP, sub, is_ipv6, verbose)
|
|
else:
|
|
if verbose:
|
|
print(f'dynIP: {dynIP}')
|
|
print(f'dnsIP: {dnsIP}')
|
|
if dynIP == dnsIP:
|
|
print ('IP Address Match - no further action')
|
|
else:
|
|
print (f'IP Address Mismatch - going to update the DNS Records for the subdomains with new IP {dynIP}')
|
|
for sub in subdomains:
|
|
update_records(uuid, dynIP, sub, is_ipv6, verbose)
|
|
if repeat:
|
|
if verbosity:
|
|
print(f'Repeating in {repeat} seconds')
|
|
th.Timer(repeat, main, [force_update, verbosity, repeat]).start()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true')
|
|
parser.add_argument('-f', '--force', help='force an update/create', action='store_true')
|
|
parser.add_argument('-r', '--repeat', type=int, help='keep running and repeat every N seconds')
|
|
args = parser.parse_args()
|
|
main(args.force, args.verbose, args.repeat)
|