1
0
mirror of https://github.com/EV21/dynb.git synced 2025-12-26 16:39:32 +01:00

12 Commits

Author SHA1 Message Date
2f5e193f88 add DynDNS2 support for noip.com 2021-01-27 18:50:24 +01:00
5210c35400 ♻️ refactor, fix and debug error handling 2021-01-27 17:52:15 +01:00
522a0f99bb ♻️ refactor main code 2021-01-26 22:23:03 +01:00
3bf6b69ae8 add interpretaton of status codes and act accordingly 2021-01-26 21:43:07 +01:00
d139022295 make network interface configurable 2021-01-26 21:43:07 +01:00
8997835903 🐛 fix sourcing of config file
♻️ do some shellcheck fixes
2021-01-26 21:43:07 +01:00
d6dc223794 add DynDNS2 support for dynv6.com 2021-01-26 21:43:07 +01:00
dc12f71d00 📝 add CHANGELOG.md 2021-01-26 21:43:07 +01:00
5ba730cff1 add .gitchangelog.rc 2021-01-26 21:43:07 +01:00
1104cf8505 📝 add example.env 2021-01-26 21:43:07 +01:00
99a446f4c7 📝 write README.md 2021-01-26 21:42:21 +01:00
f056e96e25 add dynb.sh 2021-01-26 21:24:33 +01:00
6 changed files with 1083 additions and 2 deletions

295
.gitchangelog.rc Normal file
View File

@@ -0,0 +1,295 @@
# -*- coding: utf-8; mode: python -*-
##
## Format
##
## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
##
## Description
##
## ACTION is one of 'chg', 'fix', 'new'
##
## Is WHAT the change is about.
##
## 'chg' is for refactor, small improvement, cosmetic changes...
## 'fix' is for bug fixes
## 'new' is for new features, big improvement
##
## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
##
## Is WHO is concerned by the change.
##
## 'dev' is for developpers (API changes, refactors...)
## 'usr' is for final users (UI changes)
## 'pkg' is for packagers (packaging changes)
## 'test' is for testers (test only related changes)
## 'doc' is for doc guys (doc only changes)
##
## COMMIT_MSG is ... well ... the commit message itself.
##
## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
##
## They are preceded with a '!' or a '@' (prefer the former, as the
## latter is wrongly interpreted in github.) Commonly used tags are:
##
## 'refactor' is obviously for refactoring code only
## 'minor' is for a very meaningless change (a typo, adding a comment)
## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
## 'wip' is for partial functionality but complete subfunctionality.
##
## Example:
##
## new: usr: support of bazaar implemented
## chg: re-indentend some lines !cosmetic
## new: dev: updated code to be compatible with last version of killer lib.
## fix: pkg: updated year of licence coverage.
## new: test: added a bunch of test around user usability of feature X.
## fix: typo in spelling my name in comment. !minor
##
## Please note that multi-line commit message are supported, and only the
## first line will be considered as the "summary" of the commit message. So
## tags, and other rules only applies to the summary. The body of the commit
## message will be displayed in the changelog without reformatting.
##
## ``ignore_regexps`` is a line of regexps
##
## Any commit having its full commit message matching any regexp listed here
## will be ignored and won't be reported in the changelog.
##
ignore_regexps = [
r'@minor', r'!minor',
r'@cosmetic', r'!cosmetic',
r'@refactor', r'!refactor',
r'@wip', r'!wip',
r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:',
r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:',
r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
r'^$', ## ignore commits with empty messages
]
## ``section_regexps`` is a list of 2-tuples associating a string label and a
## list of regexp
##
## Commit messages will be classified in sections thanks to this. Section
## titles are the label, and a commit is classified under this section if any
## of the regexps associated is matching.
##
## Please note that ``section_regexps`` will only classify commits and won't
## make any changes to the contents. So you'll probably want to go check
## ``subject_process`` (or ``body_process``) to do some changes to the subject,
## whenever you are tweaking this variable.
##
section_regexps = [
('New', [
r'^:sparkles\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Changes', [
r'^:wrench\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Fix', [
r'^:bug\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Documentation', [
r'^:memo\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Remove', [
r'^:fire\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('Other', None ## Match all lines
),
]
## ``body_process`` is a callable
##
## This callable will be given the original body and result will
## be used in the changelog.
##
## Available constructs are:
##
## - any python callable that take one txt argument and return txt argument.
##
## - ReSub(pattern, replacement): will apply regexp substitution.
##
## - Indent(chars=" "): will indent the text with the prefix
## Please remember that template engines gets also to modify the text and
## will usually indent themselves the text if needed.
##
## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
##
## - noop: do nothing
##
## - ucfirst: ensure the first letter is uppercase.
## (usually used in the ``subject_process`` pipeline)
##
## - final_dot: ensure text finishes with a dot
## (usually used in the ``subject_process`` pipeline)
##
## - strip: remove any spaces before or after the content of the string
##
## - SetIfEmpty(msg="No commit message."): will set the text to
## whatever given ``msg`` if the current text is empty.
##
## Additionally, you can `pipe` the provided filters, for instance:
#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ")
#body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
#body_process = noop
body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
## ``subject_process`` is a callable
##
## This callable will be given the original subject and result will
## be used in the changelog.
##
## Available constructs are those listed in ``body_process`` doc.
subject_process = (strip |
ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
SetIfEmpty("No commit message.") | ucfirst | final_dot)
## ``tag_filter_regexp`` is a regexp
##
## Tags that will be used for the changelog must match this regexp.
##
tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$'
## ``unreleased_version_label`` is a string or a callable that outputs a string
##
## This label will be used as the changelog Title of the last set of changes
## between last valid tag and HEAD if any.
unreleased_version_label = "(unreleased)"
## ``output_engine`` is a callable
##
## This will change the output format of the generated changelog file
##
## Available choices are:
##
## - rest_py
##
## Legacy pure python engine, outputs ReSTructured text.
## This is the default.
##
## - mustache(<template_name>)
##
## Template name could be any of the available templates in
## ``templates/mustache/*.tpl``.
## Requires python package ``pystache``.
## Examples:
## - mustache("markdown")
## - mustache("restructuredtext")
##
## - makotemplate(<template_name>)
##
## Template name could be any of the available templates in
## ``templates/mako/*.tpl``.
## Requires python package ``mako``.
## Examples:
## - makotemplate("restructuredtext")
##
#output_engine = rest_py
#output_engine = mustache("restructuredtext")
output_engine = mustache("markdown")
#output_engine = makotemplate("restructuredtext")
## ``include_merge`` is a boolean
##
## This option tells git-log whether to include merge commits in the log.
## The default is to include them.
include_merge = True
## ``log_encoding`` is a string identifier
##
## This option tells gitchangelog what encoding is outputed by ``git log``.
## The default is to be clever about it: it checks ``git config`` for
## ``i18n.logOutputEncoding``, and if not found will default to git's own
## default: ``utf-8``.
#log_encoding = 'utf-8'
## ``publish`` is a callable
##
## Sets what ``gitchangelog`` should do with the output generated by
## the output engine. ``publish`` is a callable taking one argument
## that is an interator on lines from the output engine.
##
## Some helper callable are provided:
##
## Available choices are:
##
## - stdout
##
## Outputs directly to standard output
## (This is the default)
##
## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start())
##
## Creates a callable that will parse given file for the given
## regex pattern and will insert the output in the file.
## ``idx`` is a callable that receive the matching object and
## must return a integer index point where to insert the
## the output in the file. Default is to return the position of
## the start of the matched string.
##
## - FileRegexSubst(file, pattern, replace, flags)
##
## Apply a replace inplace in the given file. Your regex pattern must
## take care of everything and might be more complex. Check the README
## for a complete copy-pastable example.
##
# publish = FileInsertIntoFirstRegexMatch(
# "CHANGELOG.rst",
# r'/(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/',
# idx=lambda m: m.start(1)
# )
#publish = stdout
## ``revs`` is a list of callable or a list of string
##
## callable will be called to resolve as strings and allow dynamical
## computation of these. The result will be used as revisions for
## gitchangelog (as if directly stated on the command line). This allows
## to filter exaclty which commits will be read by gitchangelog.
##
## To get a full documentation on the format of these strings, please
## refer to the ``git rev-list`` arguments. There are many examples.
##
## Using callables is especially useful, for instance, if you
## are using gitchangelog to generate incrementally your changelog.
##
## Some helpers are provided, you can use them::
##
## - FileFirstRegexMatch(file, pattern): will return a callable that will
## return the first string match for the given pattern in the given file.
## If you use named sub-patterns in your regex pattern, it'll output only
## the string matching the regex pattern named "rev".
##
## - Caret(rev): will return the rev prefixed by a "^", which is a
## way to remove the given revision and all its ancestor.
##
## Please note that if you provide a rev-list on the command line, it'll
## replace this value (which will then be ignored).
##
## If empty, then ``gitchangelog`` will act as it had to generate a full
## changelog.
##
## The default is to use all commits to make the changelog.
#revs = ["^1.0.3", ]
#revs = [
# Caret(
# FileFirstRegexMatch(
# "CHANGELOG.rst",
# r"(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")),
# "HEAD"
#]
revs = []

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env*

36
CHANGELOG.md Normal file
View File

@@ -0,0 +1,36 @@
# Changelog
## (unreleased)
### New
* :sparkles: add interpretaton of status codes and act accordingly. [Eduard Veit]
* :sparkles: make network interface configurable. [Eduard Veit]
* :sparkles: add DynDNS2 support for dynv6.com. [Eduard Veit]
* :sparkles: add .gitchangelog.rc. [Eduard Veit]
* :sparkles: add dynb. [Eduard Veit]
### Fix
* :bug: fix sourcing of config file. [Eduard Veit]
:recycle: do some shellcheck fixes
### Documentation
* :memo: add CHANGELOG.md. [Eduard Veit]
* :memo: add .env.example. [Eduard Veit]
* :memo: write README.md. [Eduard Veit]
### Other
* Initial commit. [EV21]

112
README.md
View File

@@ -1,2 +1,110 @@
# dynb
dynb - dynamic DNS update script for bash
# 🔃 DynB
DynB - dynamic DNS update script, written in bash
IPv4 (A) and IPv6 (AAAA) record updates are supported.
<!-- TOC -->
- [✨ Update Methods](#-update-methods)
- [APIs](#apis)
- [DynDNS2](#dyndns2)
- [📦 Requirements](#-requirements)
- [🚀 Installation](#-installation)
- [⚙ Configuration](#-configuration)
- [🏃 Run](#-run)
- [⏰ Cron](#-cron)
<!-- /TOC -->
## ✨ Update Methods
The following update methods are currently implemented:
### APIs
* INWX.com JSON-RPC-API
Limitations:
- minimum TTL is 300 (5 minutes)
### DynDNS2
* INWX.com
* dynv6.com
## 📦 Requirements
* `curl` - The minimum requirement for running DynDNS2 operations
essential for APIs:
* `jq` - Command-line JSON processor
optional requirement:
* `getopt` for CLI parameter handling from [util-linux](https://pkgs.org/download/util-linux)
## 🚀 Installation
Download the latest release
or simply clone this repo
```
git clone https://github.com/EV21/dynb.git
```
If you want to add the script to you PATH, run :point_down:
```
bash dynb.sh --link
```
This convenience function only works if `util-linux` is installed on your system.
## ⚙ Configuration
You can use a config in form of an `.env` file.
Or if your system meets the relevant requirements you can use CLI parameters.
Create `.env` in the app root directory or at `~/.local/share/dynb/.env`.
```
_dyn_domain=dyndns.example.com
## service provider could be inwx
_serviceProvider=inwx
## update method options: domrobot, dyndns
_update_method=domrobot
## ip mode could be either: 4, 6 or dual for dualstack
_ip_mode=dual
## If you are using the DomRobot RPC-API enter your credentials for the web interface login here
## If you are using the DynDNS2 protocol enter your credentials here
_username=
_password=
```
## 🏃 Run
If you have a config file just run :point_down:
```
dynb
```
Alternatively you can use parameters if your system meets the relevant requirements. This example shows the long form parameter, there are also short ones.
Call the help function :point_down:
```
dynb --help
```
```
dynb --ip-mode dualstack --update-method domrobot --domain dyndns.example.com --username user42 --password SuperSecretPassword
```
```
dynb --ip-mode dualstack --update-method dyndns --provider inwx --domain dyndns.example.com --username user42 --password SuperSecretPassword
```
## ⏰ Cron
To automatically call the script you can use cron.
execute :point_down:
```
crontab -e
```
then enter :point_down: to run dynb every five minutes.
```
*/5 * * * * $HOME/.local/bin/dynb >> $HOME/.local/share/dynb/dynb-cron.log
```
Note, cron typically does not use the users PATH variable.

634
dynb.sh Executable file
View File

@@ -0,0 +1,634 @@
#!/usr/bin/env bash
## Copyright (c) 2021 Eduard Veit
## All rights reserved. This program and the accompanying materials
## are made available under the terms of the MIT license
## which accompanies this distribution, and is available at
## https://opensource.org/licenses/MIT
###################
## Configuration ##
###################
_dyn_domain=
## service provider could be inwx
_serviceProvider=
## update method options: domrobot, dyndns
_update_method=
## ip mode could be either: 4, 6 or dual for dualstack
_ip_mode=
## If you are using the DomRobot RPC-API enter your credentials for the web interface login here
## If you are using the DynDNS2 protocol enter your credentials here
_username=
_password=
## or use a token
_token=
## TTL (time to live) for the DNS record
## This setting is only relevant for API based record updates (not DnyDNS2!)
## minimum allowed TTL value by inwx is 300 (5 minutes)
TTL=300
## The IP-Check sites (some sites have different urls for v4 and v6)
## Pro tip: use your own ip check server for privacy
## it could be as simple as that...
## create an index.php with <?php echo $_SERVER['REMOTE_ADDR']; ?>
_ipv4_checker=api64.ipify.org
_ipv6_checker=api64.ipify.org
## An exernal DNS check server prevents wrong info from local DNS servers/resolvers
_DNS_checkServer=1.1.1.1
## if you are actively using multiple network interfaces you might want to specify this
## normally the default value is okay
#_network_interface=eth0
_network_interface=
######################################################
## You don't need to change the following variables ##
_INWX_JSON_API_URL=https://api.domrobot.com/jsonrpc/
_new_IPv4=
_new_IPv6=
_dns_records=
_main_domain=
_has_getopt=
_is_IPv4_enabled=false
_is_IPv6_enabled=false
_interface_str=
_status=
_eventTime=0
_errorCounter=0
_response=
_statusHostname=
_statusUsername=
_statusPassword=
_version=0.0.1
_userAgent="DynB/$_version github.com/EV21/dynb"
_configFile=$HOME/.local/share/dynb/.env
_statusFile=/tmp/dynb.status
_debug=1
_help_message="$(cat << 'EOF'
dynb - dynamic DNS update script for bash
Usage
=====
dynb [options]
-h, --help displays this help message
--version outputs the client version
--link links to your script at ~/.local/bin/dynb
--reset deletes the client blocking status file
Configuration options
---------------------
-i | --ip-mode [ 4 | 6 | dual ] updates type A (IPv4) and AAAA (IPv6) records
-m | --update-method [dyndns | domrobot] choose if you want to use DynDNS2 or the DomRobot RPC-API
-s | --service-provider inwx set your provider in case you are using DynDNS2
-d | --domain "dyndns.example.com" set the domain you want to update
-u | --username "user42" depends on your selected update method and your provider
-p | --password "SuperSecretPassword" depends on your selected update method and your provider
-t | --token "YourProviderGivenToken" depends on your selected update method and your provider
##### examples #####
dynb --ip-mode dual --update-method domrobot --domain dyndns.example.com --username user42 --password SuperSecretPassword
dynb --ip-mode dual --update-method dyndns --service-provider inwx --domain dyndns.example.com --username user42 --password SuperSecretPassword
EOF
)"
function debugMode() {
if [[ $_debug -eq 1 ]]; then
return 0
else
return 1
fi
}
function debugMessage() {
if debugMode; then
echo "Debug: ${1}"
fi
}
function echoerr() { printf "%s\n" "$*" >&2; }
# The main domain as an identifier for the dns zone is required for the updateRecord call
function getMainDomain() {
request=$( echo "{}" | \
jq '(.method="nameserver.list")' | \
jq "(.params.user=\"$_username\")" | \
jq "(.params.pass=\"$_password\")"
)
_response=$(curl --silent \
"$_interface_str" \
--user-agent "$_userAgent" \
--header "Content-Type: application/json" \
--request POST $_INWX_JSON_API_URL \
--data "$request" | jq ".resData.domains[] | select(inside(.domain=\"$_dyn_domain\"))"
)
_main_domain=$( echo "$_response" | jq --raw-output '.domain' )
}
function fetchDNSRecords() {
request=$( echo "{}" | \
jq '(.method="'nameserver.info'")' | \
jq "(.params.user=\"$_username\")" | \
jq "(.params.pass=\"$_password\")" | \
jq "(.params.domain=\"$_main_domain\")" | \
jq "(.params.name=\"$_dyn_domain\")"
)
_response=$( curl --silent \
"$_interface_str" \
--user-agent "$_userAgent" \
--header "Content-Type: application/json" \
--request POST $_INWX_JSON_API_URL \
--data "$request"
)
_dns_records=$( echo "$_response" | jq '.resData.record[]' )
}
# requires parameter A or AAAA
# result to stdout
function getRecordID() {
echo "$_dns_records" | jq "select(.type == \"${1}\") | .id"
}
# requires parameter A or AAAA
# result to stdout
function getDNSIP() {
echo "$_dns_records" | jq --raw-output "select(.type == \"${1}\") | .content"
}
# requires parameter
# 1. param: 4 or 6 for ip version
# 2. param: IP check server address
# result to stdout
function getRemoteIP() {
curl --silent "$_interface_str" --user-agent "$_userAgent" \
--ipv"${1}" --dns-servers 1.1.1.1 --location "${2}"
}
# requires parameter
# 1. param: 4 or 6 as ip version
function updateRecord() {
if [[ ${1} == 4 ]]; then
ID=$(getRecordID A)
IP=$_new_IPv4
fi
if [[ ${1} == 6 ]]; then
ID=$(getRecordID AAAA)
IP=$_new_IPv6
fi
if [[ $IP != "" ]]; then
request=$( echo "{}" | \
jq '(.method="nameserver.updateRecord")' | \
jq "(.params.user=\"$_username\")" | \
jq "(.params.pass=\"$_password\")" | \
jq "(.params.id=\"$ID\")" | \
jq "(.params.content=\"$IP\")" | \
jq "(.params.ttl=\"$TTL\")"
)
_response=$(curl --silent \
"$_interface_str" \
--user-agent "$_userAgent" \
--header "Content-Type: application/json" \
--request POST $_INWX_JSON_API_URL \
--data "$request"
)
echo -e "$(echo "$_response" | jq --raw-output '.msg')\n Domain: $_dyn_domain\n new IPv${1}: $IP"
fi
}
# using DynDNS2 protocol
function dynupdate() {
# default parameter values
myip_str=myip
myipv6_str=myipv6
INWX_DYNDNS_UPDATE_URL="https://dyndns.inwx.com/nic/update?"
NOIP_DYNDNS_UPDATE_URL="https://dynupdate.no-ip.com/nic/update?hostname=$_dyn_domain&"
DYNV6_DYNDNS_UPDATE_URL="https://dynv6.com/api/update?zone=$_dyn_domain&token=$_token&"
if [[ $_serviceProvider == "inwx" ]]; then
dyndns_update_url=$INWX_DYNDNS_UPDATE_URL
fi
if [[ $_serviceProvider == "noip" ]] || [[ $_serviceProvider == "no-ip" ]]; then
dyndns_update_url=$NOIP_DYNDNS_UPDATE_URL
fi
if [[ $_serviceProvider == "dynv6" ]]; then
dyndns_update_url="${DYNV6_DYNDNS_UPDATE_URL}"
myip_str=ipv4
myipv6_str=ipv6
fi
if [[ $_is_IPv4_enabled == true ]] && [[ $_is_IPv6_enabled == true ]]; then
dyndns_update_url="${dyndns_update_url}${myip_str}=${_new_IPv4}&${myipv6_str}=${_new_IPv6}"
fi
if [[ $_is_IPv4_enabled == true ]] && [[ $_is_IPv6_enabled == false ]]; then
dyndns_update_url="${dyndns_update_url}${myip_str}=${_new_IPv4}"
fi
if [[ $_is_IPv4_enabled == false ]] && [[ $_is_IPv6_enabled == true ]]; then
dyndns_update_url="${dyndns_update_url}${myipv6_str}=${_new_IPv6}"
fi
debugMessage "Update URL was: $dyndns_update_url"
## request ##
if [[ $_serviceProvider == "dynv6" ]]; then
_response=$(curl --silent "$_interface_str" \
--user-agent "$_userAgent" \
"${dyndns_update_url}"
)
fi
if [[ $_serviceProvider == "inwx" || $_serviceProvider == "noip" ]]; then
_response=$(curl --silent "$_interface_str" \
--user-agent "$_userAgent" \
--user "$_username":"$_password" \
"${dyndns_update_url}" )
fi
case $_response in
good* )
if [[ $_response == "good 127.0.0.1" ]]; then
echoerr "Error: $_response: Request ignored."
return 1
else
echo "$_response: The DynDNS update has been executed."
_errorCounter=0
return 0
fi
;;
nochg* )
echo "$_response: Nothing has changed, IP addresses are still up to date."
return 1
;;
abuse )
echoerr "Error: $_response: Username is blocked due to abuse."
return 1
;;
badauth | 401 )
echoerr "Error: $_response: Invalid username password combination."
return 1
;;
badagent )
echoerr "Error: $_response: Client disabled. Something is very wrong!"
return 1
;;
!donator )
echoerr "Error: $_response: An update request was sent, including a feature that is not available to that particular user such as offline options."
return 1
;;
!yours )
echoerr "Error: $_response: The domain does not belong to your user account"
return 1
;;
notfqdn )
echoerr "Error: $_response: Hostname $_dyn_domain is invalid"
return 1
;;
nohost )
echoerr "Error: $_response: Hostname supplied does not exist under specified account, enter new login credentials before performing an additional request."
return 1
;;
numhost )
echoerr "Error: $_response: Too many hostnames have been specified for this update"
return 1
;;
dnserr )
echoerr "Error: $_response: There is an internal error in the dyndns update system. Retry update no sooner than 30 minutes."
return 1
;;
911 | 5* )
echoerr "Error: $_response: A fatal error on provider side such as a database outage. Retry update no sooner than 30 minutes."
return 1
;;
* )
if [[ "$_response" == "$_status" ]]; then
echoerr "Error: An unknown response code has been received. $_response"
return 1
else
echoerr "Error: unknown respnse code: $_response"
return 0
fi
;;
esac
}
function setStatus() {
echo "_status=$1; _eventTime=$2; _errorCounter=$3; _statusHostname=$4; _statusUsername=$5; _statusPassword=$6" > /tmp/dynb.status
}
# handle errors from past update requests
function checkStatus() {
case $_status in
nochg* )
if [[ _errorCounter -gt 1 ]]; then
echoerr "Error: The update client was spamming unnecessary update requests, something might be wrong with your IP-Check site."
echoerr "Fix your config an then delete $_statusFile"
return 1
fi
;;
nohost | !yours )
if [[ "$_statusHostname" == "$_dyn_domain" && ( "$_statusUsername" == "$_username" || $_statusUsername == "$_token" ) ]]; then
echoerr "Error: Hostname supplied does not exist under specified account, enter new login credentials before performing an additional request."
return 1
else
rm "$_statusFile"
fi
return 0
;;
badauth | 401 )
if [[ "$_statusUsername" == "$_username" && "$_statusPassword" == "$_password" ]]; then
echoerr "Error: Invalid username password combination."
return 1
else
rm "$_statusFile"
fi
return 0
;;
badagent )
echoerr "Error: Client is deactivated by provider."
echo "Fix your config and then manually remove $_statusFile to reset the client blockade."
echo "If it still fails file an issue at github or try another client :)"
return 1
;;
!donator )
echoerr "Error: An update request was sent, including a feature that is not available to that particular user such as offline options."
echo "Fix your config and then manually remove $_statusFile to reset the client blockade"
echo "If it still fails file an issue at github or try another client :)"
return 1
;;
abuse )
echoerr "Error: Username is blocked due to abuse."
echo "Fix your config and then manually remove $_statusFile to reset the client blockade"
echo "If it still fails file an issue at github or try another client :)"
return 1
;;
911 | 5* )
delta=$(( $(date +%s) - _eventTime ))
if [[ $delta -lt 1800 ]]; then
echoerr "$_status: The provider currently has an fatal error. DynB will wait for next update until 30 minutes have passed since last request, $(date --date=@$delta -u +%M) minutes already passed."
return 1
else
rm "$_statusFile"
fi
return 0
;;
* )
if [[ _errorCounter -gt 1 ]]; then
echoerr "Error: An unknown response code has repeatedly been received. $_response"
return 1
else
return 0
fi
;;
esac
}
# requires parameter
# 1. param: 4 or 6 for IP version
function ipHasChanged() {
if [[ ${1} == 4 ]]; then
remote_ip=$(getRemoteIP 4 $_ipv4_checker)
if [[ $_update_method == domrobot ]]; then
dns_ip=$(getDNSIP A)
else
dns_ip=$(dig @${_DNS_checkServer} in a +short "$_dyn_domain")
fi
fi
if [[ ${1} == 6 ]]; then
remote_ip=$(getRemoteIP 6 $_ipv6_checker)
if [[ $_update_method == domrobot ]]; then
dns_ip=$(getDNSIP AAAA)
else
dns_ip=$(dig @${_DNS_checkServer} in aaaa +short "$_dyn_domain")
fi
fi
if [[ ${1} == 4 ]]; then
_new_IPv4=$remote_ip
#echo "New IPv4: $_new_IPv4 old was: $dns_ip"
else
_new_IPv6=$remote_ip
#echo "New IPv6: $_new_IPv6 old was: $dns_ip"
fi
if [[ "$remote_ip" == "$dns_ip" ]]; then
return 0
else
return 1
fi
}
###############
## arguments ##
###############
ARGS=
if [[ $_has_getopt == "" ]] && [[ $(uname) == Linux ]]; then
ARGS=$(getopt --options "hvi:,d:,m:,s:,u:,p:,t:" --longoptions "help,version,link,ip-mode:,domain:,update-method:,service-provider:,username:,password:,token:,reset" -- "$@");
fi
eval set -- "$ARGS";
unset ARGS
function processParameters() {
while true; do
case $1 in
-h | --help )
echo "$_help_message"
exit 0
;;
-v | --version )
echo "$_version"
exit 0
;;
--link )
ln --verbose --symbolic "$(realpath "$0")" "$HOME/.local/bin/dynb"
exit 0
;;
-i | --ip-mode )
_ip_mode=$2
shift 2
;;
-d | --domain )
_dyn_domain=$2
shift 2
;;
-m | --update-method )
_update_method=$2
shift 2
;;
-s | --service-provider )
_serviceProvider=$2
shift 2
;;
-u | --username )
_username=$2
shift 2
;;
-p | --password )
_password=$2
shift 2
;;
-t | --token )
_token=$2
shift 2
;;
--reset )
rm --verbose "$_statusFile"
exit 0
;;
--)
shift
break
esac
done
}
##################
## dependencies ##
##################
function checkDependencies() {
## If there will be more general dependencies use a loop
# for i in curl and some other stuff; do
# if ! command -v $i >/dev/null 2>&1; then
# echoerr "Error: could not find \"$i\", DynB depends on it. "
# exit 1
# fi
# done
[[ -x $(command -v jq 2> /dev/null) ]] || {
if [[ $_update_method != dyndns* ]]; then
echo "This script depends on jq and it is not available." >&2
exit 1
fi
}
# maybe replace this with matejak/argbash
[[ -x $(command -v getopt 2> /dev/null) ]] || {
_has_getopt=false
}
}
function doUnsets() {
unset _network_interface
unset _DNS_checkServer
unset _dns_records
unset _dyn_domain
unset _has_getopt
unset _help_message
unset _INWX_JSON_API_URL
unset _ip_mode
unset _ipv4_checker
unset _ipv6_checker
unset _is_IPv4_enabled
unset _is_IPv6_enabled
unset _main_domain
unset _new_IPv4
unset _new_IPv6
unset _password
unset _username
unset _serviceProvider
unset _version
}
#################
## MAIN method ##
#################
function dynb() {
## parameters and checks
checkDependencies
# shellcheck source=.env
if test -f "$_configFile"; then
# shellcheck disable=SC1091
source "$_configFile"
else
alternativeConfig="$(dirname "$(realpath "$0")")/.env"
if test -f "$alternativeConfig"; then
# shellcheck disable=SC1091
source "$alternativeConfig"
fi
fi
if test -f "$_statusFile"; then
debugMessage "read previous status file"
# shellcheck disable=SC1090
source "$_statusFile"
fi
if [[ $_has_getopt == "" ]] && [[ $(uname) == Linux ]]; then
processParameters "$@"
fi
if [[ $_network_interface != "" ]]; then
_interface_str="--interface $_network_interface"
fi
if [[ $_ip_mode == d* ]]; then
_is_IPv4_enabled=true
_is_IPv6_enabled=true
fi
if [[ $_ip_mode == *4* ]]; then
_is_IPv4_enabled=true
fi
if [[ $_ip_mode == *6* ]]; then
_is_IPv6_enabled=true
fi
## execute operations
if [[ $_update_method == "domrobot" ]]; then
getMainDomain
fetchDNSRecords
if [[ $_is_IPv4_enabled == true ]]; then
ipHasChanged 4
if [[ $? == 1 ]]; then
updateRecord 4
fi
fi
if [[ $_is_IPv6_enabled == true ]]; then
ipHasChanged 6
if [[ $? == 1 ]]; then
updateRecord 6
fi
fi
fi
if [[ $_update_method == "dyndns" ]]; then
changed=0
if [[ $_is_IPv4_enabled == true ]]; then
ipHasChanged 4
(( changed += $? ))
fi
if [[ $_is_IPv6_enabled == true ]]; then
ipHasChanged 6
(( changed += $? ))
fi
if [[ $changed -gt 0 ]]; then
if checkStatus; then
debugMessage "checkStatus has no errors"
if dynupdate; then
debugMessage "DynDNS2 update success"
else
debugMessage "Save new status after dynupdate has failed"
setStatus "$_response" "$(date +%s)" $(( _errorCounter += 1 )) "$_dyn_domain" "${_username}${_token}"
fi
else
debugMessage "Skip DynDNS2 update, checkStatus fetched previous error."
fi
fi
fi
doUnsets
return 0
}
######################
## END MAIN section ##
######################
dynb "${@}"
exit $?

7
example.env Normal file
View File

@@ -0,0 +1,7 @@
_dyn_domain=dyndns.example.com
_serviceProvider=inwx
_update_method=domrobot
_ip_mode=dual
_username=User42
_password=SuperSecretPassword
_token=