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] ' )
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-03-07 17:49:38 +01:00
apikey = get_api ( )
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 ( )
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] ' )
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-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 ) )
2023-03-06 14:01:07 +01:00
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 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-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