commit 81f5ef665cdb1429af5b649b727cace191458f83 Author: rune Date: Mon Mar 6 14:01:07 2023 +0100 Initial commit diff --git a/ddns.py b/ddns.py new file mode 100755 index 0000000..75224da --- /dev/null +++ b/ddns.py @@ -0,0 +1,309 @@ +#!/usr/bin/python3 +import sqlite3 +from pathlib import Path +from sqlite3 import Error +import urllib.request +import json +import logging +import argparse +import requests +import os +import time + +homefilepath = Path.home() +filepath = homefilepath.joinpath('.config/ddns') +database = filepath.joinpath('ddns.db') +app_version = '0.1' + + +def get_ip(): + cursor = conn.cursor() + cursor.execute('SELECT COUNT(ip4_server) FROM ipservers') + count = cursor.fetchone()[0] + if count != 0: + cursor.execute('SELECT ip4_server from ipservers') + server = cursor.fetchone() + server = server[0] + try: + current_ip = urllib.request.urlopen(server).read().decode('utf-8') + return current_ip + except Exception as e: + error = str(e) + return error + else: + return None + + + +def connect_database(): + Path(filepath).mkdir(parents=True, exist_ok=True) + conn = None + try: + conn = sqlite3.connect(database) + except Error as e: + print(e) + finally: + if conn: + c = conn.cursor() + c.execute('''CREATE TABLE IF NOT EXISTS apikey + (id integer NOT NULL PRIMARY KEY, + api text NOT NULL)''') + c.execute('''CREATE TABLE IF NOT EXISTS ipservers + (id integer NOT NULL PRIMARY KEY, + ip4_server text, + ip6_server text)''') + c.execute('''CREATE TABLE IF NOT EXISTS domains + (id integer PRIMARY KEY, + name text NOT NULL)''') + c.execute('''CREATE TABLE IF NOT EXISTS subdomains + (id integer PRIMARY KEY, + main_id integer NOT NULL, + name text)''') + + return conn + + +def get_api(): + cursor = conn.cursor() + cursor.execute('SELECT COUNT(*) FROM apikey') + count = cursor.fetchone()[0] + if count == 0: + return None + else: + cursor.execute('SELECT * FROM apikey') + rows = cursor.fetchone() + return rows[1] + + +def api(api_value): + cursor = conn.cursor() + cursor.execute('SELECT COUNT(*) FROM apikey') + count = cursor.fetchone()[0] + if count == 0: + cursor.execute('INSERT INTO apikey values(?,?)', (1, api_value)) + print('Your API key has been added.') + else: + cursor.execute('UPDATE apikey SET api = ? WHERE id = 1',(api_value,)) + print('Your API key has been updated.') + conn.commit() + + +def add_domian(domain): + cursor = conn.cursor() + cursor.execute('SELECT COUNT(*) FROM domains WHERE name like ?',(domain,)) + count = cursor.fetchone()[0] + if count == 0: + cursor.execute('INSERT INTO domains values(?,?)', (None, domain,)) + print('The domain %s has been added to the DB' % (domain)) + conn.commit() + else: + print('Error: Domain name (%s) already in database!' % (domain)) + + + +def add_subdomain(sub,top): + apikey = get_api() + if apikey == None: + print("Missing APIkey. Please add one!") + else: + ip = get_ip() + if ip == None or 'urlopen error' in ip: + print('Failed to get public IP. Do you have a typo in your URI? Error %s' % (ip)) + else: + cursor = conn.cursor() + cursor.execute('SELECT COUNT(*) FROM domains WHERE name like ?',(top,)) + count = cursor.fetchone()[0] + if count == 0: + print('Error: Top domain %s does not exist in the DB. Please add it with ddns -t %s ' % (top,top)) + else: + cursor.execute('SELECT id FROM domains WHERE name LIKE ?',(top,)) + topdomain_id = cursor.fetchone() + topdomain_id = topdomain_id[0] + cursor.execute('SELECT count(*) FROM subdomains WHERE main_id LIKE ? AND name like ?',(topdomain_id,sub,)) + count = cursor.fetchone()[0] + if count != 0: + print('The subdomain %s already exists for the domain %s.' % (sub,top)) + else: + data = {'name': sub,'data': ip,'type': "A",'ttl': 3600} + headers = {'Authorization': 'Bearer ' + apikey, "Content-Type": "application/json"} + response = requests.post('https://api.digitalocean.com/v2/domains/' + top + '/records', + data=json.dumps(data), headers=headers) + if str(response) == '': + if response != 'Fail': + response_data = response.json() + domainid = str(response_data['domain_record']['id']) + cursor.execute('INSERT INTO subdomains values(?,?,?)',(domainid,topdomain_id,sub,)) + conn.commit() + print('The subdomain %s for the domain %s has been added.' % (sub,top)) + else: + return 'Error: %s ' % (str(response)) + + +def show_all_top_domains(): + cursor = conn.cursor() + apikey = get_api() + if apikey != None: + req = urllib.request.Request('https://api.digitalocean.com/v2/domains/?per_page=200') + req.add_header('Content-Type', 'application/json') + req.add_header('Authorization', 'Bearer ' + apikey) + current = urllib.request.urlopen(req) + remote = current.read().decode('utf-8') + remoteData = json.loads(remote) + print('Domains in database are marked with a [*]') + print('================================================') + for k in remoteData["domains"]: + cursor.execute('SELECT COUNT(*) FROM domains WHERE name like ?',(k['name'],)) + count = cursor.fetchone()[0] + if count != 0: + print('Name : '+k['name']+ ' *') + else: + print('Name : '+k['name']) + + else: + print("Missing APIkey. Please add one!") + + + +def list_sub_domains(domain): + # TODO: Finish this :) + apikey = get_api() + cursor = conn.cursor() + if apikey == None: + print("Missing APIkey. Please add one!") + else: + cursor.execute('SELECT COUNT(*) FROM domains WHERE name LIKE ?',(domain,)) + count = cursor.fetchone()[0] + if count == 0: + print("Error: No such domain. Check spelling or use ddns -d to show all top domains.") + else: + cursor.execute('SELECT * FROM domains WHERE name LIKE ?', (domain,)) + row = cursor.fetchone() + print(row) + + print("List -> " + domain) + + +def domaininfo(domain): + print("domaininfo") + + +def show_current_info(): + ipserver = None + API = get_api() + cursor = conn.cursor() + cursor.execute('SELECT COUNT(ip4_server) FROM ipservers') + count = cursor.fetchone()[0] + if count == 0: + ipserver = 'No IP resolvers in DB' + else: + cursor.execute('SELECT * FROM ipservers') + ipserver = cursor.fetchall()[0][1] + + if API == None: + API = 'API key not stored in DB' + + print('Current info.') + print('==========================================') + print('API key : %s' % (API)) + print('IP resolver : %s' % (ipserver)) + print('App version : %s' % (app_version)) + print('') + print('IPv6 is not supported and not listed here.') + + +def ip_server(ipserver, ip_type): + cursor = conn.cursor() + if ip_type == '4': + cursor.execute('SELECT COUNT(ip4_server) FROM ipservers') + count = cursor.fetchone()[0] + if count == 0: + cursor.execute('INSERT INTO ipservers values(?,?,?)', (None, ipserver,None)) + conn.commit() + print('New IP resolver (%s) for ipv%s added.' % (ipserver, ip_type)) + else: + cursor.execute('UPDATE ipservers SET ip4_server = ? WHERE id = 1',(ipserver,)) + print('IP resolver (%s) for ipv%s updated.' % (ipserver, ip_type)) + conn.commit() + elif ip_type == '6': + cursor.execute('SELECT COUNT(ip6_server) FROM ipservers') + count = cursor.fetchone()[0] + if count == 0: + cursor.execute('INSERT INTO ipservers values(?,?,?)', (None, None,ipserver)) + conn.commit() + print('New IP resolver (%s) for ipv%s added. \n\r This IP version is not supported.' % (ipserver, ip_type)) + else: + cursor.execute('UPDATE ipservers SET ip6_server = ? WHERE id = 1',(ipserver,)) + print('IP resolver (%s) for ipv%s updated. \n\r This IP version is not supported.' % (ipserver, ip_type)) + conn.commit() + + + +def updateip(): + print("updateip") + + + + +# Commandline arguments +conn = connect_database() + +parser = argparse.ArgumentParser(prog='ddns', + description='Application to use domains from DigitalOcean account as dynamic DNS domain(s).\r\nThe app only supports IP4. IPv6 is planned for a later release!', + epilog='Making Selfhosting easier...') + +parser.add_argument('-a', '--api', help='Add/Change API key.', + nargs=1, metavar=('APIkey'), required=False, action="append") + +parser.add_argument('-l', '--list', help='List subdomains for supplied domain.', + nargs=1, metavar=('domain'), required=False, action="append") + +parser.add_argument('-d', '--domains', help='List top domains on your DigitalOcean account.', + required=False, action="store_true") + +parser.add_argument('-c', '--current', help='List the current IP address for the sub-domain given', + required=False, nargs=1, action="append") + +parser.add_argument('-t', '--top', help='Add a new domain from your DigitalOcean account to use as a dynamic DNS domain', + required=False, nargs=1, metavar=('domain'), action='append') + +parser.add_argument('-s', '--sub', help='Add a new subdomain to dynamic DNS DO domain', + required=False, nargs=2, metavar=('subdomain', 'domain'), action='append') + +parser.add_argument('-i', '--info', help='Show current config info', + required=False, action='store_true') + +parser.add_argument('-p', '--ipserver', help='Set IP server lookup to use. Indicate 4 or 6 for IP type.', + required=False, nargs=2, metavar=('ip4.iurl.no', '4'), action="append") +args = vars(parser.parse_args()) + +if args['list']: + list_sub_domains(args['list'][0][0]) +elif args['domains']: + show_all_top_domains() +elif args['current']: + domaininfo(args['current'][0][0]) +elif args['top']: + add_domian(args['top'][0][0]) +elif args['sub']: + add_subdomain(args['sub'][0][0],args['sub'][0][1]) + #add_subdomain(args['sub'][0][0]) +elif args['info']: + show_current_info() +elif args['ipserver']: + ip_server(args['ipserver'][0][0],args['ipserver'][0][1]) +elif args['api']: + api(args['api'][0][0]) +else: + get_ip() + # get_api() + + + + + + + + + + +