You've already forked da-ddnsclient
Initial Import
This commit is contained in:
133
.gitignore
vendored
Normal file
133
.gitignore
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Example user template template
|
||||
### Example user template
|
||||
|
||||
# IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
### Python template
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
62
ddns_updater.py
Normal file
62
ddns_updater.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
import socket
|
||||
|
||||
import click
|
||||
import requests
|
||||
import tldextract
|
||||
|
||||
from lib.directadmin import DirectAdminClient, DirectAdminClientException
|
||||
|
||||
|
||||
def get_zone_and_name(record_domain):
|
||||
"""Find a suitable zone for a domain
|
||||
:param str record_name: the domain name
|
||||
:returns: (the zone, the name in the zone)
|
||||
:rtype: tuple
|
||||
"""
|
||||
(subdomain, domain, suffix) = tldextract.extract(record_domain)
|
||||
|
||||
directadmin_zone = ".".join((domain, suffix))
|
||||
directadmin_name = subdomain
|
||||
|
||||
return directadmin_zone, directadmin_name
|
||||
|
||||
|
||||
def get_directadmin_client(url, username, password):
|
||||
return DirectAdminClient(
|
||||
url,
|
||||
username,
|
||||
password)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option('--hostname', required=False, help='The FQDN you wish to update for DynamicDNS on DirectAdmin server')
|
||||
@click.option('--url', required=False, help='The FQDN to access DirectAdmin server e.g https://my.directadmin.com:2222')
|
||||
@click.option('--username', required=False, help='The username for your account on DirectAdmin server')
|
||||
@click.option('--password', required=False, help='The password for your account on DirectAdmin server')
|
||||
def main(hostname, url, username, password):
|
||||
click.echo('Updating DNS for ' + hostname)
|
||||
(directadmin_zone, directadmin_name) = get_zone_and_name(hostname)
|
||||
|
||||
da_client = get_directadmin_client(url, username, password)
|
||||
current_ip = requests.get('https://icanhazip.com').text.strip()
|
||||
hostname_resolves_to = socket.gethostbyname(hostname).strip()
|
||||
|
||||
click.echo(current_ip)
|
||||
click.echo(hostname_resolves_to)
|
||||
|
||||
if current_ip != hostname_resolves_to:
|
||||
try:
|
||||
response = da_client.update_dns_record(directadmin_zone,
|
||||
"A",
|
||||
directadmin_name,
|
||||
hostname_resolves_to,
|
||||
current_ip, 30)
|
||||
if int(response['error']) == 0:
|
||||
click.echo("Successfully updated A record for " + hostname)
|
||||
except DirectAdminClientException as e:
|
||||
click.echo("Error updating A record: %s" % e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
lib/__init__.py
Normal file
0
lib/__init__.py
Normal file
146
lib/directadmin.py
Normal file
146
lib/directadmin.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
import requests
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
try:
|
||||
# python 3
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.parse import urlencode, parse_qs
|
||||
except ImportError:
|
||||
# python 2
|
||||
from urllib import urlencode
|
||||
from urllib2 import urlopen, Request
|
||||
from cgi import parse_qs
|
||||
|
||||
|
||||
class DirectAdminClient:
|
||||
|
||||
def __init__(self, url, username, password):
|
||||
|
||||
self.version = "0.0.5"
|
||||
self.client = requests.session()
|
||||
self.headers = {'user-agent': 'pyDirectAdmin/' + str(self.version),
|
||||
'Authorization': 'Basic %s' % base64.b64encode(("%s:%s" %
|
||||
(username, password))
|
||||
.encode()).decode('utf8'),
|
||||
}
|
||||
self.url = url
|
||||
|
||||
def make_request(self, endpoint, data=None):
|
||||
response = None # Empty response variable
|
||||
|
||||
if data is not None:
|
||||
# Data is present add the fields to call
|
||||
response = urlopen(
|
||||
Request(
|
||||
"%s?%s" % (self.url + '/{}'.format(endpoint), urlencode(data)),
|
||||
headers=self.headers,
|
||||
)
|
||||
)
|
||||
elif data is None:
|
||||
# Data is not present so we don't need addition field.
|
||||
response = urlopen(
|
||||
Request(
|
||||
"%s?" % (self.url + '/{}'.format(endpoint)),
|
||||
headers=self.headers,
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def __process_response__(response):
|
||||
if int(response['error'][0]) > 0:
|
||||
# There was an error
|
||||
raise DirectAdminClientException(response['text'][0])
|
||||
elif int(response['error'][0]) == 0:
|
||||
# Everything succeeded
|
||||
return {'error': response['error'][0],
|
||||
'message': response['text'][0]
|
||||
}
|
||||
|
||||
def get_domain_list(self):
|
||||
r = self.make_request('CMD_API_SHOW_DOMAINS')
|
||||
|
||||
domains = parse_qs(r.read().decode('utf8'),
|
||||
keep_blank_values=0,
|
||||
strict_parsing=1)
|
||||
response = list()
|
||||
for domain in domains.values():
|
||||
response.append(domain[0])
|
||||
return response
|
||||
|
||||
def get_zone_list(self, domain):
|
||||
params = OrderedDict([('domain', domain)])
|
||||
r = self.make_request('CMD_API_DNS_CONTROL', params)
|
||||
|
||||
return r.read()
|
||||
|
||||
def add_dns_record(self, domain, record_type, record_name, record_value, record_ttl=None):
|
||||
|
||||
params = OrderedDict([('domain', domain),
|
||||
('action', 'add'),
|
||||
('type', record_type.upper()),
|
||||
('name', record_name),
|
||||
('value', record_value)])
|
||||
|
||||
if record_ttl is not None:
|
||||
params.update({'ttl': record_ttl})
|
||||
|
||||
response = self.make_request('CMD_API_DNS_CONTROL', data=params)
|
||||
response = parse_qs(response.read().decode('utf8'),
|
||||
keep_blank_values=0,
|
||||
strict_parsing=1)
|
||||
|
||||
return self.__process_response__(response)
|
||||
|
||||
def update_dns_record(self, domain, record_type, record_name, record_value_old, record_value_new, record_ttl=None):
|
||||
|
||||
params = OrderedDict([('domain', domain),
|
||||
('action', 'edit'),
|
||||
('type', record_type.upper()),
|
||||
(record_type.lower() + "recs0", 'name={}&value={}'.format(record_name, record_value_old)),
|
||||
('name', record_name),
|
||||
('value', record_value_new)])
|
||||
|
||||
if record_ttl is not None:
|
||||
params.update({'ttl': record_ttl})
|
||||
|
||||
response = self.make_request('CMD_API_DNS_CONTROL', data=params)
|
||||
response = parse_qs(response.read().decode('utf8'),
|
||||
keep_blank_values=0,
|
||||
strict_parsing=1)
|
||||
|
||||
return self.__process_response__(response)
|
||||
|
||||
def delete_dns_record(self, domain, record_type, record_name, record_value):
|
||||
params = OrderedDict([('domain', domain),
|
||||
('action', 'select'),
|
||||
(record_type.lower() + "recs0", 'name={}&value={}'.format(record_name, record_value))
|
||||
])
|
||||
|
||||
response = self.make_request('CMD_API_DNS_CONTROL', data=params)
|
||||
response = parse_qs(response.read().decode('utf8'),
|
||||
keep_blank_values=0,
|
||||
strict_parsing=1)
|
||||
|
||||
return self.__process_response__(response)
|
||||
|
||||
def override_domain_ttl(self, domain, record_name, ttl):
|
||||
params = OrderedDict([('domain', domain),
|
||||
('action', 'ttl'),
|
||||
('ttl_select', 'custom'),
|
||||
('name', record_name),
|
||||
('ttl', ttl)])
|
||||
|
||||
response = self.make_request('CMD_API_DNS_CONTROL', data=params)
|
||||
response = parse_qs(response.read().decode('utf8'),
|
||||
keep_blank_values=0,
|
||||
strict_parsing=1)
|
||||
|
||||
return self.__process_response__(response)
|
||||
|
||||
|
||||
class DirectAdminClientException(Exception):
|
||||
pass
|
||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
certifi==2019.9.11
|
||||
chardet==3.0.4
|
||||
Click==7.0
|
||||
idna==2.8
|
||||
requests==2.22.0
|
||||
urllib3==1.25.6
|
||||
tldextract==2.2.1
|
||||
Reference in New Issue
Block a user