2023-03-06 14:01:07 +01:00
#!/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
2023-03-19 17:23:11 +01:00
from datetime import datetime
2023-03-07 17:49:38 +01:00
from string import ascii_letters , digits
from rich import print
from argparse import RawTextHelpFormatter
2023-03-06 14:01:07 +01:00
homefilepath = Path . home ()
filepath = homefilepath . joinpath ( '.config/ddns' )
database = filepath . joinpath ( 'ddns.db' )
2023-03-20 09:16:06 +01:00
logfile = filepath . joinpath ( 'ddns.log' )
2023-04-05 10:44:00 +02:00
logging . basicConfig ( filename = logfile , level = logging . INFO , format = ' %(message)s ' )
2023-04-17 10:34:57 +02:00
app_version = '0.5.2'
2023-03-06 14:01:07 +01:00
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 )
2023-03-23 17:42:15 +01:00
logging . error ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Error : ' + str ( e ))
2023-03-06 14:01:07 +01:00
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 :
2023-03-23 17:42:15 +01:00
logging . error ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Error : ' + str ( e ))
2023-03-06 14:01:07 +01:00
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,
2023-03-19 17:23:11 +01:00
ip4_server text NOT NULL,
2023-03-06 14:01:07 +01:00
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,
2023-03-19 17:23:11 +01:00
name text NOT NULL,
2023-03-07 17:49:38 +01:00
current_ip4 text NOT NULL,
current_ip6 text NULL)''' )
2023-03-06 14:01:07 +01:00
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 ))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : API key added' )
2023-03-06 14:01:07 +01:00
print ( 'Your API key has been added.' )
else :
cursor . execute ( 'UPDATE apikey SET api = ? WHERE id = 1' ,( api_value ,))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : API key updated' )
2023-03-06 14:01:07 +01:00
print ( 'Your API key has been updated.' )
conn . commit ()
def add_domian ( domain ):
2023-03-07 17:49:38 +01:00
apikey = get_api ()
2023-03-06 14:01:07 +01:00
cursor = conn . cursor ()
cursor . execute ( 'SELECT COUNT(*) FROM domains WHERE name like ?' ,( domain ,))
count = cursor . fetchone ()[ 0 ]
2023-03-07 17:49:38 +01:00
if count != 0 :
print ( '[red]Error:[/red] Domain name ( %s ) already in database!' % ( domain ))
2023-03-06 14:01:07 +01:00
else :
2023-03-07 17:49:38 +01:00
if apikey != None :
headers = { 'Authorization' : 'Bearer ' + apikey , "Content-Type" : "application/json" }
response = requests . get ( 'https://api.digitalocean.com/v2/domains/' + domain , headers = headers )
response_data = response . json ()
if 'id' in response_data :
print ( '[red]Error: [/red]The domain does not exist in your DigitalOcean account. \n Please add the domain from your control panel [b]https://cloud.digitalocean.com/networking/domains/[/b]' )
else :
cursor . execute ( 'INSERT INTO domains values(?,?)' , ( None , domain ,))
print ( 'The domain [b] %s [/b] has been added to the DB' % ( domain ))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Domain %s added' % ( domain ))
2023-03-07 17:49:38 +01:00
conn . commit ()
2023-03-06 14:01:07 +01:00
2023-03-07 17:49:38 +01:00
def add_subdomain ( domain ):
2023-03-28 11:41:06 +02:00
now = datetime . now () . strftime ( "%Y-%m- %d %H:%M" )
2023-04-05 10:44:00 +02:00
if set ( domain ) . difference ( ascii_letters + '.' + digits + '-' + '@' ):
2023-03-07 17:49:38 +01:00
print ( '[red]Error:[/red] Give the domain name in simple form e.g. [b]test.domain.com[/b]' )
2023-03-06 14:01:07 +01:00
else :
2023-03-07 17:49:38 +01:00
parts = domain . split ( '.' )
2023-04-15 17:11:35 +02:00
if len ( parts ) > 3 :
top = parts [ 1 ] + '.' + parts [ 2 ] + '.' + parts [ 3 ]
sub = parts [ 0 ]
else :
sub = parts [ 0 ]
top = parts [ 1 ] + '.' + parts [ 2 ]
2023-03-07 17:49:38 +01:00
apikey = get_api ()
if apikey == None :
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
2023-03-06 14:01:07 +01:00
else :
2023-03-07 17:49:38 +01:00
ip = get_ip ()
if ip == None or 'urlopen error' in ip :
print ( '[red]Error:[/red] Failed to get public IP. Do you have a typo in your URI? [red]Error %s .[/red]' % ( ip ))
else :
cursor = conn . cursor ()
cursor . execute ( 'SELECT COUNT(*) FROM domains WHERE name like ?' ,( top ,))
count = cursor . fetchone ()[ 0 ]
if count == 0 :
print ( '[red]Error:[/red] Top domain [bold] %s [/bold] does not exist in the DB. Please add it with [i]ddns -t %s [/i].' % ( top , top ))
else :
2023-04-15 17:11:35 +02:00
cursor . execute ( 'SELECT id,name FROM domains WHERE name LIKE ?' ,( top ,))
topdomain = cursor . fetchone ()
topdomain_id = topdomain [ 0 ]
topdomain_name = topdomain [ 1 ]
2023-03-07 17:49:38 +01:00
cursor . execute ( 'SELECT count(*) FROM subdomains WHERE main_id LIKE ? AND name like ?' ,( topdomain_id , sub ,))
count = cursor . fetchone ()[ 0 ]
if count != 0 :
print ( '[red]Error:[/red] [bold] %s [/bold] already exists.' % ( domain ))
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 ) == '<Response [201]>' :
if response != 'Fail' :
response_data = response . json ()
domainid = str ( response_data [ 'domain_record' ][ 'id' ])
2023-04-05 10:44:00 +02:00
cursor . execute ( 'INSERT INTO subdomains values(?,?,?,?,?,?,?,?,?)' ,( domainid , topdomain_id , sub , ip , None , now , now , now , 1 ,))
2023-03-07 17:49:38 +01:00
conn . commit ()
print ( 'The domain %s has been added.' % ( domain ))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : subdomain %s added' % ( domain ))
2023-03-07 17:49:38 +01:00
else :
return '[red]Error: %s [/red]' % ( str ( response ))
def remove_subdomain ( domain ):
2023-04-05 10:44:00 +02:00
if set ( domain ) . difference ( ascii_letters + '.' + digits + '-' + '@' ):
2023-03-07 17:49:38 +01:00
print ( '[red]Error:[/red] Give the domain name in simple form e.g. [b]test.domain.com[/b]' )
else :
parts = domain . split ( '.' )
2023-04-15 17:11:35 +02:00
if len ( parts ) > 3 :
top = parts [ 1 ] + '.' + parts [ 2 ] + '.' + parts [ 3 ]
sub = parts [ 0 ]
else :
sub = parts [ 0 ]
top = parts [ 1 ] + '.' + parts [ 2 ]
2023-04-17 10:55:01 +02:00
longtop = sub + '.' + top
2023-03-07 17:49:38 +01:00
cursor = conn . cursor ()
2023-04-17 10:55:01 +02:00
cursor . execute ( 'SELECT COUNT(*) FROM domains WHERE name like ? or name like ?' ,( top , longtop ,))
2023-03-07 17:49:38 +01:00
count = cursor . fetchone ()[ 0 ]
if count == 0 :
print ( '[red]Error:[/red] Top domain [bold] %s [/bold] does not exist in the DB. So I \' m giving up!.' % ( top ))
else :
2023-04-17 10:55:01 +02:00
cursor . execute ( 'SELECT COUNT(*) FROM subdomains WHERE name like ? and main_id=(SELECT id from domains WHERE name like ? or name like ?)' ,( sub , top , longtop ,))
2023-03-06 14:01:07 +01:00
count = cursor . fetchone ()[ 0 ]
if count == 0 :
2023-03-07 17:49:38 +01:00
print ( '[red]Error:[/red] Domain [bold] %s [/bold] does not exist in the DB. So I \' m giving up!.' % ( domain ))
2023-03-06 14:01:07 +01:00
else :
2023-03-07 17:49:38 +01:00
apikey = get_api ()
if apikey == None :
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
2023-03-06 14:01:07 +01:00
else :
2023-04-17 10:55:01 +02:00
cursor . execute ( 'SELECT id FROM subdomains WHERE name like ? and main_id=(SELECT id from domains WHERE name like ? or name like ?)' ,( sub , top , longtop ,))
2023-03-07 17:49:38 +01:00
subdomain_id = str ( cursor . fetchone ()[ 0 ])
2023-03-06 14:01:07 +01:00
headers = { 'Authorization' : 'Bearer ' + apikey , "Content-Type" : "application/json" }
2023-03-07 17:49:38 +01:00
response = requests . delete ( 'https://api.digitalocean.com/v2/domains/' + top + '/records/' + subdomain_id , headers = headers )
if str ( response ) == '<Response [204]>' :
cursor . execute ( 'DELETE from subdomains where id=?' ,( subdomain_id ,))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Subdomain %s removed' % ( domain ))
2023-03-07 17:49:38 +01:00
conn . commit ()
2023-03-06 14:01:07 +01:00
else :
2023-03-07 17:49:38 +01:00
print ( '[red]Error: [/red]An error occurred! Please try again later!' )
2023-03-06 14:01:07 +01:00
2023-04-05 10:44:00 +02:00
def edit_subdomain ( domain ):
if set ( domain ) . difference ( ascii_letters + '.' + digits + '-' + '@' ):
print ( '[red]Error:[/red] Give the domain name in simple form e.g. [b]test.domain.com[/b]' )
else :
parts = domain . split ( '.' )
2023-04-15 17:11:35 +02:00
if len ( parts ) > 3 :
top = parts [ 1 ] + '.' + parts [ 2 ] + '.' + parts [ 3 ]
sub = parts [ 0 ]
else :
sub = parts [ 0 ]
top = parts [ 1 ] + '.' + parts [ 2 ]
2023-04-17 10:55:01 +02:00
longtop = sub + '.' + top
2023-04-05 10:44:00 +02:00
cursor = conn . cursor ()
2023-04-17 10:55:01 +02:00
cursor . execute ( 'SELECT COUNT(*) FROM domains WHERE name like ? or name like ?' ,( top , longtop ,))
2023-04-05 10:44:00 +02:00
count = cursor . fetchone ()[ 0 ]
if count == 0 :
print ( '[red]Error:[/red] Top domain [bold] %s [/bold] does not exist in the DB. So I \' m giving up!.' % ( top ))
else :
2023-04-17 11:02:08 +02:00
cursor . execute ( 'SELECT COUNT(*) FROM subdomains WHERE name like ? and main_id=(SELECT id from domains WHERE name like ? or name like ?)' ,( sub , top , longtop ))
2023-04-05 10:44:00 +02:00
count = cursor . fetchone ()[ 0 ]
if count == 0 :
print ( '[red]Error:[/red] Domain [bold] %s [/bold] does not exist in the DB. So I \' m giving up!.' % ( domain ))
else :
apikey = get_api ()
if apikey == None :
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
else :
2023-04-17 10:55:01 +02:00
cursor . execute ( 'SELECT id,active FROM subdomains WHERE name like ? and main_id=(SELECT id from domains WHERE name like ? or name like ?)' ,( sub , top , longtop ))
2023-04-05 10:44:00 +02:00
domain_info = cursor . fetchone ()
subdomain_id = str ( domain_info [ 0 ])
status = domain_info [ 1 ]
if status == 1 :
status = 0
else :
status = 1
cursor . execute ( 'UPDATE subdomains SET active = ? WHERE id = ?' ,( status , subdomain_id ,))
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Status for domain %s changed' % ( domain ))
print ( 'Status for domain %s changed' % ( domain ))
conn . commit ()
2023-03-06 14:01:07 +01:00
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 :
2023-03-07 17:49:38 +01:00
print ( 'Name : [bold]' + k [ 'name' ] + ' [*][/bold]' )
2023-03-06 14:01:07 +01:00
else :
print ( 'Name : ' + k [ 'name' ])
else :
2023-03-07 17:49:38 +01:00
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
2023-03-06 14:01:07 +01:00
def list_sub_domains ( domain ):
apikey = get_api ()
cursor = conn . cursor ()
if apikey == None :
2023-03-07 17:49:38 +01:00
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
2023-03-06 14:01:07 +01:00
else :
cursor . execute ( 'SELECT COUNT(*) FROM domains WHERE name LIKE ?' ,( domain ,))
count = cursor . fetchone ()[ 0 ]
if count == 0 :
2023-03-07 17:49:38 +01:00
print ( "[red]Error: [/red]No such domain. Check spelling or use ddns -d to show all top domains." )
2023-03-06 14:01:07 +01:00
else :
2023-03-28 12:12:04 +02:00
print ( ' \n\n Current sub domains for [b] %s [/b] \n\n ' % ( domain ))
2023-04-05 10:44:00 +02:00
print ( 'Domain \t\t\t\t Created \t\t\t Updated \t\t\t Checked \t\t\t Active' )
print ( '==================================================================================================================' )
2023-03-07 17:49:38 +01:00
cursor . execute ( 'SELECT id FROM domains WHERE name LIKE ?' , ( domain ,))
topdomain_id = cursor . fetchone ()[ 0 ]
cursor . execute ( 'SELECT COUNT(*) FROM subdomains WHERE main_id LIKE ?' ,( topdomain_id ,))
count = cursor . fetchone ()[ 0 ]
if count == 0 :
print ( '[red]Error:[/red] No sub domains for [b] %s [/b]' % ( domain ))
else :
2023-04-05 10:44:00 +02:00
cursor . execute ( 'SELECT name,last_updated,last_checked,created,active FROM subdomains WHERE main_id LIKE ?' ,( topdomain_id ,) )
2023-03-07 17:49:38 +01:00
subdomains = cursor . fetchall ()
for i in subdomains :
2023-04-05 10:44:00 +02:00
if i [ 4 ] == 1 :
active = 'True'
else :
active = 'False'
2023-03-20 09:16:06 +01:00
topdomain = i [ 0 ] + '.' + domain
topdomain = " {:<25} " . format ( topdomain )
2023-04-05 10:44:00 +02:00
print ( topdomain + ' \t ' + i [ 3 ] + ' \t ' + i [ 1 ] + ' \t ' + i [ 2 ] + ' \t ' + active )
2023-03-07 17:49:38 +01:00
print ( ' \n ' )
def list_do_sub_domains ( domain ):
apikey = get_api ()
cursor = conn . cursor ()
if apikey == None :
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
else :
req = urllib . request . Request ( 'https://api.digitalocean.com/v2/domains/' + domain + '/records?type="A"/?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 your DigitalOcean account not in ddns DB for [b] %s [/b]' % ( domain ))
2023-03-07 19:05:02 +01:00
print ( '===================================================================' )
2023-03-07 17:49:38 +01:00
for k in remoteData [ "domain_records" ]:
if k [ 'type' ] == 'A' :
cursor . execute ( 'SELECT COUNT(*) FROM subdomains WHERE id like ?' ,( str ( k [ 'id' ]),))
count = cursor . fetchone ()[ 0 ]
if count == 0 :
2023-03-20 09:16:06 +01:00
print ( k [ 'name' ] + '.' + domain + ' \t\t ID : ' + str ( k [ 'id' ]))
2023-03-07 17:49:38 +01:00
2023-03-06 14:01:07 +01:00
def domaininfo ( domain ):
2023-03-07 17:49:38 +01:00
apikey = get_api ()
local_ip = get_ip ()
cursor = conn . cursor ()
2023-04-05 10:44:00 +02:00
if set ( domain ) . difference ( ascii_letters + '.' + digits + '@' + '-' ):
2023-03-07 17:49:38 +01:00
print ( '[red]Error:[/red]. Give the domain name in simple form e.g. [bold]test.domain.com[/bold]' )
else :
parts = domain . split ( '.' )
2023-04-17 10:34:57 +02:00
if len ( parts ) > 3 :
top = parts [ 1 ] + '.' + parts [ 2 ] + '.' + parts [ 3 ]
sub = parts [ 0 ]
else :
sub = parts [ 0 ]
top = parts [ 1 ] + '.' + parts [ 2 ]
cursor . execute ( 'SELECT id FROM domains WHERE name like ?' , ( top ,))
2023-03-07 17:49:38 +01:00
domainid = cursor . fetchone ()[ 0 ]
cursor . execute ( 'SELECT * FROM subdomains WHERE main_id like ?' , ( domainid ,))
domains = cursor . fetchall ()
if local_ip != domains [ 0 ][ 3 ]:
localip = '[red] %s [/red]' % ( local_ip )
else :
localip = local_ip
print ( 'The domain [bold] %s [/bold] has the IP [bold] %s [/bold]. Your public IP is [bold] %s [/bold]' % ( domain , domains [ 0 ][ 3 ], localip ))
2023-03-06 14:01:07 +01:00
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 :
2023-03-07 17:49:38 +01:00
ipserver = '[red]Error:[/red] No IP resolvers in DB'
2023-03-06 14:01:07 +01:00
else :
cursor . execute ( 'SELECT * FROM ipservers' )
2023-03-29 09:08:08 +02:00
ipservers = cursor . fetchall ()
ip4server = ipservers [ 0 ][ 1 ]
ip6server = ipservers [ 0 ][ 2 ]
2023-03-06 14:01:07 +01:00
if API == None :
2023-03-07 17:49:38 +01:00
API = '[red]Error:[/red] API key not stored in DB'
cursor . execute ( 'SELECT COUNT(*) FROM domains' )
topdomains = cursor . fetchone ()[ 0 ]
cursor . execute ( 'SELECT COUNT(*) FROM subdomains' )
subdomains = cursor . fetchone ()[ 0 ]
2023-03-06 14:01:07 +01:00
2023-03-23 17:42:15 +01:00
print ( ' \n [b]ddns[/b] - a DigitalOcean dynamic DNS solution.' )
2023-03-08 10:28:09 +01:00
print ( '===================================================' )
print ( 'API key : [b] %s [/b]' % ( API ))
2023-03-29 09:08:08 +02:00
print ( 'IP v4 resolver : [b] %s [/b]' % ( ip4server ))
print ( 'IP v6 resolver : [b] %s [/b]' % ( ip6server ))
2023-03-20 09:16:06 +01:00
print ( 'Logfile : [b] %s [/b]' % ( logfile ))
2023-03-08 10:28:09 +01:00
print ( 'Top domains : [b] %s [/b]' % ( topdomains ))
print ( 'sub domains : [b] %s [/b]' % ( subdomains ))
2023-03-06 14:01:07 +01:00
print ( '' )
2023-03-08 10:28:09 +01:00
print ( 'App version : [b] %s [/b] (https://gitlab.pm/rune/ddns)' % ( app_version ))
print ( '' )
print ( '[i]IPv6 is not supported and not listed here.[/i]' )
2023-03-06 14:01:07 +01:00
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 ))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : IP resolver ( %s ) for ipv %s updated.' % ( ipserver , ip_type ))
2023-03-06 14:01:07 +01:00
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 ))
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : IP resolver ( %s ) for ipv %s updated.' % ( ipserver , ip_type ))
2023-03-06 14:01:07 +01:00
conn . commit ()
2023-03-20 09:16:06 +01:00
def updateip ( force ):
2023-03-07 17:49:38 +01:00
apikey = get_api ()
current_ip = get_ip ()
cursor = conn . cursor ()
cursor . execute ( 'SELECT COUNT(*) FROM subdomains' )
count = cursor . fetchone ()[ 0 ]
2023-03-29 09:08:08 +02:00
now = datetime . now () . strftime ( " %d -%m-%Y %H:%M" )
2023-03-23 17:42:15 +01:00
updated = None
2023-03-07 17:49:38 +01:00
if count == 0 :
print ( '[red]Error: [/red]There are no dynamic domains active.' \
' Start by adding a new domain with [i]ddns -s test.example.com[/i]' )
else :
2023-04-05 10:44:00 +02:00
cursor . execute ( 'SELECT id,active FROM subdomains' )
2023-03-07 17:49:38 +01:00
rows = cursor . fetchall ()
for i in rows :
cursor . execute ( 'SELECT name FROM domains WHERE id like (SELECT main_id from subdomains WHERE id = ?)' ,( i [ 0 ],))
2023-04-05 10:44:00 +02:00
domain_info = cursor . fetchone ()
domain_name = str ( domain_info [ 0 ])
domain_status = i [ 1 ]
2023-03-07 17:49:38 +01:00
subdomain_id = str ( i [ 0 ])
2023-03-20 09:16:06 +01:00
# Chek if an update is required
2023-04-05 10:44:00 +02:00
if domain_status == 1 :
req = urllib . request . Request ( 'https://api.digitalocean.com/v2/domains/' + domain_name + '/records/' + subdomain_id )
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 )
remoteIP4 = remoteData [ 'domain_record' ][ 'data' ]
domainname = str ( remoteData [ 'domain_record' ][ 'name' ])
if remoteIP4 != current_ip or force == True and domain_status == 1 :
updated = True
data = { 'type' : 'A' , 'data' : current_ip }
headers = { 'Authorization' : 'Bearer ' + apikey , "Content-Type" : "application/json" }
response = requests . patch ( 'https://api.digitalocean.com/v2/domains/' + domain_name + '/records/' + subdomain_id , data = json . dumps ( data ), headers = headers )
if str ( response ) != '<Response [200]>' :
logging . error ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Error updating (' + str ( domain_name ) + ') : ' + str ( response . content ))
else :
cursor . execute ( 'UPDATE subdomains SET current_ip4=? WHERE id = ?' ,( current_ip , subdomain_id ,))
cursor . execute ( 'UPDATE subdomains SET last_updated=? WHERE id = ?' ,( now , subdomain_id ,))
cursor . execute ( 'UPDATE subdomains SET last_checked=? WHERE id = ?' ,( now , subdomain_id ,))
conn . commit ()
2023-03-20 09:16:06 +01:00
else :
2023-03-23 17:42:15 +01:00
cursor . execute ( 'UPDATE subdomains SET last_checked=? WHERE id = ?' ,( now , subdomain_id ,))
2023-03-20 09:16:06 +01:00
conn . commit ()
2023-03-23 17:42:15 +01:00
if updated == None :
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : No updated necessary' )
else :
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Updates done. Use ddns -l domain.com to check domain' )
2023-03-06 14:01:07 +01:00
2023-03-07 17:49:38 +01:00
def local_add_subdomain ( domain , domainid ):
2023-03-29 09:08:08 +02:00
now = datetime . now () . strftime ( "%Y-%m- %d %H:%M" )
2023-04-05 10:44:00 +02:00
if set ( domain ) . difference ( ascii_letters + '.' + digits + '-' + '@' ):
2023-03-07 17:49:38 +01:00
print ( '[red]Error:[/red] Give the domain name in simple form e.g. [b]test.domain.com[/b]' )
else :
parts = domain . split ( '.' )
2023-04-15 17:11:35 +02:00
if len ( parts ) > 3 :
top = parts [ 1 ] + '.' + parts [ 2 ] + '.' + parts [ 3 ]
sub = parts [ 0 ]
2023-04-17 10:34:57 +02:00
2023-04-15 17:11:35 +02:00
else :
sub = parts [ 0 ]
top = parts [ 1 ] + '.' + parts [ 2 ]
2023-03-07 17:49:38 +01:00
apikey = get_api ()
2023-04-17 10:34:57 +02:00
longtop = sub + '.' + top
2023-03-07 17:49:38 +01:00
if apikey == None :
print ( "[red]Error:[/red] Missing APIkey. Please add one!" )
else :
ip = get_ip ()
if ip == None or 'urlopen error' in ip :
print ( '[red]Error:[/red] Failed to get public IP. Do you have a typo in your URI? [red]Error %s .[/red]' % ( ip ))
else :
cursor = conn . cursor ()
2023-04-17 10:34:57 +02:00
cursor . execute ( 'SELECT COUNT(*) FROM domains WHERE name like ? or name like ?' ,( top , longtop ,))
2023-03-07 17:49:38 +01:00
count = cursor . fetchone ()[ 0 ]
if count == 0 :
print ( '[red]Error:[/red] Top domain [bold] %s [/bold] does not exist in the DB. Please add it with [i]ddns -t %s [/i].' % ( top , top ))
else :
2023-04-17 10:34:57 +02:00
cursor . execute ( 'SELECT id FROM domains WHERE name LIKE ? or name like ?' ,( top , longtop ,))
2023-03-07 17:49:38 +01:00
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 ( '[red]Error:[/red] [bold] %s [/bold] already exists.' % ( domain ))
else :
2023-04-05 10:44:00 +02:00
cursor . execute ( 'INSERT INTO subdomains values(?,?,?,?,?,?,?,?,?)' ,( domainid , topdomain_id , sub , ip , None , now , now , now , 1 ,))
2023-03-07 17:49:38 +01:00
conn . commit ()
print ( 'The domain %s has been added.' % ( domain ))
2023-04-05 10:44:00 +02:00
def show_log ():
log_file = open ( logfile , 'r' )
content = log_file . read ()
print ( content )
log_file . close ()
2023-03-06 14:01:07 +01:00
2023-03-19 17:23:11 +01:00
def updatedb ():
# Update DB with new column 20.03.23
# Add last updated field for subdomains
2023-03-28 12:12:04 +02:00
new_column = 'last_updated'
2023-03-19 17:23:11 +01:00
info = conn . execute ( "PRAGMA table_info('subdomains')" ) . fetchall ()
2023-03-28 12:12:04 +02:00
if not any ( new_column in word for word in info ):
2023-03-20 14:57:15 +01:00
add_column = "ALTER TABLE subdomains ADD COLUMN last_updated text default 'N/A'"
2023-03-19 17:23:11 +01:00
conn . execute ( add_column )
2023-03-20 09:16:06 +01:00
conn . commit ()
2023-03-23 17:42:15 +01:00
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Database updated' )
2023-03-19 17:23:11 +01:00
2023-03-28 12:12:04 +02:00
new_column = 'last_checked'
2023-03-22 11:30:39 +01:00
info = conn . execute ( "PRAGMA table_info('subdomains')" ) . fetchall ()
2023-03-28 12:12:04 +02:00
if not any ( new_column in word for word in info ):
2023-03-22 11:30:39 +01:00
add_column = "ALTER TABLE subdomains ADD COLUMN last_checked text default 'N/A'"
conn . execute ( add_column )
2023-03-23 17:42:15 +01:00
conn . commit ()
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Database updated' )
2023-03-28 12:12:04 +02:00
new_column = 'created'
info = conn . execute ( "PRAGMA table_info('subdomains')" ) . fetchall ()
if not any ( new_column in word for word in info ):
2023-03-28 12:16:45 +02:00
add_column = "ALTER TABLE subdomains ADD COLUMN created text default '[b]Unknown Info[/b]'"
2023-03-28 12:12:04 +02:00
conn . execute ( add_column )
conn . commit ()
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Database updated' )
2023-04-05 10:44:00 +02:00
new_column = 'active'
info = conn . execute ( "PRAGMA table_info('subdomains')" ) . fetchall ()
if not any ( new_column in word for word in info ):
add_column = "ALTER TABLE subdomains ADD COLUMN active integer default 1"
conn . execute ( add_column )
conn . commit ()
logging . info ( time . strftime ( "%Y-%m- %d %H:%M" ) + ' - Info : Database updated' )
2023-03-22 11:30:39 +01:00
2023-03-19 17:23:11 +01:00
2023-03-06 14:01:07 +01:00
# Commandline arguments
conn = connect_database ()
2023-03-19 17:23:11 +01:00
updatedb ()
2023-03-23 17:42:15 +01:00
2023-03-06 14:01:07 +01:00
parser = argparse . ArgumentParser ( prog = 'ddns' ,
2023-03-07 17:49:38 +01:00
description = 'Application to use domains from DigitalOcean account as dynamic ' \
'DNS domain(s). \n The app only supports IP4. IPv6 is planned for a later release!' \
2023-03-23 17:42:15 +01:00
' \n You \' ll always find the latest version on https://gitlab.pm/rune/ddns \n\n ' \
'For bugs, suggestions, pull requests visit https://gitlab.pm/rune/ddns/issues' ,
2023-03-07 17:49:38 +01:00
formatter_class = RawTextHelpFormatter ,
2023-03-06 14:01:07 +01:00
epilog = 'Making Selfhosting easier...' )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-a' , '--api' , help = 'Add/Change API key. \n\n ' ,
2023-03-06 14:01:07 +01:00
nargs = 1 , metavar = ( 'APIkey' ), required = False , action = "append" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-f' , '--force' , help = 'Force update of IP address for all domains. \n\n ' ,
2023-03-20 09:16:06 +01:00
required = False , action = "store_true" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-l' , '--list' , help = 'List subdomains for supplied domain. \n\n ' ,
2023-03-06 14:01:07 +01:00
nargs = 1 , metavar = ( 'domain' ), required = False , action = "append" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-o' , '--serverdomains' , help = 'List subdomains for supplied domain not in ddns DB. \n\n ' ,
2023-03-07 17:49:38 +01:00
nargs = 1 , metavar = ( 'domain' ), required = False , action = "append" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-d' , '--domains' , help = 'List top domains in your DigitalOcean account. \n\n ' ,
2023-03-06 14:01:07 +01:00
required = False , action = "store_true" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-c' , '--current' , help = 'List the current IP address for the sub-domain given \n\n ' ,
2023-03-06 14:01:07 +01:00
required = False , nargs = 1 , action = "append" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-t' , '--top' , help = 'Add a new domain from your DigitalOcean account to use as a dynamic DNS domain \n\n ' ,
2023-03-06 14:01:07 +01:00
required = False , nargs = 1 , metavar = ( 'domain' ), action = 'append' )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-s' , '--sub' , help = 'Add a new subdomain to your DigitalOcean account and use as dynamic DNS. \n\n\n ' ,
2023-03-07 17:49:38 +01:00
required = False , nargs = 1 , metavar = ( 'domain' ), action = 'append' )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-k' , '--local' , help = 'Add an existing DigitalOcean subdomain to your ddns DB and use as dynamic DNS. \n\n ' ,
2023-03-07 17:49:38 +01:00
required = False , nargs = 2 , metavar = ( 'domain' , 'domainid' ), action = 'append' )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-r' , '--remove' , help = 'Remove a subdomain from your DigitalOcean account and ddns. \n\n ' ,
2023-03-07 17:49:38 +01:00
required = False , nargs = 1 , metavar = ( 'domain' ), action = 'append' )
2023-03-06 14:01:07 +01:00
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-v' , '--version' , help = 'Show current version and config info \n\n ' ,
2023-03-06 14:01:07 +01:00
required = False , action = 'store_true' )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-q' , '--log' , help = argparse . SUPPRESS , required = False , action = 'store_true' )
parser . add_argument ( '-p' , '--ipserver' , help = 'Sets or updates IP server lookup to use. Indicate 4 or 6 for IP type. \n\n ' ,
2023-03-06 14:01:07 +01:00
required = False , nargs = 2 , metavar = ( 'ip4.iurl.no' , '4' ), action = "append" )
2023-04-05 10:44:00 +02:00
parser . add_argument ( '-e' , '--edit' , help = 'Changes domain from active to inactive or the other way around...' ,
required = False , nargs = 1 , metavar = ( 'test.example.com' ), action = "append" )
2023-03-06 14:01:07 +01:00
args = vars ( parser . parse_args ())
if args [ 'list' ]:
list_sub_domains ( args [ 'list' ][ 0 ][ 0 ])
elif args [ 'domains' ]:
show_all_top_domains ()
2023-03-07 17:49:38 +01:00
elif args [ 'serverdomains' ]:
list_do_sub_domains ( args [ 'serverdomains' ][ 0 ][ 0 ])
2023-03-06 14:01:07 +01:00
elif args [ 'current' ]:
domaininfo ( args [ 'current' ][ 0 ][ 0 ])
elif args [ 'top' ]:
add_domian ( args [ 'top' ][ 0 ][ 0 ])
elif args [ 'sub' ]:
2023-03-07 17:49:38 +01:00
add_subdomain ( args [ 'sub' ][ 0 ][ 0 ])
2023-03-07 19:05:02 +01:00
elif args [ 'version' ]:
2023-03-06 14:01:07 +01:00
show_current_info ()
2023-03-20 09:16:06 +01:00
elif args [ 'force' ]:
updateip ( True )
2023-04-05 10:44:00 +02:00
elif args [ 'log' ]:
show_log ()
2023-03-06 14:01:07 +01:00
elif args [ 'ipserver' ]:
ip_server ( args [ 'ipserver' ][ 0 ][ 0 ], args [ 'ipserver' ][ 0 ][ 1 ])
elif args [ 'api' ]:
api ( args [ 'api' ][ 0 ][ 0 ])
2023-03-07 17:49:38 +01:00
elif args [ 'remove' ]:
remove_subdomain ( args [ 'remove' ][ 0 ][ 0 ])
2023-04-05 10:44:00 +02:00
elif args [ 'edit' ]:
edit_subdomain ( args [ 'edit' ][ 0 ][ 0 ])
2023-03-07 17:49:38 +01:00
elif args [ 'local' ]:
local_add_subdomain ( args [ 'local' ][ 0 ][ 0 ], args [ 'local' ][ 0 ][ 1 ])
2023-03-06 14:01:07 +01:00
else :
2023-03-20 09:16:06 +01:00
updateip ( None )
2023-03-28 12:12:04 +02:00