From a97c64b08fcf2d803e5b77ee7fea57d0f655afbd Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Wed, 16 Sep 2026 10:19:06 +0200 Subject: [PATCH] Configure Aegis host DNS and NFS support --- AGENTS.md | 14 ++- README.md | 28 +++++- ansible/inventory/host_vars/aegis.yml | 6 ++ ansible/roles/profile_aegis/defaults/main.yml | 6 ++ ansible/roles/profile_aegis/tasks/main.yml | 89 ++++++++++++++++++- 5 files changed, 133 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b320cad..ce2695e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,8 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora - Rocky server after activation: `ansible-playbook ansible/site.yml --limit --check --diff` - Atlas NAS: `ansible-playbook ansible/site.yml --limit atlas --check --diff` - Aegis IoT: `ansible-playbook ansible/site.yml --limit aegis --check --diff` + - Aegis NFS client layer: `ansible-playbook ansible/site.yml --limit aegis --tags nfs --list-tasks` + - Aegis host DNS: `ansible-playbook ansible/site.yml --limit aegis --tags dns --check --diff` - Focused checks: - Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit --tags emacs --check --diff -e emacs_enabled=true` - AI coding agents: `ansible-playbook ansible/site.yml --limit --tags ai_agents --check --diff` @@ -221,14 +223,18 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i - `aegis` is a remote Fedora IoT Raspberry Pi 4 node. Bootstrap it once with `ansible/bootstrap/aegis.bu`; the remaining configuration is applied by `profile_aegis` over SSH. - Fedora IoT is immutable. Do not add it to mutable Fedora package or shared dotfile roles. -- `profile_aegis` owns rootful Podman Quadlets, persistent container state under `/var/lib`, the - Podman auto-update timer, LAN-restricted firewalld rules, and SSH hardening. Keep - `aegis_lan_subnet` and `aegis_adguard_web_port` host-specific; SSH permits only the declared +- `profile_aegis` owns the `nfs-utils` rpm-ostree layer used as the Atlas NFS client and reports the + required reboot without initiating it. It also owns rootful Podman Quadlets, persistent container + state under `/var/lib`, the Podman auto-update timer, LAN-restricted firewalld rules, and SSH hardening. Keep + `aegis_lan_subnet`, `aegis_adguard_web_port`, and `aegis_network_connection_uuid` host-specific; + SSH permits only the declared key-authenticated users, never root or password authentication. Keep Apple IDs and other credentials in Vault and use `no_log` for their rendering. - `aegis_adguard_web_port` defaults to `80`. The initial AdGuard Home wizard port `3000` is intentionally unmanaged: open and close it manually only while completing initial setup. Disable the local systemd-resolved stub through `profile_aegis` before AdGuard binds port 53; keep - `/etc/resolv.conf` linked to `/run/systemd/resolve/resolv.conf` so Aegis retains router-provided DNS. + `/etc/resolv.conf` linked to `/run/systemd/resolve/resolv.conf`. LAN clients may use AdGuard, but + Aegis must use the independent upstream DNS declared by `aegis_host_dns_servers` so Greenboot does + not depend on the AdGuard container during startup. - iCloudPD requires post-deployment interactive MFA initialization; its cookie/configuration state is persisted in `/var/lib/icloudpd/config`. diff --git a/README.md b/README.md index a3d529e..652beda 100644 --- a/README.md +++ b/README.md @@ -193,12 +193,17 @@ ansible/bootstrap/generate-aegis-ign.sh --write IMAGE DEVICE The controller manages it remotely as `pi@aegis`; unlike local desktop profiles, Aegis is intentionally an SSH inventory target. `profile_aegis` manages rootful Podman Quadlets for AdGuard Home and iCloudPD, persistent data under `/var/lib`, the Podman auto-update timer, LAN-restricted -firewalld rules, SSH key-only access for `pi`, and `wake-ikaros`. Set the host-local -`aegis_lan_subnet` and `aegis_adguard_web_port` values before applying it. The playbook permits +firewalld rules, SSH key-only access for `pi`, the `nfs-utils` rpm-ostree layer required by the +Atlas NFS client, and `wake-ikaros`. A new layered package deployment requires a manual reboot; the +role reports this condition but never reboots Aegis automatically. Set the host-local +`aegis_lan_subnet`, `aegis_adguard_web_port`, and `aegis_network_connection_uuid` values before +applying it. The playbook permits AdGuard Home HTTP on port `80`; the initial wizard port `3000` is intentionally unmanaged and must be opened and closed manually during initial setup. The profile disables the local systemd-resolved DNS -stub and points `/etc/resolv.conf` to its full resolver data, freeing port 53 -for AdGuard while retaining DNS learned from the router. Define +stub and points `/etc/resolv.conf` to its full resolver data, freeing port 53 for AdGuard. LAN clients +may use AdGuard on Aegis, while Aegis itself uses the independent upstream DNS declared by +`aegis_host_dns_servers`; this prevents Greenboot from depending on the AdGuard container during +startup. Reboot Aegis after changing its NetworkManager DNS profile. Define `vault_aegis_icloudpd_apple_id` in Vault before applying it. iCloudPD still requires interactive MFA initialization after its first deployment. @@ -213,6 +218,21 @@ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ansible-playbook ansible/site.yml --limit aegis --check --diff --ask-become-pass ``` +Apply only the independent host DNS configuration, then reboot Aegis manually: + +```bash +ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ +ansible-playbook ansible/site.yml --limit aegis --tags dns --ask-become-pass +``` + +Layer the Atlas NFS client package independently, then reboot Aegis manually when the role reports +that the new deployment is ready: + +```bash +ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ +ansible-playbook ansible/site.yml --limit aegis --tags nfs --ask-become-pass +``` + ## NAS `atlas` is a Rocky Linux 9 NAS reached through SSH. Normally its pool already exists and the profile diff --git a/ansible/inventory/host_vars/aegis.yml b/ansible/inventory/host_vars/aegis.yml index 6fc2f0b..5abcb4c 100644 --- a/ansible/inventory/host_vars/aegis.yml +++ b/ansible/inventory/host_vars/aegis.yml @@ -9,6 +9,12 @@ ansible_ssh_use_tty: false aegis_lan_subnet: 192.168.178.0/24 aegis_adguard_web_port: 80 +aegis_network_connection_name: FRITZ!Box 7530 NR +aegis_network_connection_uuid: a52fda3d-3eb6-351f-bf04-753edcb76944 +aegis_host_dns_servers: + - 192.168.178.1 +aegis_host_dns_search_domains: + - fritz.box aegis_ssh_authorized_keys: - name: ikaros key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros" diff --git a/ansible/roles/profile_aegis/defaults/main.yml b/ansible/roles/profile_aegis/defaults/main.yml index 2d63a59..4760fc5 100644 --- a/ansible/roles/profile_aegis/defaults/main.yml +++ b/ansible/roles/profile_aegis/defaults/main.yml @@ -1,5 +1,11 @@ --- aegis_hostname: aegis +aegis_layered_packages: + - nfs-utils +aegis_network_connection_name: "" +aegis_network_connection_uuid: "" +aegis_host_dns_servers: [] +aegis_host_dns_search_domains: [] aegis_adguard_image: docker.io/adguard/adguardhome:latest aegis_icloudpd_image: docker.io/boredazfcuk/icloudpd:latest aegis_icloudpd_folder_structure: '{:%Y/%m/%d}' diff --git a/ansible/roles/profile_aegis/tasks/main.yml b/ansible/roles/profile_aegis/tasks/main.yml index f609d3d..c0430f8 100644 --- a/ansible/roles/profile_aegis/tasks/main.yml +++ b/ansible/roles/profile_aegis/tasks/main.yml @@ -1,4 +1,18 @@ --- +- name: Manage Aegis layered packages + tags: [aegis, packages, nfs] + community.general.rpm_ostree_pkg: + name: "{{ aegis_layered_packages }}" + state: present + register: aegis_layered_packages_result + when: aegis_layered_packages | length > 0 + +- name: Report reboot required for Aegis layered packages + tags: [aegis, packages, nfs] + ansible.builtin.debug: + msg: Reboot Aegis to activate the newly layered packages, then rerun the playbook. + when: aegis_layered_packages_result.needs_reboot | default(false) + - name: Require Aegis iCloudPD Apple ID tags: [aegis, icloudpd] ansible.builtin.assert: @@ -8,15 +22,86 @@ no_log: true - name: Require completed Aegis network placeholders - tags: [aegis, firewall, services] + tags: [aegis, dns, firewall, network, services] ansible.builtin.assert: that: - aegis_lan_subnet != 'CHANGEME_LAN_SUBNET' - aegis_firewalld_zone | length > 0 - aegis_adguard_web_port | int > 0 - aegis_adguard_web_port | int < 65536 + - aegis_network_connection_name | length > 0 + - aegis_network_connection_uuid | length > 0 + - aegis_host_dns_servers | length > 0 + - ansible_facts["default_ipv4"]["address"] not in aegis_host_dns_servers - aegis_ssh_allowed_users | length > 0 - fail_msg: Define the Aegis LAN subnet, firewalld zone, AdGuard web port, and SSH users. + fail_msg: >- + Define the Aegis LAN subnet, firewalld zone, AdGuard web port, independent host DNS, + NetworkManager connection, and SSH users. Aegis must not use its own address as upstream DNS. + +- name: Verify the declared Aegis NetworkManager connection exists + tags: [aegis, dns, network, services] + ansible.builtin.command: + argv: + - nmcli + - --get-values + - connection.id + - connection + - show + - uuid + - "{{ aegis_network_connection_uuid }}" + register: aegis_network_connection + changed_when: false + failed_when: >- + aegis_network_connection.rc != 0 + or aegis_network_connection.stdout != aegis_network_connection_name + +- name: Read the current Aegis host DNS configuration + tags: [aegis, dns, network, services] + ansible.builtin.command: + argv: + - nmcli + - --get-values + - ipv4.ignore-auto-dns,ipv4.dns,ipv4.dns-search,ipv6.ignore-auto-dns + - connection + - show + - uuid + - "{{ aegis_network_connection_uuid }}" + register: aegis_host_dns_current + changed_when: false + +- name: Configure independent upstream DNS for the Aegis host + tags: [aegis, dns, network, services] + ansible.builtin.command: + argv: + - nmcli + - connection + - modify + - uuid + - "{{ aegis_network_connection_uuid }}" + - ipv4.ignore-auto-dns + - "yes" + - ipv4.dns + - "{{ aegis_host_dns_servers | join(',') }}" + - ipv4.dns-search + - "{{ aegis_host_dns_search_domains | join(',') }}" + - ipv6.ignore-auto-dns + - "yes" + register: aegis_host_dns_profile + when: >- + aegis_host_dns_current.stdout_lines != + [ + 'yes', + aegis_host_dns_servers | join(','), + aegis_host_dns_search_domains | join(','), + 'yes' + ] + changed_when: true + +- name: Report Aegis reboot required for host DNS changes + tags: [aegis, dns, network, services] + ansible.builtin.debug: + msg: Reboot Aegis to activate its independent upstream DNS before testing another OS update. + when: aegis_host_dns_profile.changed | default(false) - name: Set Aegis hostname tags: [aegis, services]