From 4931117343a3105749b4a3698dd24c824a505441 Mon Sep 17 00:00:00 2001 From: Joe Carstairs Date: Fri, 28 Aug 2026 15:23:36 +0100 Subject: ovh-ify dyndns --- roles/domain-zone-management/tasks/main.yml | 34 +++++ roles/domain-zone-management/templates/ovh.conf | 7 + .../templates/update_ovhcloud_domain_zone | 156 +++++++++++++++++++++ roles/dyndns/files/DIGITALOCEAN_TOKEN | 9 -- roles/dyndns/tasks/main.yml | 75 +--------- roles/dyndns/templates/dyndns.zone | 4 + roles/tls/files/ovh-dns-credentials.ini | 14 -- roles/tls/tasks/main.yml | 2 +- roles/tls/templates/ovh-dns-credentials.ini | 4 + 9 files changed, 213 insertions(+), 92 deletions(-) create mode 100644 roles/domain-zone-management/tasks/main.yml create mode 100644 roles/domain-zone-management/templates/ovh.conf create mode 100755 roles/domain-zone-management/templates/update_ovhcloud_domain_zone delete mode 100644 roles/dyndns/files/DIGITALOCEAN_TOKEN create mode 100644 roles/dyndns/templates/dyndns.zone delete mode 100644 roles/tls/files/ovh-dns-credentials.ini create mode 100644 roles/tls/templates/ovh-dns-credentials.ini (limited to 'roles') diff --git a/roles/domain-zone-management/tasks/main.yml b/roles/domain-zone-management/tasks/main.yml new file mode 100644 index 0000000..c3592e4 --- /dev/null +++ b/roles/domain-zone-management/tasks/main.yml @@ -0,0 +1,34 @@ +- name: Install jq + community.general.apk: + name: jq + +- name: Install OVHCloud CLI + ansible.builtin.shell: + cmd: curl -fsSL https://raw.githubusercontent.com/ovh/ovhcloud-cli/main/install.sh | sh + creates: /usr/bin/ovhcloud + +- name: Install OVHCloud CLI config + ansible.builtin.template: + src: ovh.conf + dest: /etc/ovh.conf + +- name: Create OVH domain zone append records directory + ansible.builtin.file: + path: "{{ ovh_domain_zone_append_records_directory }}" + state: directory + +- name: Create OVH domain zone replace records directory + ansible.builtin.file: + path: "{{ ovh_domain_zone_replace_records_directory }}" + state: directory + +- name: Create OVH domain zone log directory + ansible.builtin.file: + path: "{{ ovh_domain_zone_log_directory }}" + state: directory + +- name: Install OVHCloud domain zone management script + ansible.builtin.template: + src: update_ovhcloud_domain_zone + dest: /usr/bin/update_ovhcloud_domain_zone + mode: "755" diff --git a/roles/domain-zone-management/templates/ovh.conf b/roles/domain-zone-management/templates/ovh.conf new file mode 100644 index 0000000..a2c3739 --- /dev/null +++ b/roles/domain-zone-management/templates/ovh.conf @@ -0,0 +1,7 @@ +[default] +endpoint={{ ovhcloud_endpoint }} + +[ovh-eu] +application_key={{ ovhcloud_application_key }} +application_secret={{ ovhcloud_application_secret }} +consumer_key={{ ovhcloud_consumer_key }} diff --git a/roles/domain-zone-management/templates/update_ovhcloud_domain_zone b/roles/domain-zone-management/templates/update_ovhcloud_domain_zone new file mode 100755 index 0000000..dfeac61 --- /dev/null +++ b/roles/domain-zone-management/templates/update_ovhcloud_domain_zone @@ -0,0 +1,156 @@ +#!/bin/sh + + +SCRIPT="$0" +SCRIPT_DIR=$(cd "${SCRIPT%/*}" && pwd ) +SCRIPT_NAME=${SCRIPT##*/} +APPEND_RECORDS_DIR="{{ ovh_domain_zone_append_records_directory }}" +REPLACE_RECORDS_DIR="{{ ovh_domain_zone_replace_records_directory }}" +mkdir -p ${APPEND_RECORDS_DIR} +mkdir -p ${REPLACE_RECORDS_DIR} + +cut_nth_word() +{ + echo "$1" | cut -d ' ' -f "$2" +} + +get_record_subdomain() +{ + record="$1" + subdomain="$(cut_nth_word "$record" 1)" + if test "$(echo "$subdomain" | grep -q [A-Z])" + then + subdomain="$(cut_nth_word "$record" 2)" + fi + if test "$subdomain" == "@" + then + subdomain="" + fi + echo "$subdomain" +} + +get_record_type() +{ + record="$1" + record_type="$(cut_nth_word "$record" 2)" + if test "$(echo "$record_type" | grep -q [0-9])" + then + record_type="$(cut_nth_word "$record" 1)" + fi + echo "$record_type" +} + +get_record_target() +{ + record="$1" + target_and_ttl="$(cut_nth_word "$record" "3-")" + target="$(cut_nth_word "$(echo "$target_and_ttl" | rev)" "2-" | rev)" + echo "$target" +} + +get_record_ttl() +{ + record="$1" + ttl="$(cut_nth_word "$(echo "$record" | rev)" 1 | rev)" + echo "$ttl" +} + +find_existing_record_ids() +{ + subdomain="$1" + record_type="$2" + target="$3" + query=".subDomain == \"${subdomain}\" and .fieldType == \"${record_type}\"" + if [ -n "$target" ] + then + query="${query} and .target == ${target}" + fi + ovhcloud domain-zone record list joeac.net -o json \ + | jq "map(select($query))" \ + | jq "map(.id)" \ + | grep -Eo [0-9]\+ +} + +append_record() +{ + subdomain="$1" + record_type="$2" + target="$3" + ttl="$4" + echo "Appending record to joeac.net zone: ${record_type} ${subdomain:-@} ${target} ${ttl}" + ovhcloud domain-zone record create joeac.net \ + --field-type "$record_type" \ + --sub-domain "$subdomain" \ + --target "$target" \ + --ttl "$ttl" +} + +replace_records() +{ + subdomain="$1" + target="$2" + ttl="$3" + first_existing_record_id="$4" + echo "Replacing record ${first_existing_record_id} in joeac.net zone: ${record_type} ${subdomain:-@} ${target} ${ttl}" + ovhcloud domain-zone record update joeac.net "$first_existing_record_id" \ + --sub-domain "${subdomain}" \ + --target "${target}" \ + --ttl "${ttl}" + shift 4 + excess_record_ids="$@" + if [ -n "$excess_record_ids" ] + then + for id in "$excess_record_ids" + do + echo "Removing record ${id} in joeac.net zone: ${record_type} ${subdomain:-@}" + ovhcloud domain-zone record delete joeac.net "$id" + done + fi +} + +if test $(ls ${APPEND_RECORDS_DIR} | wc -l) -eq 0 +then + echo "Nothing to append: no zone files in ${APPEND_RECORDS_DIR}" +else + for zone_file in ${APPEND_RECORDS_DIR}/* + do + while read record + do + subdomain="$(get_record_subdomain "$record")" + record_type="$(get_record_type "$record")" + target="$(get_record_target "$record")" + ttl="$(get_record_ttl "$record")" + if test -z "$(find_existing_record_ids "$subdomain" "$record_type" "$target")" + then + append_record "$subdomain" "$record_type" "$target" "$ttl" + fi + done < "${zone_file}" + done +fi + +if test $(ls ${REPLACE_RECORDS_DIR} | wc -l) -eq 0 +then + echo "Nothing to replace: no zone files in ${REPLACE_RECORDS_DIR}" + exit 0 +else + for zone_file in ${REPLACE_RECORDS_DIR}/* + do + while read record + do + subdomain="$(get_record_subdomain "$record")" + record_type="$(get_record_type "$record")" + target="$(get_record_target "$record")" + ttl="$(get_record_ttl "$record")" + + existing_record_ids="$(find_existing_record_ids "$subdomain" "$record_type")" + if test -n "$existing_record_ids" + then + replace_records "$subdomain" "$target" "$ttl" "$existing_record_ids" + else + append_record "$subdomain" "$record_type" "$target" "$ttl" "$existing_record_ids" + fi + done < "${zone_file}" + done +fi + +ovhcloud domain-zone refresh joeac.net diff --git a/roles/dyndns/files/DIGITALOCEAN_TOKEN b/roles/dyndns/files/DIGITALOCEAN_TOKEN deleted file mode 100644 index 7f85a33..0000000 --- a/roles/dyndns/files/DIGITALOCEAN_TOKEN +++ /dev/null @@ -1,9 +0,0 @@ -$ANSIBLE_VAULT;1.2;AES256;ansible -32323233343662343033613265383639303739386139363735646133633435393261346466666530 -3031303363663639343163633164393335663639353165300a373961376339336565353630303566 -32616562373263363365656531353633663830343633356462396464626164306337656665326463 -3336666530656539330a303238353335313130313130653138663266393430646662663066663036 -37626630396438336136303032633439323436323132616665353938373139353530613539366632 -39653161363765376262393933666163363230333037383033666138373439623838643833633463 -38393437353830356362393439366461623231653437613462666466643563323933366237313366 -33613166633665363238 diff --git a/roles/dyndns/tasks/main.yml b/roles/dyndns/tasks/main.yml index e157644..9547f1a 100644 --- a/roles/dyndns/tasks/main.yml +++ b/roles/dyndns/tasks/main.yml @@ -1,72 +1,11 @@ -- name: Fetch digitalocean_dyndns source - ansible.builtin.git: - repo: git://git.joeac.net/digitalocean_dyndns.git - dest: /usr/local/lib/digitalocean_dyndns - -- name: Mark get_ip_addr.sh executable - ansible.builtin.file: - path: /usr/local/lib/digitalocean_dyndns/get_ip_addr.sh - owner: joeac.net - group: joeac.net - mode: "775" - -- name: Mark dyndns.sh executable - ansible.builtin.file: - path: /usr/local/lib/digitalocean_dyndns/dyndns.sh - owner: joeac.net - group: joeac.net - mode: "775" - -- name: Symlink get_ip_addr.sh - ansible.builtin.file: - src: /usr/local/lib/digitalocean_dyndns/get_ip_addr.sh - dest: /usr/bin/get_ip_addr.sh - owner: joeac.net - group: joeac.net - state: link - -- name: Symlink dyndns.sh - ansible.builtin.file: - src: /usr/local/lib/digitalocean_dyndns/dyndns.sh - dest: /usr/bin/dyndns.sh - owner: joeac.net - group: joeac.net - state: link - -- name: Create config directory - ansible.builtin.file: - path: /etc/digitalocean_dyndns - state: directory +- name: Install DynDNS zone file + ansible.builtin.template: + src: dyndns.zone + dest: "{{ ovh_domain_zone_replace_records_directory }}/dyndns.zone" mode: "644" -- name: Create cache directory - ansible.builtin.file: - path: /var/digitalocean_dyndns - state: directory - mode: "666" - -- name: Create log directory - ansible.builtin.file: - path: /var/log/digitalocean_dyndns - state: directory - mode: "666" - -- name: Copy DIGITALOCEAN_TOKEN - ansible.builtin.copy: - src: DIGITALOCEAN_TOKEN - dest: /etc/digitalocean_dyndns/DIGITALOCEAN_TOKEN - mode: "640" - -- name: Install daily crontabs for dyndns (IPv4) - loop: "{{ subdomains | map(attribute='name') }}" - ansible.builtin.cron: - special_time: daily - name: daily crontab for dyndns for {{ item }}.joeac.net (IPv4) - job: CACHE_DIR=/var/digitalocean_dyndns CONFIG_DIR=/etc/digitalocean_dyndns dyndns.sh 4 {{ item }}.joeac.net >> /var/log/digitalocean_dyndns/{{ item }}.joeac.net.ipv4.log - -- name: Install daily crontabs for dyndns (IPv6) - loop: "{{ subdomains | map(attribute='name') }}" +- name: Install daily crontabs for DynDNS ansible.builtin.cron: special_time: daily - name: daily crontab for dyndns for {{ item }}.joeac.net (IPv6) - job: CACHE_DIR=/var/digitalocean_dyndns CONFIG_DIR=/etc/digitalocean_dyndns CONN_DEVICE_NAME={{ ansible_facts.default_ipv4.alias }} dyndns.sh 6 {{ item }}.joeac.net >> /var/log/digitalocean_dyndns/{{ item }}.joeac.net.ipv6.log + name: daily crontab for DynDNS + job: update_ovhcloud_domain_zone >> {{ ovh_domain_zone_log_directory }}/cron.log diff --git a/roles/dyndns/templates/dyndns.zone b/roles/dyndns/templates/dyndns.zone new file mode 100644 index 0000000..c3a7e6a --- /dev/null +++ b/roles/dyndns/templates/dyndns.zone @@ -0,0 +1,4 @@ +{% for subdomain in ( subdomains | rejectattr("service", "none") ) %} +{{ subdomain.name }} A {{ router.wan.ipv4 }} {{ dns_default_ttl }} +{{ subdomain.name }} AAAA {{ ansible_facts.default_ipv6.address }} {{ dns_default_ttl }} +{% endfor %} diff --git a/roles/tls/files/ovh-dns-credentials.ini b/roles/tls/files/ovh-dns-credentials.ini deleted file mode 100644 index 210a403..0000000 --- a/roles/tls/files/ovh-dns-credentials.ini +++ /dev/null @@ -1,14 +0,0 @@ -$ANSIBLE_VAULT;1.2;AES256;ansible -37333731336135353732336462623564633235633561336130376361646266363930626230616230 -6261356563343235353865663836643830616561613132320a643933303936343361336634313363 -38646163363939333662393666373335346565666236363163366137616137343734303531346531 -6135343839646662660a383339303664383235656431303333623661343638663631646131393164 -34666135616533656561626131323631303637663062363266663934666634386565656339663331 -65663135303661636564386266383333326536303161636230363461313131643664663739616434 -63366335623935656337313735623134323530393636373639633863346265343831363130623861 -30316233336335653233333633333461646364656435333936613733393835343931346536366430 -31333634346362346234613137623133633731353432363037306235393131356665323761643835 -37333562393362333033313932353363356536626565373939333230303435326635636639343262 -30366466636362653536613635653034313765646238383737386636306134633135326365333065 -33656335316330326439313235323862393134663062643562636434343839323232356661656633 -39653361313235396436366564366131313731346436346236356534363937303337 diff --git a/roles/tls/tasks/main.yml b/roles/tls/tasks/main.yml index f637ff5..caf07b2 100644 --- a/roles/tls/tasks/main.yml +++ b/roles/tls/tasks/main.yml @@ -28,7 +28,7 @@ certbot renew --non-interactive - name: Install OVH DNS credentials - ansible.builtin.copy: + ansible.builtin.template: src: ovh-dns-credentials.ini dest: /etc/ovh-dns-credentials.ini mode: "400" diff --git a/roles/tls/templates/ovh-dns-credentials.ini b/roles/tls/templates/ovh-dns-credentials.ini new file mode 100644 index 0000000..d8f9522 --- /dev/null +++ b/roles/tls/templates/ovh-dns-credentials.ini @@ -0,0 +1,4 @@ +dns_ovh_endpoint = {{ ovhcloud_endpoint }} +dns_ovh_application_key = {{ ovhcloud_application_key }} +dns_ovh_application_secret = {{ ovhcloud_application_secret }} +dns_ovh_consumer_key = {{ ovhcloud_consumer_key }} -- cgit v1.2.3