Compare commits

..

2 Commits

Author SHA1 Message Date
Fabio Scotto di Santolo
e46a2b22d7 Update Atlas NAS validation status and remaining TODOs 2026-09-16 10:25:38 +02:00
Fabio Scotto di Santolo
a97c64b08f Configure Aegis host DNS and NFS support 2026-09-16 10:19:06 +02:00
5 changed files with 152 additions and 23 deletions

View File

@@ -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 <host> --check --diff` - Rocky server after activation: `ansible-playbook ansible/site.yml --limit <host> --check --diff`
- Atlas NAS: `ansible-playbook ansible/site.yml --limit atlas --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 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: - Focused checks:
- Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit <host> --tags emacs --check --diff -e emacs_enabled=true` - Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit <host> --tags emacs --check --diff -e emacs_enabled=true`
- AI coding agents: `ansible-playbook ansible/site.yml --limit <host> --tags ai_agents --check --diff` - AI coding agents: `ansible-playbook ansible/site.yml --limit <host> --tags ai_agents --check --diff`
@@ -168,19 +170,24 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
service ports only in the WireGuard firewalld zone. service ports only in the WireGuard firewalld zone.
## Atlas NAS TODO ## Atlas NAS TODO
- Provide the required Vault variables and validate the first remote bootstrap on the real Rocky Linux 9 host. Completed validation: the existing RAIDZ2 pool and datasets, SELinux, LAN firewall, SSH, Cockpit with
Before the first apply, confirm the pool, mountpoints, LAN subnet and firewalld zone. Keep the selected 45Drives plugins, encrypted SMB3 `Archive`, the Aegis-only NFSv4 `photobook` export, and
`atlas_manage_media_stack` disabled until `/dev/dri`, the container paths and the Immich database secret are validated. the Prometheus--Atlas WireGuard path are operational. Aegis has validated NFSv4.2 read, write, delete,
- Validate the complete baseline on the target: OpenZFS kmod loading, existing pool import, dataset and `all_squash` mapping to UID/GID `1100` end-to-end.
mounts, SSH reconnect, Cockpit and all selected 45Drives plugins, NFSv4, SMB and Syncthing. - Complete the Phase 1 Navidrome cutover: stop the Prometheus writer, copy and verify its complete
- Finalize dataset properties and the shared UID/GID, group and POSIX ACL model; test the same files `/opt/navidrome/data/` directory (including SQLite sidecars) under
through both NFS and SMB before considering multiprotocol access complete. `/zpool/archive/app_data/navidrome/`, then set `backend_phase1_start_services: true` and validate
Navidrome on Atlas through WireGuard. Do not delete the source until a restore test succeeds.
- Start and validate the rendered Syncthing Quadlet only after its device IDs, star topology, folders,
folder modes, ignore rules, and GUI/API protection are declared. Validate its GUI and native transfer
ports through WireGuard only.
- Decide whether a common SMB/NFS namespace is required. `Archive` (SMB) and `photobook` (NFS) are
intentionally distinct today; only if a shared namespace is selected, finalize its UID/GID, group,
and POSIX ACL model and test the same files through both protocols.
- Keep `atlas_manage_media_stack` disabled until the future Immich deployment has validated `/dev/dri`,
container paths, and the required Vault database secret.
- Add Ansible-managed ZFS snapshot retention and scrub timers. Use Cockpit Scheduler for visibility - Add Ansible-managed ZFS snapshot retention and scrub timers. Use Cockpit Scheduler for visibility
or manual operations, not as the only source of configuration, and never automate snapshot rollback. or manual operations, not as the only source of configuration, and never automate snapshot rollback.
- Manage the Syncthing star topology, device IDs, folders, folder modes, ignore rules and protected GUI
or API access for the selected clients.
- Validate the managed WireGuard path and its LAN/VPN-only firewalld rules before enabling remote services;
never expose SSH, Cockpit, NFS, SMB or Syncthing through public port forwarding.
- Add the Atlas-initiated least-privilege Prometheus backup pull: Prometheus exposes only prepared - Add the Atlas-initiated least-privilege Prometheus backup pull: Prometheus exposes only prepared
read-only dumps through a dedicated account and Atlas retains the private SSH key, pinned host key, read-only dumps through a dedicated account and Atlas retains the private SSH key, pinned host key,
atomic pull, verification, retention and systemd service/timer. atomic pull, verification, retention and systemd service/timer.
@@ -193,8 +200,9 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
failed backup timers, plus a controlled Rocky kernel/OpenZFS update and reboot procedure. failed backup timers, plus a controlled Rocky kernel/OpenZFS update and reboot procedure.
- Document and test disaster recovery: rebuild Atlas with Ansible, import the existing pool, restore - Document and test disaster recovery: rebuild Atlas with Ansible, import the existing pool, restore
from snapshot/USB/Hetzner, preserve Vault and Borg recovery material offline, and define RPO/RTO. from snapshot/USB/Hetzner, preserve Vault and Borg recovery material offline, and define RPO/RTO.
- Optionally design iCloud photo ingestion as a separate workflow after the storage and backup layers - Optionally design iCloud photo ingestion and an Aegis persistent NFS mount as a separate workflow
are validated; do not make it a dependency of the Atlas baseline. after the storage and backup layers are validated; do not make either a dependency of the Atlas
baseline.
## Coding Agent Notes ## Coding Agent Notes
- Shared agent definitions and lifecycle flags live in `ai_agents` in `ansible/inventory/group_vars/all.yml`. - Shared agent definitions and lifecycle flags live in `ai_agents` in `ansible/inventory/group_vars/all.yml`.
@@ -221,14 +229,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 - `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. `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. - 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 - `profile_aegis` owns the `nfs-utils` rpm-ostree layer used as the Atlas NFS client and reports the
Podman auto-update timer, LAN-restricted firewalld rules, and SSH hardening. Keep required reboot without initiating it. It also owns rootful Podman Quadlets, persistent container
`aegis_lan_subnet` and `aegis_adguard_web_port` host-specific; SSH permits only the declared 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 key-authenticated users, never root or password authentication. Keep Apple IDs and other
credentials in Vault and use `no_log` for their rendering. 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 - `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 completing initial setup. Disable the local systemd-resolved stub through `profile_aegis` before
AdGuard binds port 53; keep 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 - iCloudPD requires post-deployment interactive MFA initialization; its cookie/configuration state is
persisted in `/var/lib/icloudpd/config`. persisted in `/var/lib/icloudpd/config`.

View File

@@ -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 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 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 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 firewalld rules, SSH key-only access for `pi`, the `nfs-utils` rpm-ostree layer required by the
`aegis_lan_subnet` and `aegis_adguard_web_port` values before applying it. The playbook permits 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 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 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 stub and points `/etc/resolv.conf` to its full resolver data, freeing port 53 for AdGuard. LAN clients
for AdGuard while retaining DNS learned from the router. Define 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 `vault_aegis_icloudpd_apple_id` in Vault before applying it. iCloudPD still requires interactive MFA
initialization after its first deployment. 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 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 ## NAS
`atlas` is a Rocky Linux 9 NAS reached through SSH. Normally its pool already exists and the profile `atlas` is a Rocky Linux 9 NAS reached through SSH. Normally its pool already exists and the profile

View File

@@ -9,6 +9,12 @@ ansible_ssh_use_tty: false
aegis_lan_subnet: 192.168.178.0/24 aegis_lan_subnet: 192.168.178.0/24
aegis_adguard_web_port: 80 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: aegis_ssh_authorized_keys:
- name: ikaros - name: ikaros
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros" key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"

View File

@@ -1,5 +1,11 @@
--- ---
aegis_hostname: aegis 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_adguard_image: docker.io/adguard/adguardhome:latest
aegis_icloudpd_image: docker.io/boredazfcuk/icloudpd:latest aegis_icloudpd_image: docker.io/boredazfcuk/icloudpd:latest
aegis_icloudpd_folder_structure: '{:%Y/%m/%d}' aegis_icloudpd_folder_structure: '{:%Y/%m/%d}'

View File

@@ -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 - name: Require Aegis iCloudPD Apple ID
tags: [aegis, icloudpd] tags: [aegis, icloudpd]
ansible.builtin.assert: ansible.builtin.assert:
@@ -8,15 +22,86 @@
no_log: true no_log: true
- name: Require completed Aegis network placeholders - name: Require completed Aegis network placeholders
tags: [aegis, firewall, services] tags: [aegis, dns, firewall, network, services]
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- aegis_lan_subnet != 'CHANGEME_LAN_SUBNET' - aegis_lan_subnet != 'CHANGEME_LAN_SUBNET'
- aegis_firewalld_zone | length > 0 - aegis_firewalld_zone | length > 0
- aegis_adguard_web_port | int > 0 - aegis_adguard_web_port | int > 0
- aegis_adguard_web_port | int < 65536 - 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 - 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 - name: Set Aegis hostname
tags: [aegis, services] tags: [aegis, services]