Mddclient: structure
This commit is contained in:
parent
a9f0b79fb9
commit
c05a6717e4
36
mddclient/README.md
Normal file
36
mddclient/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# MDDCLIENT
|
||||
It's a client for dynamic DNS services like Infomaniak's.
|
||||
Like [ddclient](https://github.com/ddclient/ddclient), but supports sending multiple updates for different domains.
|
||||
I developed this because [Infomaniak's DynDns APIS](https://www.infomaniak.com/en/support/faq/2376/dyndns-updating-a-dynamic-dns-via-the-api) doesn't support multiple domains updates on the same call. So, even if ddclient was supported for a single domain, it was needed to setup multiple instances to update multiple domains (or subdomains) on the same machine.
|
||||
|
||||
## Compatibility
|
||||
I wrote this to solve the multiple domain update problem on Infomainak, but should work for every other provider supporting the original ddclient.
|
||||
|
||||
## Use case
|
||||
Let's say we have our Nextcloud instance on `https://mysite.cloud`. As self hosters, we run this instance on our server at home, behind our fiber or DSL provider with a dynamic IP. We need to use ddclient to keep the dynamic DNS updated, so the requests to `https://mysite.cloud` are sent to our current IP, that may change in any moment.
|
||||
Now we decide to host a Matrix chat instance on `https://matrix.mysite.cloud` and a Mastodon social instance on `https://mastodon.mysite.cloud`. We configure ddclient adding the new subdomains in `/etc/ddclient.conf`, but the requests begin to fail with a 400 http code. This happens because our Dynamic DNS provider doesn't support multiple updates on the same request.
|
||||
|
||||
So we need a script that issues an update request for every (sub)domain. This is mddclient.
|
||||
|
||||
## Setup
|
||||
Copy the script and the config file into the system to check:
|
||||
```
|
||||
cp mddclient.py /usr/local/bin/mddclient.py
|
||||
cp mddclient.cfg.example /usr/local/etc/mddclient.cfg
|
||||
```
|
||||
Make the script executable:
|
||||
```
|
||||
chmod +x /usr/local/bin/mddclient.py
|
||||
```
|
||||
Edit `/usr/local/etc/mddclient.cfg` setting up the server url, username, password and the main domain to update. If you have other domains or subdomains running on the same fiber/dsl, configure them in the subsections as shown in the config example.
|
||||
Run `/usr/local/bin/mddclient.py /usr/local/etc/mddclient.cfg` to check it is working.
|
||||
Now copy the cron file:
|
||||
```
|
||||
cp mddclient.cron.example /etc/cron.d/mddclient
|
||||
```
|
||||
For increased safety, edit the cron file placing your email address in MAILTO var to be notified in case of mddclient.py catastrophic failure.
|
||||
|
||||
Setup is now complete: the cron runs the script every minute and updates the dns.
|
||||
|
||||
## Optimization
|
||||
If multiple subdomains are served from the same public url (the same fiber/DSL account) it is possible to optimize the domain updates: when the main one results already up-to-date, the others are not updated. This feature is enabled by default, but can be disabled setting OPTIMIZE_API_CALLS to false in the config.
|
30
mddclient/mddclient.cfg.example
Normal file
30
mddclient/mddclient.cfg.example
Normal file
@ -0,0 +1,30 @@
|
||||
# The DEFAULT section contains the main domain config.
|
||||
[DEFAULT]
|
||||
|
||||
# Dynamic DNS Server
|
||||
SERVER=infomaniak.com
|
||||
LOGIN=myUserName
|
||||
PASSWORD=mySuperSecretPassword
|
||||
|
||||
# Main domain
|
||||
DOMAIN=mysite.cloud
|
||||
|
||||
# If multiple subdomains are served from the same public url
|
||||
# (the same fiber/DSL account) it is possible to optimize the
|
||||
# domain updates: when the main one results already up-to-date,
|
||||
# the others are not updated.
|
||||
OPTIMIZE_API_CALLS=True
|
||||
|
||||
[matrix]
|
||||
# The matrix.mysite.cloud subdomain
|
||||
# This will be updated only if the main domain needed update
|
||||
# (or the OPTIMIZE_API_CALLS setting is disabled)
|
||||
DISABLED=True
|
||||
DOMAIN=matrix.mysite.cloud
|
||||
|
||||
[mastodon]
|
||||
# The mastodon.mysite.cloud subdomain
|
||||
# This will be updated only if the main domain needed update
|
||||
# (or the OPTIMIZE_API_CALLS setting is disabled)
|
||||
DISABLED=True
|
||||
DOMAIN=mastodon.mysite.cloud
|
6
mddclient/mddclient.cron.example
Normal file
6
mddclient/mddclient.cron.example
Normal file
@ -0,0 +1,6 @@
|
||||
# Cron to execute mddclient
|
||||
# As a security measure, the address in MAILTO will be notified
|
||||
# if the mddclient.py script crashes.
|
||||
|
||||
MAILTO="your-email-address"
|
||||
* * * * * root /usr/local/bin/mddclient.py /usr/local/etc/mddclient.cfg -q
|
136
mddclient/mddclient.py
Executable file
136
mddclient/mddclient.py
Executable file
@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
""" @package docstring
|
||||
Mddclient
|
||||
|
||||
A DynamicDns client like ddclient, but supporting multiple (sub)domains,
|
||||
every one in its autonomous request.
|
||||
|
||||
Installation:
|
||||
- Copy mddclient.cfg in /usr/local/etc/mddclient.cfg and customize it
|
||||
- Copy mddclient.py in /usr/local/bin/mddclient.py
|
||||
|
||||
Usage:
|
||||
Place a cron entry like this one:
|
||||
|
||||
* * * * * root python3 /usr/local/bin/mddclient.py /usr/local/etc/mddclient.cfg
|
||||
|
||||
@author Daniele Verducci <daniele.verducci@ichibi.eu>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import configparser
|
||||
import traceback
|
||||
|
||||
|
||||
NAME = 'mddclient'
|
||||
VERSION = '0.1'
|
||||
DESCRIPTION = 'A DynamicDns client like ddclient, but supporting multiple (sub)domains'
|
||||
|
||||
class Main:
|
||||
|
||||
def __init__(self, configPath):
|
||||
''' Reads the config '''
|
||||
self._log = logging.getLogger('main')
|
||||
|
||||
if not os.path.exists(configPath) or not os.path.isfile(configPath):
|
||||
raise ValueError('configPath must be a file')
|
||||
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read(configPath)
|
||||
|
||||
def run(self):
|
||||
''' Makes the update requests '''
|
||||
|
||||
for section in self.config:
|
||||
if section == 'DEFAULT':
|
||||
# Main domain
|
||||
# TODO
|
||||
continue
|
||||
|
||||
s = Settings(section, self.config)
|
||||
if s.disabled:
|
||||
self._log.info('Ignoring disabled domain "{}"'.format(section))
|
||||
continue
|
||||
|
||||
self._log.info('Updating "{}"'.format(section))
|
||||
|
||||
# TODO: subdomain update request
|
||||
|
||||
|
||||
class Settings:
|
||||
''' Represents settings for a check '''
|
||||
|
||||
EMAIL_LIST_SEP = ','
|
||||
|
||||
def __init__(self, name, config):
|
||||
self.config = config
|
||||
|
||||
## Section name
|
||||
self.name = name
|
||||
|
||||
## Settings
|
||||
self.disabled = self.getBoolean(name, 'DISABLED', False)
|
||||
self.optimize = self.getBoolean(name, 'OPTIMIZE_API_CALLS', True)
|
||||
|
||||
## DynDNS server data
|
||||
self.ddserver = self.getStr(name, 'SERVER', None)
|
||||
self.dduser = self.getStr(name, 'LOGIN', None)
|
||||
self.ddpass = self.getStr(name, 'PASSWORD', None)
|
||||
|
||||
## Domain to update
|
||||
self.domain = self.getStr(name, 'DOMAIN', False)
|
||||
|
||||
def getStr(self, name, key, defaultValue):
|
||||
try:
|
||||
return self.config.get(name, key)
|
||||
except configparser.NoOptionError:
|
||||
return defaultValue
|
||||
|
||||
def getBoolean(self, name, key, defaultValue):
|
||||
try:
|
||||
return self.config.getboolean(name, key)
|
||||
except configparser.NoOptionError:
|
||||
return defaultValue
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog = NAME + '.py',
|
||||
description = NAME + ' ' + VERSION + '\n' + DESCRIPTION,
|
||||
formatter_class = argparse.RawTextHelpFormatter
|
||||
)
|
||||
parser.add_argument('configFile', help="configuration file path")
|
||||
parser.add_argument('-q', '--quiet', action='store_true', help="suppress non-essential output")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.quiet:
|
||||
level = logging.WARNING
|
||||
else:
|
||||
level = logging.INFO
|
||||
logging.basicConfig(level=level)
|
||||
|
||||
try:
|
||||
main = Main(args.configFile)
|
||||
main.run()
|
||||
except Exception as e:
|
||||
logging.critical(traceback.format_exc())
|
||||
print('ERROR: {}'.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
Loading…
Reference in New Issue
Block a user