From bd84dd02132b831cd2b9ea99576af2e0d1f8fcb3 Mon Sep 17 00:00:00 2001 From: sjat Date: Sun, 14 Jun 2026 10:34:42 +0200 Subject: [PATCH] feat(public_dns): role tasks, defaults, meta, README Implement M1: manage wingu.me public DNS zone at Gandi LiveDNS via community.general.gandi_livedns (PAT from vault.gandi.pat). Adds assertion guard for domain + null-MX, present/absent record loops with run_once, and apply-gate for Molecule dry-run mode. Co-Authored-By: Claude Opus 4.8 (1M context) --- roles/public_dns/README.md | 29 ++++++++++++++++++++++- roles/public_dns/defaults/main.yml | 8 +++++++ roles/public_dns/meta/main.yml | 10 ++++++++ roles/public_dns/tasks/main.yml | 37 ++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/roles/public_dns/README.md b/roles/public_dns/README.md index a9e5c20..9200951 100644 --- a/roles/public_dns/README.md +++ b/roles/public_dns/README.md @@ -1,3 +1,30 @@ # public_dns -Role description here. +Manages boma's public DNS zone (**wingu.me**) at **Gandi LiveDNS** as code, via +`community.general.gandi_livedns` (PAT auth from `vault.gandi.pat`). Provider-agnostic +name on purpose. Run from the control node: `make check/deploy PLAYBOOK=dns`. + +Mesh/LAN-only by default — only deliberate public records live in the zone (the +anti-spoof baseline now; `askari` in M4). Everything else is reached over LAN/mesh and +never appears here. + +## Data (in `group_vars/all/public_dns.yml`) + +| Var | Meaning | +|---|---| +| `public_dns__domain` | the zone (`wingu.me`) | +| `public_dns__records` | records to ensure **present** (`record`, `type`, `values`, optional `ttl`) | +| `public_dns__absent` | records to ensure **absent** (Gandi's auto-seeded defaults) | + +## Behaviour knobs (`defaults/main.yml`) + +| Var | Default | Meaning | +|---|---|---| +| `public_dns__apply` | `true` | set `false` to validate without calling the Gandi API (Molecule) | +| `public_dns__default_ttl` | `1800` | TTL when a record omits one | + +## Notes + +The zone is reconciled **additively** plus an explicit `absent` list (Gandi seeds 13 +default records on a new `.me`; we purge the unwanted 11 and overwrite MX/SPF with the +anti-spoof baseline). Full-zone authoritative pruning is a future enhancement (TODO 8.3). diff --git a/roles/public_dns/defaults/main.yml b/roles/public_dns/defaults/main.yml index ed97d53..231fdc5 100644 --- a/roles/public_dns/defaults/main.yml +++ b/roles/public_dns/defaults/main.yml @@ -1 +1,9 @@ --- +# public_dns — manage the public zone at Gandi LiveDNS as code (M1). +# Record data (public_dns__domain / __records / __absent) lives in group_vars/all. +# See docs/decisions/007-network.md. +public_dns__apply: true # set false to validate without calling the Gandi API (Molecule) +public_dns__default_ttl: 1800 # TTL when a record omits one +public_dns__domain: "" # overridden in group_vars/all +public_dns__records: [] # present records +public_dns__absent: [] # records to remove diff --git a/roles/public_dns/meta/main.yml b/roles/public_dns/meta/main.yml index ed97d53..6379547 100644 --- a/roles/public_dns/meta/main.yml +++ b/roles/public_dns/meta/main.yml @@ -1 +1,11 @@ --- +galaxy_info: + author: sjat + description: Manage boma's public DNS zone (wingu.me) at Gandi LiveDNS as code. + license: MIT + min_ansible_version: "2.17" + platforms: + - name: Debian + versions: + - trixie +dependencies: [] diff --git a/roles/public_dns/tasks/main.yml b/roles/public_dns/tasks/main.yml index ed97d53..c1f3e2f 100644 --- a/roles/public_dns/tasks/main.yml +++ b/roles/public_dns/tasks/main.yml @@ -1 +1,38 @@ --- +- name: Assert public DNS data is sane + ansible.builtin.assert: + that: + - public_dns__domain | length > 0 + - public_dns__records | selectattr('type', 'equalto', 'MX') | list | length > 0 + fail_msg: >- + public_dns__domain must be set and a null-MX anti-spoof record declared in + public_dns__records (group_vars/all/public_dns.yml). + run_once: true + +- name: Ensure desired records are present (Gandi LiveDNS) + community.general.gandi_livedns: + domain: "{{ public_dns__domain }}" + record: "{{ item.record }}" + type: "{{ item.type }}" + values: "{{ item.values }}" + ttl: "{{ item.ttl | default(public_dns__default_ttl) }}" + state: present + personal_access_token: "{{ vault.gandi.pat }}" + loop: "{{ public_dns__records }}" + loop_control: + label: "{{ item.record }} {{ item.type }}" + run_once: true + when: public_dns__apply | bool + +- name: Ensure unwanted records are absent (Gandi LiveDNS) + community.general.gandi_livedns: + domain: "{{ public_dns__domain }}" + record: "{{ item.record }}" + type: "{{ item.type }}" + state: absent + personal_access_token: "{{ vault.gandi.pat }}" + loop: "{{ public_dns__absent }}" + loop_control: + label: "{{ item.record }} {{ item.type }}" + run_once: true + when: public_dns__apply | bool