ddns-scripts: add Hetzner Cloud support
authorChristopher Obbard <redacted>
Sat, 31 Jan 2026 01:47:44 +0000 (02:47 +0100)
committerFlorian Eckert <redacted>
Tue, 3 Feb 2026 07:32:33 +0000 (08:32 +0100)
Add a new Hetzner DDNS provider using the Hetzner Cloud API
(api.hetzner.cloud) with Bearer token authentication.

Configuration guide:
* set [domain] to domain
* set [username] to subdomain (without domain)
* set [password] to Bearer API key

Signed-off-by: Christopher Obbard <redacted>
net/ddns-scripts/Makefile
net/ddns-scripts/files/usr/lib/ddns/update_hetzner_cloud.sh [new file with mode: 0755]
net/ddns-scripts/files/usr/share/ddns/default/hetzner.com.json [new file with mode: 0644]
net/ddns-scripts/files/usr/share/ddns/list

index c3983e077372d900f675fe3b50bce9a4ea1ef0e8..e9ac12dbb32e171af40fda64c87fc05bbd7d9ef3 100755 (executable)
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=ddns-scripts
 PKG_VERSION:=2.8.2
-PKG_RELEASE:=86
+PKG_RELEASE:=87
 
 PKG_LICENSE:=GPL-2.0
 
@@ -124,6 +124,17 @@ define Package/ddns-scripts-godaddy/description
   Dynamic DNS Client scripts extension for 'godaddy.com API v1'.
 endef
 
+define Package/ddns-scripts-hetzner-cloud
+  $(call Package/ddns-scripts/Default)
+  TITLE:=Extension for hetzner.com cloud API
+  DEPENDS:=ddns-scripts +curl
+  PROVIDES:=ddns-scripts_hetzner-cloud
+endef
+
+define Package/ddns-scripts-hetzner-cloud/description
+  Dynamic DNS Client scripts extension for 'hetzner.com cloud API'.
+endef
+
 define Package/ddns-scripts-namesilo
   $(call Package/ddns-scripts/Default)
   TITLE:=Extension for namesilo.com API v1
@@ -446,6 +457,7 @@ define Package/ddns-scripts-services/install
        rm $(1)/usr/share/ddns/default/cloud.google.com-v1.json
        rm $(1)/usr/share/ddns/default/freedns.42.pl.json
        rm $(1)/usr/share/ddns/default/godaddy.com-v1.json
+       rm $(1)/usr/share/ddns/default/hetzner.com.json
        rm $(1)/usr/share/ddns/default/namesilo.com-v1.json
        rm $(1)/usr/share/ddns/default/digitalocean.com-v2.json
        rm $(1)/usr/share/ddns/default/dnspod.cn.json
@@ -570,6 +582,24 @@ fi
 exit 0
 endef
 
+define Package/ddns-scripts-hetzner/install
+       $(INSTALL_DIR) $(1)/usr/lib/ddns
+       $(INSTALL_BIN) ./files/usr/lib/ddns/update_hetzner_cloud.sh \
+               $(1)/usr/lib/ddns
+
+       $(INSTALL_DIR) $(1)/usr/share/ddns/default
+       $(INSTALL_DATA) ./files/usr/share/ddns/default/hetzner.com.json \
+               $(1)/usr/share/ddns/default
+endef
+
+define Package/ddns-scripts-hetzner/prerm
+#!/bin/sh
+if [ -z "$${IPKG_INSTROOT}" ]; then
+       /etc/init.d/ddns stop
+fi
+exit 0
+endef
+
 define Package/ddns-scripts-namesilo/install
        $(INSTALL_DIR) $(1)/usr/lib/ddns
        $(INSTALL_BIN) ./files/usr/lib/ddns/update_namesilo_com_v1.sh \
@@ -917,6 +947,7 @@ $(eval $(call BuildPackage,ddns-scripts-cloudflare))
 $(eval $(call BuildPackage,ddns-scripts-gcp))
 $(eval $(call BuildPackage,ddns-scripts-freedns))
 $(eval $(call BuildPackage,ddns-scripts-godaddy))
+$(eval $(call BuildPackage,ddns-scripts-hetzner))
 $(eval $(call BuildPackage,ddns-scripts-namesilo))
 $(eval $(call BuildPackage,ddns-scripts-digitalocean))
 $(eval $(call BuildPackage,ddns-scripts-dnspod))
diff --git a/net/ddns-scripts/files/usr/lib/ddns/update_hetzner_cloud.sh b/net/ddns-scripts/files/usr/lib/ddns/update_hetzner_cloud.sh
new file mode 100755 (executable)
index 0000000..07b0384
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# Script to update Hetzner DNS Records using the Cloud API (api.hetzner.cloud)
+#
+# 2026 Christopher Obbard <obbardc@gmail.com>
+#
+# Options passed from /etc/config/ddns:
+# Domain   - the zone name in Hetzner Console (e.g. `example.com`)
+# Username - the RRset name within the zone (e.g. `www`)
+# Password - Hetzner Console API token (Bearer token)
+#
+# Reference: https://docs.hetzner.cloud/reference/cloud#tag/zone-rrset-actions/set_zone_rrset_records
+
+# Hetzner API base URL
+__API="https://api.hetzner.cloud/v1"
+
+# Hetzner API requires a comment. An empty string is fine.
+comment=""
+
+. /usr/share/libubox/jshn.sh
+
+# Check CURL exists
+[ -z "$CURL" ] || [ -z "$CURL_SSL" ] && {
+       write_log 14 "Hetzner Cloud DDNS script requires cURL with SSL support"
+       return 1
+}
+
+# Check options
+[ -z "$username" ] && write_log 14 "Hetzner Cloud DDNS: 'username' (rrset name) not set" && return 1
+[ -z "$password" ] && write_log 14 "Hetzner Cloud DDNS: 'password' (API Token) not set" && return 1
+[ -z "$domain" ] && write_log 14 "Hetzner Cloud DDNS: 'domain' not set (Zone name)" && return 1
+[ "$use_ipv6" -eq 1 ] && type="AAAA" || type="A"
+
+__TYPE="A"
+[ "$use_ipv6" -ne 0 ] && __TYPE="AAAA"
+
+# Create JSON payload for set_records API call
+# Payload:
+# { "records": [ { "value": "<ip>", "comment": "" } ] }
+json_init
+json_add_array "records"
+       json_add_object
+               json_add_string "value" "$__IP"
+               json_add_string "comment" "$comment"
+       json_close_object
+json_close_array
+
+__URL="${__API}/zones/${domain}/rrsets/${username}/${__TYPE}/actions/set_records"
+
+__STATUS=$(curl -Ss -X POST "$__URL" \
+       -H "Authorization: Bearer ${password}" \
+       -H "Content-Type: application/json" \
+       -d "$(json_dump)" \
+       -w "%{http_code}\n" -o "$DATFILE" 2>"$ERRFILE")
+
+if [ $? -ne 0 ]; then
+       write_log 14 "Curl failed (set_records): $(cat "$ERRFILE")"
+       return 1
+fi
+
+# Treat any 2xx as success; otherwise error.
+case "$__STATUS" in
+       200|201|202) return 0 ;;
+       *)
+               write_log 14 "Curl failed (set_records): $__STATUS\nresponse body: $(cat "$DATFILE")"
+               return 1
+               ;;
+esac
diff --git a/net/ddns-scripts/files/usr/share/ddns/default/hetzner.com.json b/net/ddns-scripts/files/usr/share/ddns/default/hetzner.com.json
new file mode 100644 (file)
index 0000000..7bbe027
--- /dev/null
@@ -0,0 +1,9 @@
+{
+       "name": "hetzner.cloud",
+       "ipv4": {
+               "url": "update_hetzner_cloud.sh"
+       },
+       "ipv6": {
+               "url": "update_hetzner_cloud.sh"
+       }
+}
index 8960ca1ee403da83373de59c75808f2935b4eac6..7cbf50791876c2fa78d665255a0f4953ed495e62 100644 (file)
@@ -36,6 +36,7 @@ easydns.com
 goip.de
 google.com
 he.net
+hetzner.com
 hosting.de
 infomaniak.com
 ipnodns.ru
git clone https://git.99rst.org/PROJECT