Skip to content

Instantly share code, notes, and snippets.

@sixtyfive
Last active January 18, 2025 00:42
Show Gist options
  • Save sixtyfive/ef1480538a4cf6928ff690ca8dc5dfad to your computer and use it in GitHub Desktop.
Save sixtyfive/ef1480538a4cf6928ff690ca8dc5dfad to your computer and use it in GitHub Desktop.
DIY dDNS service script using Hetzner's DNS API
#!/usr/bin/env ruby
# on OpenRC systems, place into e.g. /etc/periodic/15min
# (or create /etc/periodic/1min and add line to root's crontab)
# on systemd systems, install it as a timer
# create/find these in Hetzner's DNS admin interface at https://dns.hetzner.com/
API_TOKEN_NAME="..."
API_TOKEN="..."
ZONES=["...", "..."] # TLD; must be registered at https://robot.hetzner.com/domain
RECORDS=["...", "..."] # list of subdomains, also specified there
# will be a bit faster if specified
# ZONE_ID="..."
# RECORD_ID="..."
require 'json'
require 'socket'
ZONES.each do |zone|
@zone_id = ZONE_ID ? ZONE_ID : (
json = JSON.parse `curl -s "https://dns.hetzner.com/api/v1/zones" -H "Auth-API-Token: #{API_TOKEN}"`
zones_json = json.delete('zones')
zones_json.find{|x| x['name']==zone}['id']
)
json = JSON.parse `curl -s "https://dns.hetzner.com/api/v1/records?zone_id=#{@zone_id}" -H "Auth-API-Token: #{API_TOKEN}"`
records_json = json.delete('records')
RECORDS.each do |record|
record_json = records_json.find{|x| x['name']==record}
@record_id ||= record_json['id']
current_record_ip = record_json['value']
current_wan_ip = Socket.getifaddrs
.map{|iface| [iface.name, iface.addr.ip_address] if iface.addr.ipv4? if iface.addr}
.compact
.find{|x| x[0]=='ppp0'}[1]
next if current_record_ip == current_wan_ip
system %Q(
curl -X "PUT" "https://dns.hetzner.com/api/v1/records/#{@record_id}" \\
-H "Content-Type: application/json" \\
-H "Auth-API-Token: #{API_TOKEN}" \\
-d $'{
"value": "#{current_wan_ip}",
"ttl": 60,
"type": "A",
"name": "#{record}",
"zone_id": "#{@zone_id}"
}'
)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment