Skip to content

Feature/hetzner dns provider#5091

Open
ArcanConsulting wants to merge 16 commits intoopnsense:masterfrom
ArcanConsulting:feature/hetzner-dns-provider
Open

Feature/hetzner dns provider#5091
ArcanConsulting wants to merge 16 commits intoopnsense:masterfrom
ArcanConsulting:feature/hetzner-dns-provider

Conversation

@ArcanConsulting
Copy link
Contributor

Summary

New plugin for comprehensive Hetzner DNS management in OPNsense.

Features

  • Multi-account support - Manage multiple Hetzner API tokens
  • Multi-zone DNS management - Handle all your domains from one interface
  • Dynamic DNS with automatic failover between WAN gateways
  • Dual-Stack support - IPv4 (A) and IPv6 (AAAA) records
  • Full DNS record management - Create, edit, delete all record types (A, AAAA, CNAME, MX, TXT, SRV, CAA, etc.)
  • Change history - Track all DNS changes with revert/undo functionality
  • Notifications - Email, Webhook, and Ntfy support
  • Configuration backup/restore

Supports both Hetzner Cloud API and legacy DNS Console API.

Screenshots
dyndns
dnsmanagement
gateways
gateway fail state
gateway failure
gateway recovered
history
settings
spf helper

Technical Details

  • Location: dns/hclouddns
  • Backend: Python 3 with Hetzner DNS API integration
  • Frontend: OPNsense MVC (Volt templates, PHP controllers)
  • Service: Configurable update intervals via cron

Testing

  • Tested on OPNsense 24.7
  • Multiple Hetzner accounts and zones
  • Failover between multiple WAN gateways verified
  • IPv4 and IPv6 record updates confirmed

Checklist

  • Code follows OPNsense plugin conventions
  • Makefile with proper versioning
  • ACL definitions included
  • Menu integration

  Add native support for Hetzner Cloud DNS API (api.hetzner.cloud).
  Hetzner is migrating from dns.hetzner.com to Cloud Console,
  with the old API shutting down in May 2026.

  Features:
  - Bearer token authentication
  - A and AAAA record support
  - Multiple hostnames (comma-separated)
  - Configurable TTL
  ## Features
  - Multi-account support (multiple Hetzner API tokens)
  - Multi-zone DNS management
  - Dynamic DNS with automatic failover between WAN interfaces
  - IPv4 and IPv6 (Dual-Stack) support
  - Direct DNS management (view/edit/delete records)
  - Change history with undo functionality
  - Notifications (Email, Webhook, Ntfy)
  - Configuration backup/restore

  Supports both Hetzner Cloud API and legacy DNS Console API.
  Bugfixes:
  - Fix DNS record edit mode (editing TTL no longer fails with "Failed to create record")
  - Fix error dialog titles showing "Danger" instead of "Error"
  - Add detailed error messages with record info for debugging
  - Fix TTL dropdown not being populated

  Improvements:
  - Integrate notifications into automatic DNS update flow
  - Add global default TTL setting for DynDNS entries (60s default)
  - Add "Save & Apply TTL to All Entries" button
  - Move DynDNS TTL settings from Scheduled to DNS Entries tab
  - Simplified TTL settings UI
  - Fix TTL updates and add TTL dropdown selector
  - Add plugin concepts for UniFi and MikroTik integration
  - Integrate notifications into automatic DNS update flow
  - Add global default TTL setting for DynDNS entries
  - Add "Apply TTL to All Entries" button
  - Fix DNS record edit mode and improve error dialogs
  - Move DynDNS TTL settings from Scheduled to DNS Entries tab
  - Fix TTL dropdown and simplify settings layout
  - Auto-save TTL before applying to all entries
  - Simplify TTL UI to single Save & Apply button
  - Move Import to DNS Entries, add DynDNS button to DNS Management
  - Add grouped record display with filter and search in DNS Management
  ### DNS Management Improvements
  - Sort zone groups alphabetically
  - Persist collapsed group state in localStorage
  - Fix zone search visibility - move to separate container
  - Preserve collapsed group state on refresh
  - Add DNS Management improvements: sorting, search, zone groups

  ### Import & Account Handling
  - Fix Import from Hetzner account dropdown

  ### DKIM & TXT Records
  - Fix DKIM wizard field sizes
  - Improve TXT record value display with smart formatting
  - Show TXT record subtypes (SPF, DKIM, DMARC, Google, MS) in DNS Management

  ### DynDNS Entries
  - Fix default TTL loading from settings API
  - Use default DynDNS TTL when creating entries from DNS Management
  - Mark A/AAAA records already configured as DynDNS with green bolt icon

  ### Record Edit/Delete
  - Add synthetic record IDs for rrsets API
  - Fetch record data live from API for edit/delete
  - Refactor edit/delete to lookup record data from cache
  - Fix edit/delete handlers for new TXT value display

  ### UI/UX
  - Fix gateway auto-selection to use sorted order instead of exact priority values
  - Add grouped record display with filter and search in DNS Management
  - Move Import to DNS Entries, add DynDNS button to DNS Management
  - Simplify TTL UI to single Save & Apply button
@AdSchellevis
Copy link
Member

@ArcanConsulting We're not really sure yet if this fits our plugin scope, reviewing these large amounts of code takes a lot of time and we're not sure about the number of users interested in it. Keeping a project like this alive, also requires a time investment in the long run from your end. Are there already people using this?

In some cases it's better to offer a package from your own infrastructure to avoid maintenance issues in the long run from our end which also makes clearer for the user that this isn't a part of our distribution.

@ArcanConsulting
Copy link
Contributor Author

@ArcanConsulting We're not really sure yet if this fits our plugin scope, reviewing these large amounts of code takes a lot of time and we're not sure about the number of users interested in it. Keeping a project like this alive, also requires a time investment in the long run from your end. Are there already people using this?

In some cases it's better to offer a package from your own infrastructure to avoid maintenance issues in the long run from our end which also makes clearer for the user that this isn't a part of our distribution.

I built this primarily for my own infrastructure - I manage ~30 domains on Hetzner and needed proper multi-zone DynDNS with failover support. The existing solutions didn't cover my requirements.

I understand the review burden for a plugin this size. I'm happy to maintain it as a community package from my own repo - that removes the long-term maintenance concern from your side. If it gains traction and proves stable, we can revisit official inclusion.

Could you point me to docs on setting up a community package repo?

@AdSchellevis
Copy link
Member

building the index is part of the package manager pkg repo is probably what you are looking for (man pkg).

@fichtner
Copy link
Member

The rough sequence is as seen in the tools repo:

https://github.com/opnsense/tools/blob/6890a15b051471dfb82c897a5d15b496c42cab71/build/common.sh#L998-L1098

From a project perspective it's not useful to document how all of this works. Other documentation about it exists in FreeBSD.

Cheers,
Franco

  API Changes (based on Hetzner feedback):
  - Migrate to proper rrset-actions endpoints for record updates
  - Use POST /zones/{zone_id}/rrsets/{name}/{type}/actions/set_records
  - Add async action polling - wait for success/error status before continuing

  Performance:
  - Switch from sequential to parallel DNS update processing (ThreadPoolExecutor)
  - Deduplicate entries by (zone_id, record_name, record_type) before processing
  - Thread-safe state access with locks

  Notifications:
  - Single batch notification per update run instead of per-entry
  - Clean title format with gateway names:
    "HCloudDNS: Failover WAN_Primary → WAN_Backup"
    "HCloudDNS: Failback WAN_Backup → WAN_Primary"
    "HCloudDNS: DynIP Update on WAN_Primary"
  - Records listed once in body (no duplication)
  - Grouped by domain with proper spacing
- Fix Log File tab URL to use core module path
- Fix TTL updates: use dedicated change_ttl endpoint per Hetzner API spec
  (PR opnsense#5091 feedback), split update_record into set_records + change_ttl
- Fix Implement forceInterval for periodic forced DNS updates
- Fix Remove insecure SSL context (CERT_NONE) from gateway_health.py
- Fix atomic file writes with 0600 permissions via write_state_file()
- Fix service showing as disabled in OPNsense service overview by adding nocheck flag and proper start/stop/restart configd actions

- Notification channels side by side: Ntfy | Email | Webhook (col-md-4)

- Align Save/Test buttons at bottom of all wells using flexbox
- Align Export/Import buttons at bottom of backup wells

- Add deploy.sh
- Add install.sh
- Add OPNsense standard service buttons (Start/Stop/Restart) via updateServiceControlUI with proper state tracking via flag file
- Add syslog-ng filter template matching message("HCloudDNS:") for log capture
- Add HistoryController::addEntry() for JSONL history writes
- Add SMTP fields to settings
- Add email notifications via system mail
- Add HMAC-SHA256 webhook signatures (X-HCloudDNS-Signature header)
- Add dry-run/preview mode with UI dialog
- Add read_history.py and manage_history.py configd scripts
- Add M2_1_0.php migration to export existing history
- Add hetzner_api_v2.py with TokenBucket rate limiter and 429 retry
- Add clickable error link in status bar that jumps to DNS Entries tab
- Add CARP-aware mode: DNS updates only run on CARP master node, backup node skips all operations with fail-open for standalone systems
- Add CARP VHID filter: monitor all interfaces (default) or a specific VHID to determine master/backup status
- Add CARP syshook (20-hclouddns) to trigger DNS update on failover
- Add model migration M2_0_3 for new carpAware/carpVhid fields

- Update hcloud_api.py wrapper with v2 routing and update_ttl()
- Update export/import, deploy script, and package manifest

- Move DNS change history from config.xml to JSONL file backend

- Delete unused v1 update_records.py script
- Delete HetznerCloudAPI (v1) class, use HetznerCloudAPIv2 with rate limiting

- Rewrite HistoryController.php to use configd instead of model

- Clean up old hcloudddns (double-D) plugin artifacts in deploy.sh to prevent crashes from the Dec 2025 rename
@ArcanConsulting
Copy link
Contributor Author

ArcanConsulting commented Feb 16, 2026

Quick update on the latest changes:

Addressed review feedback:

  • Migrated record updates to proper rrset-actions endpoints (set_records, change_ttl) as suggested by @jooola — this also eliminates the Action polling concern, since these endpoints don't return async Actions
  • Raised ACTION_POLL_INTERVAL to 1.0s (was 0.5s)
  • Removed insecure SSL context (CERT_NONE)
  • TTL changes now use the dedicated Hetzner endpoint
  • Zone ID vs. Zone Name: sticking with Zone ID as it's immutable (zone names could theoretically change). Both work per API docs as @Scorpoon confirmed.

New features:

  • OPNsense service integration (Start/Stop/Restart via configd, visible in Services overview)
  • CARP-aware mode — DNS updates only run on the CARP master node, with automatic failover via syshook
  • Email notifications (SMTP) alongside existing Ntfy and Webhook channels
  • HMAC-SHA256 signatures for webhook payloads
  • Dry-run/preview mode before applying DNS changes
  • JSONL-based history (moved out of config.xml)
  • Syslog-ng filter for log file tab
  • forceInterval for periodic DNS update enforcement

Bug fixes:

  • Fixed ntfy notification unicode error (latin-1 codec can't encode → in HTTP headers)
  • Fixed gateway health detection: now uses OPNsense's dpinger status instead of an unbound ping check that always went through the default gateway

The plugin is running stable on my infrastructure (~30 domains). Happy to address further feedback.

FIX: The health check was only checking "has IP = up", ignoring OPNsense's
actual dpinger monitoring. A gateway with a stale DHCP lease would
still show as up even when dpinger reported it down.

Now queries OPNsense's Gateways class + dpinger_status() and matches
by interface name (wan, opt1, etc.) — the same interface field already
used in the plugin's gateway dropdown.
The ping-based check had no source binding, so it always went through
the default gateway — reporting all gateways as up even when one was
down. Now queries OPNsense's own gateway_status.php which uses dpinger
for accurate per-gateway health monitoring.

FIX: Replace → (U+2192) with -> in ntfy Title headers. urllib encodes
headers as latin-1 which cannot represent unicode characters.
The message body (sent as UTF-8 data) is unaffected.
Add CAA wizard with CA presets for record creation
Add Pinned/favorite zones with localStorage persistence
Add DNS health check (NS delegation, SOA consistency, MX, SPF, DMARC, CAA, CNAME apex)
Add DNS propagation monitor across Hetzner nameservers
Add Zone export (BIND format) and import with preview
Add Record templates (mail server, Google Workspace, Microsoft 365, basic website, custom)
Add Audit dashboard with change history, detail view, and revert capability
Add Global record search across all zones with type filter, debounced live search, and Refresh All
Add Record cloning between zones via existing createRecord API
Add Keyboard shortcuts (/ s r i t h ?) with help modal
Add DNSSEC status check via DNSKEY/DS DNS queries with shield badge per zone
Add DNSSEC integrated into health check as additional check item

Fix checkbox data-type from "string" to "boolean" in gateways, accounts, entries
Fix The health check was only checking "has IP = up", ignoring OPNsense's actual dpinger monitoring. A gateway with a stale DHCP lease would still show as up even when dpinger reported it down. Now queries OPNsense's Gateways class + dpinger_status() and matches by interface name (wan, opt1, etc.) — the same interface field already used in the plugin's gateway dropdown. The ping-based check had no source binding, so it always went through the default gateway — reporting all gateways as up even when one was down. Now queries OPNsense's own gateway_status.php which uses dpinger for accurate per-gateway health monitoring.

Change Maintenance notifications are now deferred until DNS records have actually been updated (failover/failback complete), preventing premature or duplicate notifications.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants