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 --- inventory.yml | 4 + playbook.yml | 2 + 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 + todo.txt | 2 - vars/common.yml | 13 +- vars/ovhcloud.yml | 27 ++++ 14 files changed, 253 insertions(+), 100 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 create mode 100644 vars/ovhcloud.yml diff --git a/inventory.yml b/inventory.yml index a406a1a..39b2c74 100644 --- a/inventory.yml +++ b/inventory.yml @@ -11,6 +11,8 @@ ungrouped: 37626438343134636130646239653439303333363665343931373830613661366264316462393137 3438353664653365300a626535303539653166663665356563386234343161313738663534333033 6139 + connection_name: nevis + connection_device_name: eth0 blade-canongate: ansible_host: 192.168.178.75 ansible_ssh_user: canongate @@ -22,5 +24,7 @@ ungrouped: 63326363386631313835306630336535616335336237663831613832663237396661363436643437 3237646663616430340a656137396337303564646333316234623264643963623162623766383131 6338 + connection_name: nevis + connection_device_name: eth0 mox_checksum: 0KIk721VDR6D_SkRbZ-Vc8-fXe7I mox_platform: amd64 diff --git a/playbook.yml b/playbook.yml index d8009e1..4e4ff04 100644 --- a/playbook.yml +++ b/playbook.yml @@ -2,6 +2,7 @@ hosts: "{{ services.mox.host }}" become: true roles: + - domain-zone-management - dyndns - tls - mox @@ -9,6 +10,7 @@ - common.yml - email.yml - network.yml + - ovhcloud.yml - name: Install http hosts: "{{ services.http.host }}" 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 }} diff --git a/todo.txt b/todo.txt index 23c092c..40abd04 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,3 @@ -Make dyndns self-updating -Fix DynDNS to work with OVHCloud's API Add an Immich instance Incorporate backups into this config Add a dashboard with health checks, update checks, backup checks diff --git a/vars/common.yml b/vars/common.yml index db9a0ff..fe330d1 100644 --- a/vars/common.yml +++ b/vars/common.yml @@ -1,3 +1,4 @@ +dns_default_ttl: 3600 src_dir: /usr/share/src services: @@ -49,16 +50,16 @@ subdomains: service: ln - name: mail full_domain: mail.joeac.net - mox_handler: none - service: none + mox_handler: null + service: null - name: autoconfig.mail full_domain: autoconfig.mail.joeac.net - mox_handler: none - service: none + mox_handler: null + service: null - name: mta-sts.mail full_domain: mta-sts.mail.joeac.net - mox_handler: none - service: none + mox_handler: null + service: null - name: pwd full_domain: pwd.joeac.net mox_handler: forward diff --git a/vars/ovhcloud.yml b/vars/ovhcloud.yml new file mode 100644 index 0000000..0b38329 --- /dev/null +++ b/vars/ovhcloud.yml @@ -0,0 +1,27 @@ +ovh_domain_zone_append_records_directory: /etc/ovh_domain_zone/append +ovh_domain_zone_replace_records_directory: /etc/ovh_domain_zone/replace +ovh_domain_zone_log_directory: /var/log/ovh_domain_zone +ovhcloud_endpoint: ovh-eu +ovhcloud_application_key: !vault | + $ANSIBLE_VAULT;1.2;AES256;ansible + 33316563376231643138363538373531343932303236333234326561356163313164333434336130 + 6563336538303931353435636161326635326130323336390a623366386130313165653530323434 + 37363032346436346363643563646639613265326335376563646236393663633434343639336461 + 3364326437613131610a633764633734616430363537383438633464336464363230623938373933 + 34373066643864306332336462343365623034343632613461663232636163313034 +ovhcloud_application_secret: !vault | + $ANSIBLE_VAULT;1.2;AES256;ansible + 36636663343461643966376431393866373839343234623932643965663863616537636364343262 + 6665663761636439616639646538646433313631303037300a656664616538366264383234353466 + 61623534363536353035653731616566313134663931336534643534326636643730653864646138 + 3132623633343434340a323564386236373934376633646130653830643331303338653361653961 + 62356131383664336633373962633962386563656564376338363635643766346135336366393761 + 3530663637316530396630656430623766363265616635303362 +ovhcloud_consumer_key: !vault | + $ANSIBLE_VAULT;1.2;AES256;ansible + 34633631383634366131303466646462373039643661333962323764376337653932353861346536 + 3134353265616462373361613637326164646230616363320a633930353034356330383965363333 + 38626335643534386431376264666462383933323239316139623462313161383635633135613839 + 3837633131386265360a356162656130306532353164313030383932623332306436336633623866 + 37623862616537373837643064613630396634643830633335326535623135633566396566363262 + 3632643736383431653635646461623137356161643239616332 -- cgit v1.2.3