diff --git a/README.md b/README.md index 9e58c25..76e3485 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,40 @@ Example Usage: -------------- The PHP script has been designed to be called as a command line tool. Config is passed into it in the form of CLI parameters, for example: - php updater.php accessToken domain record recordtype + php updater.php accessToken domain record recordType - python updater.py accessToken domain record recordtype + python updater.py accessToken domain record recordType where 'accessToken' is your ['Personal Access Token'](https://cloud.digitalocean.com/settings/applications), 'domain' is the domain name you want to update (e.g: joebloggs.com), 'record' is the value of the record you want to update (e.g: home), and 'recordtype' is either A or AAAA. +Run Continuously / Cron Style: +----------------------------- + +You can run this script continuously (every X number of seconds) by calling it: + + # If you wan to run it every 5 minutes + python updater.py accessToken domain record recordType --run-every 300 + +This mode is perfect if you want to run inside a Docker container, where there is no cron by default. + + TOKEN={your token} + DOMAIN={your domain} + RECORD={your record} + RTYPE=A + TIMEOUT=60 + ARGS="$TOKEN $DOMAIN $RECORD $RTYPE --run-every $TIMEOUT" + + docker run \ + -it \ + --rm \ + --name do-ddns-updater \ + -v "$PWD":/usr/src/app \ + -w /usr/src/app \ + python:3 python updater.py $ARGS + + Thanks to: ---------- @surfer190, @nickwest, @gnoeley, @ryanwcraig, @c17r for adding additional features, testing code and feedback. diff --git a/updater.py b/updater.py index 365a530..9c88fa0 100755 --- a/updater.py +++ b/updater.py @@ -66,12 +66,13 @@ def get_external_ip(expected_rtype): external_ip = get_url(CHECKIP_URL).rstrip() ip = ipaddress.ip_address(external_ip) if (ip.version == 4 and expected_rtype != 'A') or (ip.version == 6 and expected_rtype != 'AAAA'): - raise Exception('Expected Rtype {} but got {}'.format(expected_rtype, external_ip)) + raise Exception("Expected Rtype {} but got {}".format(expected_rtype, external_ip)) + debug("Obtaining the current external IP address: {}", external_ip) return external_ip def get_domain(name, token): - output('Fetching Domain ID for: {}', name) + debug("Fetching Domain ID for: {}", name) url = "%s/domains" % (APIURL) while True: @@ -89,11 +90,11 @@ def get_domain(name, token): else: break - raise Exception("Could not find domain: %s" % name) + raise Exception(f"Could not find domain: {name}") def get_record(domain, name, rtype, token): - output("Fetching Record ID for: {}", name) + debug("Fetching Record ID for: {}", name) url = "%s/domains/%s/records" % (APIURL, domain['name']) while True: @@ -111,11 +112,11 @@ def get_record(domain, name, rtype, token): else: break - raise Exception("Could not find record: %s" % name) + raise Exception(f"Could not find record: {name}") def set_record_ip(domain, record, ipaddr, token): - print("Updating record {}.{} to {}".format(record['name'], domain['name'], ipaddr)) + debug("Updating record {}.{} to {}", record['name'], domain['name'], ipaddr) url = "%s/domains/%s/records/%s" % (APIURL, domain['name'], record['id']) data = json.dumps({'data': ipaddr}).encode('utf-8') @@ -123,39 +124,52 @@ def set_record_ip(domain, record, ipaddr, token): result = json.loads(put_url(url, data, headers)) if result['domain_record']['data'] == ipaddr: - print("Success") + debug("Record {}.{} sucessfully updated to {}", record['name'], domain['name'], ipaddr) + else: + raise Exception(f"Could not set {record['name']}.{domain['name']} to {ipaddr}") def output(line, *args): - check = getattr(output, 'suppress', False) - if check: + quiet = getattr(output, 'quiet', False) + if quiet: return - print(line.format(*args)) + print(f"[{datetime.now()}]", line.format(*args)) + +def debug(line, *args): + debugEnable = getattr(debug, 'debug', False) + if not debugEnable: + return + output(f"DEBUG - {line}", *args) def process_args(): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(description='Updates DNS records in Digital Ocean') parser.add_argument("token") parser.add_argument("domain") parser.add_argument("record") parser.add_argument("rtype", choices=['A', 'AAAA']) - parser.add_argument("-q", "--quiet", action="store_true", help='Only display output on IP change') - parser.add_argument("-ecoc", "--error-code-on-change", action="store_true", help='return Error Code 1 on IP change') + parser.add_argument("-q", "--quiet", + action="store_true", + help="Only display output on IP change") + parser.add_argument("-d", "--debug", + action="store_true", + help="Shows debug messages") + parser.add_argument("-ecoc", "--error-code-on-change", + action="store_true", + help="Return Error Code 1 on IP change") + parser.add_argument("-re", "--run-every", + help="Run every number of seconds") return parser.parse_args() -def run(): +def run(args): try: - args = process_args() - if args.quiet: - output.suppress = True - - output("Update {}.{}: {}", args.record, args.domain, datetime.now()) + debug("Update {}.{} record type {}", args.record, args.domain, args.rtype) ipaddr = get_external_ip(args.rtype) domain = get_domain(args.domain, args.token) record = get_record(domain, args.record, args.rtype, args.token) if record['data'] == ipaddr: - output("Records {}.{} already set to {}.", record['name'], domain['name'], ipaddr) + output("Record {}.{} already set to {}", record['name'], domain['name'], ipaddr) return 0 set_record_ip(domain, record, ipaddr, args.token) @@ -163,9 +177,24 @@ def run(): return ec except (Exception) as err: - print("Error: ", err, file=sys.stderr) + print(f"[{datetime.now()}] ERROR - {err}", file=sys.stderr) return -1 if __name__ == '__main__': - sys.exit(run()) + args = process_args() + if args.debug: + debug.debug = True + + if args.quiet: + output.quiet = True + + if args.run_every: + timeout = int(args.run_every) + output("Running this script continuously every {} seconds", timeout) + starttime=time.time() + while True: + run(args) + time.sleep(timeout - ((time.time() - starttime) % timeout)) + else: + sys.exit(run(args))