initial commit
This commit is contained in:
commit
8639bdb81b
|
@ -0,0 +1,170 @@
|
||||||
|
#!/run/current-system/sw/bin/bash
|
||||||
|
|
||||||
|
# homeserverdns - https://ulrich.earth/code/homeserverdns
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Christian Ulrich
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
readonly required_config_options=("protocol" "auth_key" "domains")
|
||||||
|
readonly default_ttl=300
|
||||||
|
readonly default_public_ip4_hook="upnpc -s | grep ExternalIPAddress | cut -d ' ' -f3"
|
||||||
|
readonly default_update_hook="./homeserverdns-update"
|
||||||
|
|
||||||
|
|
||||||
|
### () -> default_network_interface
|
||||||
|
function get_interface {
|
||||||
|
ip -o -6 route show to default | cut -d " " -f5
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### (ip_address) -> bool
|
||||||
|
function is_unique_local_address {
|
||||||
|
prefix=$(echo $1 | cut -d ":" -f1)
|
||||||
|
[ "${prefix}" == "fd00" ] && echo true || echo false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### (ip_oneline_output) -> ip6_address
|
||||||
|
function extract_ip6 {
|
||||||
|
ip_address=$(echo $1 | tr -s " " | cut -d " " -f4 | cut -d "/" -f1)
|
||||||
|
[ "$(is_unique_local_address $ip_address)" == false ] && echo $ip_address || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### Returns an IPv6 address extracted from a oneline output of ip monitor if
|
||||||
|
### it's global, used for routing and still in use. Returns "" otherwise.
|
||||||
|
### (ip_monitor_output) -> ip6_address
|
||||||
|
function parse_ip_monitor_output {
|
||||||
|
filtered=$(echo $1 | grep -Ev '^Deleted|temporary|deprecated|noprefixroute|preferred_lft 0sec' | grep "scope global")
|
||||||
|
[ -n "${filtered}" ] && echo $(extract_ip6 "${filtered}") || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### (config) -> external_ip4_address
|
||||||
|
function lookup_ip4 {
|
||||||
|
eval "declare -A config="${1#*=}
|
||||||
|
local public_ip4_hook=${config["public_ip4_hook"]}
|
||||||
|
echo $(eval ${public_ip4_hook:-$default_public_ip4_hook})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### () -> global_ip6_address
|
||||||
|
function lookup_ip6 {
|
||||||
|
iface=$(get_interface)
|
||||||
|
ip_output=$(ip -6 -o address show dev $iface scope global -temporary -deprecated -noprefixroute | head -n1)
|
||||||
|
echo $(extract_ip6 "${ip_output}")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### (config_path) -> config
|
||||||
|
function parse_config {
|
||||||
|
declare -A config
|
||||||
|
while IFS= read -r line
|
||||||
|
do
|
||||||
|
if [ -n "${line}" ] && [[ $line != \#* ]]; then
|
||||||
|
local key value
|
||||||
|
IFS="=" read -r key value <<< $line
|
||||||
|
[ -n "${key}" ] && [ -n "${value}" ] && config[$key]=$value
|
||||||
|
fi
|
||||||
|
done < $1
|
||||||
|
declare -p config
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### utility function for joining strings
|
||||||
|
### (delimiter, list) -> joined_list
|
||||||
|
function join_by {
|
||||||
|
local IFS=$1
|
||||||
|
shift
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### utility function for splitting a full-qualified domain name
|
||||||
|
### (fqdn) -> (record_name, second_level_domain)
|
||||||
|
function split_domain {
|
||||||
|
local tokens=(${1//./ })
|
||||||
|
local token_count=${#tokens[*]}
|
||||||
|
local second_level_domain=${tokens[@]:$((token_count - 2))}
|
||||||
|
if [ "${token_count}" -gt 2 ]; then
|
||||||
|
local record_name=${tokens[@]:0:$((token_count - 2))}
|
||||||
|
else
|
||||||
|
local record_name=("@")
|
||||||
|
fi
|
||||||
|
echo $(join_by . ${record_name[@]})" "$(join_by . ${second_level_domain[@]})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### (ip4, ip6, config) -> log_output
|
||||||
|
function update {
|
||||||
|
eval "declare -A config="${3#*=}
|
||||||
|
local domains=(${config["domains"]})
|
||||||
|
local update_hook=${config["update_hook"]}
|
||||||
|
export PROTOCOL=${config["protocol"]}
|
||||||
|
export API_ADDRESS=${config["api_address"]}
|
||||||
|
export USER=${config["user"]}
|
||||||
|
export AUTH_KEY=${config["auth_key"]}
|
||||||
|
#export IP4=$1
|
||||||
|
#export IP6=$2
|
||||||
|
export TTL=${config["ttl"]:-$default_ttl}
|
||||||
|
for domain in "${domains[@]}"; do
|
||||||
|
local record_name second_level_domain
|
||||||
|
read -r record_name second_level_domain <<< $(split_domain $domain)
|
||||||
|
export DOMAIN=$domain
|
||||||
|
export L2_DOMAIN=$second_level_domain
|
||||||
|
export RECORD_NAME=$record_name
|
||||||
|
echo "[$(date "+%Y-%m-%d %H:%M")] updating ${domain} ..."
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
echo -n "| setting A=$1 ... "
|
||||||
|
echo "status: "$(eval "IP4=$1 bash ${update_hook:-$default_update_hook}")
|
||||||
|
fi
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
echo -n "| setting AAAA=$2 ... "
|
||||||
|
echo "status: "$(eval "IP6=$2 bash ${update_hook:-$default_update_hook}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function main {
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
echo "usage: $0 CONFIG_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
config=$(parse_config $1)
|
||||||
|
eval "declare -A config="${config#*=}
|
||||||
|
for key in "${required_config_options[@]}"; do
|
||||||
|
if [ -z "${config[$key]}" ]; then
|
||||||
|
echo "configuration is missing option \"${key}\"" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
local ip4=$(lookup_ip4 "$(declare -p config)")
|
||||||
|
local ip6=$(lookup_ip6)
|
||||||
|
update "${ip4}" "${ip6}" "$(declare -p config)"
|
||||||
|
ip -6 -o monitor address dev $(get_interface) |
|
||||||
|
while IFS= read -r line
|
||||||
|
do
|
||||||
|
ip4=$(lookup_ip4 "$(declare -p config)")
|
||||||
|
local new_ip6=$(parse_ip_monitor_output "${line}")
|
||||||
|
#echo "ip-monitor output was ${line}"
|
||||||
|
if [ -n "${new_ip6}" ] && [ "${new_ip6}" != "${ip6}" ]; then
|
||||||
|
ip6=$new_ip6
|
||||||
|
update "${ip4}" "${ip6}" "$(declare -p config)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/run/current-system/sw/bin/bash
|
||||||
|
|
||||||
|
# homeserverdns - https://ulrich.earth/code/homeserverdns
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Christian Ulrich
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
# This script is called by homeserverdns-daemon for updating A/AAAA records.
|
||||||
|
# These environment variables will be set by homeserverdns-daemon:
|
||||||
|
#
|
||||||
|
# PROTOCOL protocol name (gandi|dyndns|httpnet)
|
||||||
|
# API_ADDRESS address for API requests, required for dnydns protocol
|
||||||
|
# USER user name for authentication at the API
|
||||||
|
# AUTH_KEY authentication token for authentication at the API
|
||||||
|
# DOMAIN fqdn for which A/AAAA records will be set
|
||||||
|
# L2_DOMAIN second-level domain for which A/AAAA records will be set
|
||||||
|
# RECORD_NAME name of the record to be set
|
||||||
|
# IP4 new value of the A record (IP4 or IP6 will be set exclusively)
|
||||||
|
# IP6 new value of the AAAA record (IP4 or IP6 will be set exclusively)
|
||||||
|
# TTL new TTL value for the A/AAAA records, required for gandi protocol
|
||||||
|
|
||||||
|
|
||||||
|
### gandi.net LiveDNS API
|
||||||
|
### () -> status_code
|
||||||
|
function update_gandi {
|
||||||
|
local url="https://dns.api.gandi.net/api/v5/domains/${L2_DOMAIN}/records/${RECORD_NAME}"
|
||||||
|
local ip=""
|
||||||
|
if [ -n "${IP4}" ]; then
|
||||||
|
url="${url}/A"
|
||||||
|
ip=$IP4
|
||||||
|
elif [ -n "${IP6}" ]; then
|
||||||
|
url="${url}/AAAA"
|
||||||
|
ip=$IP6
|
||||||
|
fi
|
||||||
|
status=$(curl -o /dev/null -s -w "%{http_code}\n" \
|
||||||
|
-X PUT \
|
||||||
|
-H "Content-Type:application/json" \
|
||||||
|
-H "X-Api-Key:${AUTH_KEY}" \
|
||||||
|
-d "{\"rrset_ttl\":${TTL},\"rrset_values\":[\"${ip}\"]}" \
|
||||||
|
$url)
|
||||||
|
echo "${status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: untested
|
||||||
|
### http.net DNS API v1
|
||||||
|
### () -> status_code
|
||||||
|
function update_httpnet {
|
||||||
|
local current_ip4=$(dig A +short $DOMAIN)
|
||||||
|
local current_ip6=$(dig AAAA +short $DOMAIN)
|
||||||
|
local url="https://partner.http.net/api/dns/v1/json/zoneUpdate"
|
||||||
|
local items_add=""
|
||||||
|
local items_delete=""
|
||||||
|
if [ -n "${IP4}" ]; then
|
||||||
|
items_add="{\"name\":\"${DOMAIN}\",\"type\":\"A\",\"content\":\"${IP4}\",\"ttl\":\"${TTL}\"}"
|
||||||
|
elif [ -n "${IP6}" ]; then
|
||||||
|
items_add="{\"name\":\"${DOMAIN}\",\"type\":\"AAAA\",\"content\":\"${IP6}\",\"ttl\":\"${TTL}\"}"
|
||||||
|
fi
|
||||||
|
if [ -n "${current_ip4}" ]; then
|
||||||
|
items_delete="{\"name\":\"${DOMAIN}\",\"type\":\"A\",\"content\":\"${current_ip4}\"}"
|
||||||
|
fi
|
||||||
|
if [ -n "${current_ip6}" ]; then
|
||||||
|
items_delete="{\"name\":\"${DOMAIN}\",\"type\":\"AAAA\",\"content\":\"${current_ip6}\"}"
|
||||||
|
fi
|
||||||
|
status=$(curl -o /dev/null -s -w "%{http_code}\n" \
|
||||||
|
-X PUT \
|
||||||
|
-d "{\"authToken\":\"${AUTH_KEY}\"," \
|
||||||
|
"\"zoneConfig\":{\"name\":\"${L2_DOMAIN}\"}," \
|
||||||
|
"\"recordsToAdd\":[${items_add}]," \
|
||||||
|
"\"recordsToDelete\":[${items_delete}]}" \
|
||||||
|
$url)
|
||||||
|
echo "${status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: untested
|
||||||
|
### dyndns API v2/v3 API
|
||||||
|
### () -> status_code
|
||||||
|
function update_dyndns {
|
||||||
|
local hostname=${L2_DOMAIN}
|
||||||
|
if [ "${RECORD_NAME}" != "@" ]; then
|
||||||
|
local hostname=$DOMAIN
|
||||||
|
fi
|
||||||
|
local address=${IP4:-$IP6}
|
||||||
|
local url="https://${USER}:${AUTH_KEY}@${API_ADDRESS}?hostname=${hostname}&myip=${address}"
|
||||||
|
status=$(curl -o /dev/null -s -w "%{http_code}\n" $url)
|
||||||
|
echo "${url} ${status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
eval "update_${PROTOCOL}"
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Protocol name, may be left empty when using a custom update_hook.
|
||||||
|
# The default update_hook supports: gandi, dyndns
|
||||||
|
protocol=
|
||||||
|
|
||||||
|
# The command to be called to determine our public IP address. By default UPNP
|
||||||
|
# will be used
|
||||||
|
public_ip4_hook=
|
||||||
|
|
||||||
|
# The command to be called when an IP change is detected. Environment variables
|
||||||
|
# will be set when calling it (for documentation see homeserverdns-update).
|
||||||
|
# Default: ./homeserverdns-update
|
||||||
|
update_hook=
|
||||||
|
|
||||||
|
# The address of the API, required for dyndns protocol.
|
||||||
|
# Example: dyndns.strato.com/nic/update
|
||||||
|
api_address=
|
||||||
|
|
||||||
|
# User name for authentication at the API (not all protocols require this).
|
||||||
|
user=
|
||||||
|
|
||||||
|
# Token used for authentication at the API
|
||||||
|
auth_key=
|
||||||
|
|
||||||
|
# Time-to-live for the A/AAAA records (not all protocols support this).
|
||||||
|
# default: 300
|
||||||
|
ttl=
|
||||||
|
|
||||||
|
# A space separated list of the domains for which the A/AAAA records shall be
|
||||||
|
# updated. For each domain the update_hook will be called.
|
||||||
|
domains=
|
||||||
|
|
Loading…
Reference in New Issue