From 7994df3ed99529bc79db484231b14eb5f9ad930c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tollk=C3=BChn?= Date: Sat, 13 Jun 2026 02:01:45 +0200 Subject: [PATCH] migrate git repo to repo-format 0 --- LICENSE | 2 +- README.md | 107 ++++++++++++++++++++ actions_netcup_ddns.conf | 6 ++ install.sh | 20 ++++ update_netcup_ddns.sh | 205 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 actions_netcup_ddns.conf create mode 100644 install.sh create mode 100644 update_netcup_ddns.sh diff --git a/LICENSE b/LICENSE index cb6d075..54c4f66 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 stefan.tollkuehn +Copyright (c) 2023 Falk Lubiniecki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/README.md b/README.md index 5f33ed6..8eaa5d8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,109 @@ # opnsense-action-netcup-ddns +Dynamic DNS for opnsense for the netcup api as cronjob action. + +This repository contains the cron job action for opnsense and a shell script to use the ddns api of netcup. With this you can update multiple dns records on different domains via opnsense cron jobs. + +This is a fork of https://github.com/lubbyhst/opnsense-action-netcup-ddns + +The fork is able to update IPv4 as well as IPv6 records and set custom hostname via commandline parameter + +## Installation + +Clone this repo on your opnsense installation + +```bash +git clone https://github.com/lubbyhst/opnsense-action-netcup-ddns.git +cd opnsense-action-netcup-ddns +``` + +Run install script + +```bash +chmod +x ./install.sh && ./install.sh +``` + +Obtain DNS record id + +If your DNS Type A record does not exists. Create it via the CCP Gui. After that you can optain the id via this script. You need to modify the parameters for your account. After executing the script you find you dns record and use the id of your record in the cron job. + +```bash +DOMAIN_NAME="test.com" +NC_Apikey="apikey" +NC_Apipw="apipw" +NC_CID="customer_id" +NC_ENDPOINT='https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON' + +echo "Try logging in" +tmp=$(curl -X POST \ + $NC_ENDPOINT \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "action":"login", + "param":{ + "apikey": "'$NC_Apikey'", + "apipassword": "'$NC_Apipw'", + "customernumber": "'$NC_CID'" + } +}') + +sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4) +curl -X POST \ + $NC_ENDPOINT \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "action":"infoDnsRecords", + "param":{ + "apikey": "'$NC_Apikey'", + "apisessionid": "'$sid'", + "customernumber": "'$NC_CID'", + "clientrequestid": "", + "domainname": "'$DOMAIN_NAME'" + } +}' +``` + +for better readability pipe this into `jq` + + +### Example output +```json +{ + "serverrequestid": "dgZmd0p4j=YFhdsdo", + "clientrequestid": "", + "action": "infoDnsRecords", + "status": "success", + "statuscode": 2000, + "shortmessage": "DNS records found", + "longmessage": "DNS Records for this zone were found.", + "responsedata": { + "dnsrecords": [ + { + "id": "54356783", + "hostname": "testserver", + "type": "A", + "priority": "0", + "destination": "82.66.268.178", + "deleterecord": false, + "state": "yes" + } + ] + } +} +``` + +## Configure cron job + +1. Open opnsense webui +2. go to System -> Settings -> Cron +3. Click on [+] to add a new cron task +4. Define the cron interval eg. * * * * * for every minute +5. Select the command "Netcup DDNS update" +6. Set the parameters in the following order [DOMAIN_NAME DNS_RECORD_ID NC_ApiKey NC_Apipw NC_CID] +7. Define a description +8. Click save +9. Click Confirm to activate the cron task + +After a successfull run, you should see the correct external ip in your ccp dns view. diff --git a/actions_netcup_ddns.conf b/actions_netcup_ddns.conf new file mode 100644 index 0000000..5ad615d --- /dev/null +++ b/actions_netcup_ddns.conf @@ -0,0 +1,6 @@ +[start] +command: update_netcup_ddns.sh +parameters: %s %s %s %s %s %s %s +type:script +message: Netcup Dynamic DNS Update +description: Netcup DDNS update diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..360bbb2 --- /dev/null +++ b/install.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +IS_OPNSENSE=$(cat /etc/shells | grep opnsense-shell) + +if [ ! -z $IS_OPNSENSE ]; then + echo "copy action conf" + cp ./actions_netcup_ddns.conf /usr/local/opnsense/service/conf/actions.d/actions_netcup_ddns.conf + echo "reload action config" + service configd restart + echo "copy update script to bin folder" + cp ./update_netcup_ddns.sh /usr/local/bin/update_netcup_ddns.sh + echo "make script executable" + chmod +x /usr/local/bin/update_netcup_ddns.sh + echo "install done" +else + echo "Could not detect OPNsense environment! Exiting doing nothing!" + exit 1 +fi diff --git a/update_netcup_ddns.sh b/update_netcup_ddns.sh new file mode 100644 index 0000000..0e11966 --- /dev/null +++ b/update_netcup_ddns.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash + +# This script will update given netcup domain dns record with the actual ip of the calling service +# Parameters: +# IPVERSION: 4 or 6 +# DOMAIN_NAME: the zone name eg. test.com +# DNS_RECORD_ID: the id of the dns record that should be updated +# HOSTNAMERECORD: the hostname in domain +# NC_Apikey: Api Key from your netcup account +# NC_Apipw: Api password from your netcup account +# NC_CID: Customer ID from your netcup account + +IPVERSION=$1 +DOMAIN_NAME=$2 +DNS_RECORD_ID=$3 +HOSTNAMERECORD=$4 +NC_Apikey=$5 +NC_Apipw=$6 +NC_CID=$7 +NC_ENDPOINT='https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON' +CACHE_PATH="/var/cache/update_netcup_ddns" + +usage() { + # + # Displays usage of script + # + # returns usage + # + + echo + echo "./update_netcup_ddns.sh " + echo +} + +query_ip_address() { + # + # Queries the IP address in use + # + # returns IP address or exits with log message + # + + local IPVERSION=$1 + local CACHE_PATH=$2 + + case $IPVERSION in + 4) + QUERY_ADDRESS=$(curl -s -4 -q v4.ident.me | grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])') + STATEFILE="$CACHE_PATH/ddns_state_ipv"$IPVERSION"_$DOMAIN_NAME.txt" + ;; + 6) + QUERY_ADDRESS=$(curl -s -6 -q v6.ident.me | grep -oE '^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$') + STATEFILE="$CACHE_PATH/ddns_state_ipv"$IPVERSION"_$DOMAIN_NAME.txt" + ;; + *) + usage + exit 1 + ;; + esac + + if [ ! -d $CACHE_PATH ]; then mkdir -p $CACHE_PATH; fi + if [ ! -f $STATEFILE ]; then touch $STATEFILE; fi + + if [ ! -z $QUERY_ADDRESS ]; then + echo $QUERY_ADDRESS + else + logger -t $(basename $0) -p user.err "Unable to query IP address" + exit 255; + fi +} + +get_auth_token() { + # + # Get auth token + # + # returns auth_token + # + + echo $(curl -s -X POST \ + $NC_ENDPOINT \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "action":"login", + "param":{ + "apikey": "'$NC_Apikey'", + "apipassword": "'$NC_Apipw'", + "customernumber": "'$NC_CID'" + } + }') +} + +determine_record_type() { + # + # Determine dns record type + # + # returns dns record type (currently support A and AAAA) + # + + local IPVERSION=$1 + + if [ $IPVERSION -eq 4 ]; then + RECORDTYPE="A" + elif [ $IPVERSION -eq 6 ]; then + RECORDTYPE="AAAA" + else + logger -t $(basename $0) -p user.err "Could not determine IP version. Exiting" + exit 1 + fi + + echo $RECORDTYPE +} + +get_ip_from_state() { + # + # Get IP address currently set in DNS + # + # returns address set in DNS + # + +# local IPVERSION=$1 +# local CACHE_PATH=$2 +# +# STATEFILE="$CACHE_PATH/ddns_state_ipv"$IPVERSION"_$DOMAIN_NAME.txt" +# LAST_ADDRESS=$(<$STATEFILE) +# +# echo $LAST_ADDRESS + + sid=$(get_auth_token | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4) + curl -s -X POST \ + $NC_ENDPOINT \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "action":"infoDnsRecords", + "param":{ + "apikey": "'$NC_Apikey'", + "apisessionid": "'$sid'", + "customernumber": "'$NC_CID'", + "clientrequestid": "", + "domainname": "'$DOMAIN_NAME'" + } + }' | jq -r '.responsedata.dnsrecords[] | select(.id == "'$DNS_RECORD_ID'") | .destination' +} + +update_dns_record() { + # + # Update DNS record + # + # returns the IP address updated in DNS + # + + update=$(curl -s -X POST \ + $NC_ENDPOINT \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "action":"updateDnsRecords", + "param":{ + "apikey": "'$NC_Apikey'", + "apisessionid": "'$sessionid'", + "customernumber": "'$NC_CID'", + "clientrequestid": "", + "domainname": "'$DOMAIN_NAME'", + "dnsrecordset": { + "dnsrecords": [ + { + "id": "'$DNS_RECORD_ID'", + "hostname": "'$HOSTNAMERECORD'", + "type": "'$(determine_record_type $IPVERSION)'", + "destination": "'$QUERIED_ADDRESS'" + } + ] + } + } + }') + + echo $update | jq -r '.responsedata.dnsrecords[] | select(.id == "'$DNS_RECORD_ID'") | .destination' +} + +QUERIED_ADDRESS=$(query_ip_address $IPVERSION "$CACHE_PATH") +LAST_ADDRESS=$(get_ip_from_state) + +# If IP address hasn't changed, exit, otherwise save the new IP +if [ "$QUERIED_ADDRESS" == "$LAST_ADDRESS" ]; then + logger -t $(basename $0) -p user.notice "No update necessary for $HOSTNAMERECORD.$DOMAIN_NAME ($(determine_record_type $IPVERSION))!" + exit 0 +fi + +echo $QUERIED_ADDRESS > "$CACHE_PATH/ddns_state_ipv"$IPVERSION"_$DOMAIN_NAME.txt" + +sessionid=$(get_auth_token | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4) +if [ "$sessionid" = "" ]; then + logger -t $(basename $0) -p user.err "SessionID was empty. Login failed." + exit 1 +fi +logger -t $(basename $0) -p user.notice "Successfully logged in" +logger -t $(basename $0) -p user.notice "Try registering new IP for FQDN $HOSTNAMERECORD.$DOMAIN_NAME ($QUERIED_ADDRESS - $(determine_record_type $IPVERSION))" + +update=$(update_dns_record) + +if [ "$update" == "$QUERIED_ADDRESS" ]; then + logger -t $(basename $0) -p user.notice "Finished updating domain $DOMAIN_NAME" +else + logger -t $(basename $0) -p user.err "Could not update $HOSTNAMERECORD.$DOMAIN_NAME ($QUERIED_ADDRESS - $(determine_record_type $IPVERSION))" +fi