diff --git a/README.md b/README.md index 70fc261..3c621ee 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ gandi-live-dns ---- This is a simple dynamic DNS updater for the -[Gandi](https://www.gandi.net) registrar. It uses their [LiveDNS REST API](http://doc.livedns.gandi.net/) to update the zone file for a subdomain of a domain to point at the external IPv4 address of the computer it has been run from. +[Gandi](https://www.gandi.net) registrar. It uses their [LiveDNS REST API](http://doc.livedns.gandi.net/) to update the zone file for a subdomain of a domain to point at the external IPv4 and/or IPv6 address of the computer it has been run from. It has been developed on Debian 8 Jessie and tested on Debian 9 Stretch GNU/Linux using Python 2.7. @@ -17,25 +17,25 @@ You want your homeserver to be always available at `dynamic_subdomain.mydomain.t `apt-get update && apt-get upgrade && apt-get install unzip python-requests python-args python-simplejson` #### API Key -First, you must apply for an API key with Gandi. Visit -https://account.gandi.net/en/ and apply for (at least) the production API +First, you must apply for an API key with Gandi. Visit +https://account.gandi.net/en/ and apply for (at least) the production API key by following their directions. -#### A DNS Record -Create the DNS A Records in the GANDI Webinterface which you want to update if your IP changes. +#### A or AAAA DNS Record +Create the DNS Records in the GANDI Webinterface which you want to update if your IP changes. Use an A Record for IPv4 and AAAA Record for IPv6. #### Git Clone or Download the Script Download the Script from here as [zip](https://github.com/cavebeat/gandi-live-dns/archive/master.zip)/[tar.gz](https://github.com/cavebeat/gandi-live-dns/archive/master.tar.gz) and extract it. or clone from git -`git clone https://github.com/cavebeat/gandi-live-dns.git` +`git clone https://github.com/cavebeat/gandi-live-dns.git` #### Script Configuration Then you'd need to configure the script in the src directory. Copy `example.config.py` to `config.py`, and put it in the same directory as the script. -Edit the config file to fit your needs. +Edit the config file to fit your needs. ##### api_secret Start by retrieving your API Key from the "Security" section in new [Gandi Account admin panel](https://account.gandi.net/) to be able to make authenticated requests to the API. @@ -50,16 +50,16 @@ api_endpoint = 'https://dns.api.gandi.net/api/v5' ``` ##### domain -Your domain for the subdomains to be updated +Your domain for the subdomains to be updated ##### subdomains All subdomains which should be updated. They get created if they do not yet exist. -``` +``` subdomains = ["subdomain1", "subdomain2", "subdomain3"] ``` -The first subdomain is used to find out the actual IP in the Zone Records. +The first subdomain is used to find out the actual IP in the Zone Records. #### Run the script And run the script: @@ -68,17 +68,26 @@ And run the script: root@dyndns:~/gandi-live-dns-master/src# ./gandi-live-dns.py Checking dynamic IP: 127.0.0.1 Checking IP from DNS Record subdomain1: 127.0.0.1 -IP Address Match - no further action +IPv4 Address Match - no further action +Checking dynamic IP: fe80::1 +Checking IP from DNS Record subdomain1: fe80::1 +IPv6 Address Match - no further action ``` -If your IP has changed, it will be detected and the update will be triggered. +If your IP has changed, it will be detected and the update will be triggered. ``` root@dyndns:~/gandi-live-dns-master/src# ./gandi-live-dns.py Checking dynamic IP: 127.0.0.2 Checking IP from DNS Record subdomain1: 127.0.0.1 -IP Address Mismatch - going to update the DNS Records for the subdomains with new IP 127.0.0.2 +IPv4 Address Mismatch - going to update the DNS Records for the subdomains with new IP 127.0.0.2 +Status Code: 201 , DNS Record Created , IP updated for subdomain1 +Status Code: 201 , DNS Record Created , IP updated for subdomain2 +Status Code: 201 , DNS Record Created , IP updated for subdomain3 +Checking dynamic IP: fe80::2 +Checking IP from DNS Record subdomain1: fe80::1 +IPv6 Address Mismatch - going to update the DNS Records for the subdomains with new IP fe80::2 Status Code: 201 , DNS Record Created , IP updated for subdomain1 Status Code: 201 , DNS Record Created , IP updated for subdomain2 Status Code: 201 , DNS Record Created , IP updated for subdomain3 @@ -96,36 +105,38 @@ optional arguments: ``` -The force option runs the script, even when no IP change has been detected. -It will update all subdomains and even create them if they are missing in the -Zone File/Zone UUID. This can be used if additional/new subdomains get appended to the conig file. +The force option runs the script, even when no IP change has been detected. +It will update all subdomains and even create them if they are missing in the +Zone File/Zone UUID. This can be used if additional/new subdomains get appended to the config file. -### IP address lookup service -There exist several providers for this case, but better is to run your own somewhere. +### IP address lookup service +There exist several providers for this case, but better is to run your own somewhere. #### Poor Mans PHP Solution -On a LAMP Stack, place the file [index.php](https://github.com/cavebeat/gandi-live-dns/blob/master/src/example-index.php) in a directory /ip in your webroot. +On a LAMP Stack, place the file [index.php](https://github.com/cavebeat/gandi-live-dns/blob/master/src/example-index.php) in a directory /ip in your webroot. ``` root@laptop:~# curl https://blog.cavebeat.org/ip/ 127.0.0.1 ``` -This should fit your personal needs and you still selfhost the whole thing. +This should fit your personal needs and you still selfhost the whole thing. #### IP address lookup service https://ifconfig.co https://github.com/mpolden/ipd A simple service for looking up your IP address. This is the code that powers [https://ifconfig.co](https://ifconfig.co) -#### use external services -choose one as described in the config file. +#### Use external services +Choose one as described in the config file. +If you are updating AAAA Records with your IPv6 address, make sure your IP address lookup service supports returning your IPv6 address. +See `example.config.py` for how https://ifconfig.co can be used in this way. ### Cron the script -Run the script every five minutes. +Run the script every five minutes. ``` -*/5 * * * * /root/gandi-live-dns-master/src/gandi-live-dns.py >/dev/null 2>&1 +*/5 * * * * /root/gandi-live-dns-master/src/gandi-live-dns.py >/dev/null 2>&1 ``` ### Limitations -The XML-RPC API has a limit of 30 requests per 2 seconds, so i guess it's safe to update 25 subdomains at once with the REST API. +The XML-RPC API has a limit of 30 requests per 2 seconds, so i guess it's safe to update 25 subdomains at once with the REST API. ### Upcoming Features @@ -133,7 +144,7 @@ The XML-RPC API has a limit of 30 requests per 2 seconds, so i guess it's safe t ### Inspiration -This DynDNS updater is inspired by https://github.com/jasontbradshaw/gandi-dyndns which worked very well +This DynDNS updater is inspired by https://github.com/jasontbradshaw/gandi-dyndns which worked very well with the classic DNS from Gandiv4 Website and their XML-RPC API. -Gandi has created a new API, i accidently switched to the new DNS Record System, so someone had to start a new updater. +Gandi has created a new API, I accidentally switched to the new DNS Record System, so someone had to start a new updater. diff --git a/src/example.config.py b/src/example.config.py index 7ac37fc..3c767fd 100644 --- a/src/example.config.py +++ b/src/example.config.py @@ -20,7 +20,7 @@ ''' api_endpoint = 'https://dns.api.gandi.net/api/v5' -#your domain with the subdomains in the zone file/UUID +#your domain with the subdomains in the zone file/UUID domain = 'mydomain.tld' #enter all subdomains to be updated, subdomains must already exist to be updated @@ -29,17 +29,19 @@ #300 seconds = 5 minutes ttl = '300' -''' -IP address lookup service +''' +IP address lookup service run your own external IP provider: + https://github.com/mpolden/ipd + -e.g. -+ https://ifconfig.co/ip +e.g. ++ https://[v4|v6].ifconfig.co/ip + http://ifconfig.me/ip + http://whatismyip.akamai.com/ + http://ipinfo.io/ip + many more ... ''' -ifconfig = 'choose_from_above_or_run_your_own' +#Leave blank to not update that protocol (v4 or v6) +ifconfigv4 = 'choose_from_above_or_run_your_own' +ifconfigv6 = 'choose_from_above_or_run_your_own' diff --git a/src/gandi-live-dns.py b/src/gandi-live-dns.py index 55e5757..9282e1d 100755 --- a/src/gandi-live-dns.py +++ b/src/gandi-live-dns.py @@ -8,7 +8,7 @@ https://www.gnu.org/licenses/gpl-3.0.html Created on 13 Aug 2017 -http://doc.livedns.gandi.net/ +http://doc.livedns.gandi.net/ http://doc.livedns.gandi.net/#api-endpoint -> https://dns.gandi.net/api/v5/ ''' @@ -18,19 +18,19 @@ def get_dynip(ifconfig_provider): - ''' 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 - ''' + ''' find out own IP 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 + ''' r = requests.get(ifconfig_provider) print 'Checking dynamic IP: ' , r._content.strip('\n') return r.content.strip('\n') def get_uuid(): - ''' + ''' find out ZONE UUID from domain Info on domain "DOMAIN" GET /domains/: - + ''' url = config.api_endpoint + '/domains/' + config.domain u = requests.get(url, headers={"X-Api-Key":config.api_secret}) @@ -42,16 +42,16 @@ def get_uuid(): print json_object['message'] exit() -def get_dnsip(uuid): +def get_dnsip(uuid, rrtype): ''' find out IP from first Subdomain DNS-Record List all records with name "NAME" and type "TYPE" in the zone UUID GET /zones//records//: - - The first subdomain from config.subdomain will be used to get + + The first subdomain from config.subdomain will be used to get the actual DNS Record IP ''' - url = config.api_endpoint+ '/zones/' + uuid + '/records/' + config.subdomains[0] + '/A' + url = config.api_endpoint+ '/zones/' + uuid + '/records/' + config.subdomains[0] + '/' + rrtype headers = {"X-Api-Key":config.api_secret} u = requests.get(url, headers=headers) if u.status_code == 200: @@ -59,12 +59,12 @@ def get_dnsip(uuid): print 'Checking IP from DNS Record' , config.subdomains[0], ':', json_object['rrset_values'][0].encode('ascii','ignore').strip('\n') return json_object['rrset_values'][0].encode('ascii','ignore').strip('\n') else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to get IP from subdomain', config.subdomains[0] + print 'Error: HTTP Status Code ', u.status_code, 'when trying to get IP from subdomain', config.subdomains[0] print json_object['message'] exit() -def update_records(uuid, dynIP, subdomain): - ''' update DNS Records for Subdomains +def update_records(uuid, dynIP, subdomain, rrtype): + ''' update DNS Records for Subdomains Change the "NAME"/"TYPE" record from the zone UUID PUT /zones//records//: curl -X PUT -H "Content-Type: application/json" \ @@ -73,7 +73,7 @@ def update_records(uuid, dynIP, subdomain): "rrset_values": [""]}' \ https://dns.gandi.net/api/v5/zones//records// ''' - url = config.api_endpoint+ '/zones/' + uuid + '/records/' + subdomain + '/A' + url = config.api_endpoint+ '/zones/' + uuid + '/records/' + subdomain + '/' + rrtype 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) @@ -83,7 +83,7 @@ def update_records(uuid, dynIP, subdomain): print 'Status Code:', u.status_code, ',', json_object['message'], ', IP updated for', subdomain return True else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to update IP from subdomain', subdomain + print 'Error: HTTP Status Code ', u.status_code, 'when trying to update IP from subdomain', subdomain print json_object['message'] exit() @@ -94,37 +94,49 @@ def main(force_update, verbosity): if verbosity: print "verbosity turned on - not implemented by now" - + #get zone ID from Account uuid = get_uuid() - - #compare dynIP and DNS IP - dynIP = get_dynip(config.ifconfig) - dnsIP = get_dnsip(uuid) - - if force_update: - print "Going to update/create the DNS Records for the subdomains" - for sub in config.subdomains: - update_records(uuid, dynIP, sub) - else: - if dynIP == dnsIP: - print "IP Address Match - no further action" + + #compare dynIP and DNS IP for IPv4 + if config.ifconfigv4 != "": + dynIPv4 = get_dynip(config.ifconfigv4) + dnsIPv4 = get_dnsip(uuid, "A") + + if force_update: + print "Going to update/create the DNS Records for the subdomains" + for sub in config.subdomains: + update_records(uuid, dynIPv4, sub, "A") else: - print "IP Address Mismatch - going to update the DNS Records for the subdomains with new IP", dynIP + if dynIPv4 == dnsIPv4: + print "IPv4 Address Match - no further action" + else: + print "IPv4 Address Mismatch - going to update the DNS Records for the subdomains with new IP", dynIPv4 + for sub in config.subdomains: + update_records(uuid, dynIPv4, sub, "A") + + #compare dynIP and DNS IP for IPv6 + if config.ifconfigv6 != "": + dynIPv6 = get_dynip(config.ifconfigv6) + dnsIPv6 = get_dnsip(uuid, "AAAA") + + if force_update: + print "Going to update/create the DNS Records for the subdomains" for sub in config.subdomains: - update_records(uuid, dynIP, sub) + update_records(uuid, dynIPv6, sub, "AAAA") + else: + if dynIPv6 == dnsIPv6: + print "IPv6 Address Match - no further action" + else: + print "IPv6 Address Mismatch - going to update the DNS Records for the subdomains with new IP", dynIPv6 + for sub in config.subdomains: + update_records(uuid, dynIPv6, sub, "AAAA") 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") args = parser.parse_args() - - - main(args.force, args.verbose) - - - - + main(args.force, args.verbose)