Enable Atlas NAS management and document bootstrap workflow

This commit is contained in:
Fabio Scotto di Santolo
2026-09-12 18:53:21 +02:00
parent 8c35ef63c9
commit db10d1296e
9 changed files with 138 additions and 78 deletions

View File

@@ -56,7 +56,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora
- Atlas media stack: - Atlas media stack:
`ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff` `ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff`
- Atlas phase-one rootless services: - Atlas phase-one rootless services:
`ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff -e backend_phase1_enabled=true` `ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff`
- Prometheus/Atlas WireGuard overlay: - Prometheus/Atlas WireGuard overlay:
`ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard --check --diff` `ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard --check --diff`
- DuckDNS config only: `ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff` - DuckDNS config only: `ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff`
@@ -126,15 +126,16 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
- `atlas` is a remote Rocky Linux 9 NAS. Keep its connection, LAN, pool and mountpoint values in - `atlas` is a remote Rocky Linux 9 NAS. Keep its connection, LAN, pool and mountpoint values in
`host_vars/atlas.yml`. Bootstrap it once with `-e atlas_connection_username=<existing-admin>`; `host_vars/atlas.yml`. Bootstrap it once with `-e atlas_connection_username=<existing-admin>`;
subsequent runs use the dedicated Atlas account. subsequent runs use the dedicated Atlas account.
- The pool is pre-existing: never add pool creation, disk partitioning, RAIDZ creation, rollback, - The pool is normally pre-existing. A one-time bootstrap may create it only when `atlas_create_pool=true`
or destruction to the Atlas profile. is explicitly supplied and `atlas_zpool_disks` contains exactly four real `/dev/disk/by-id/...` paths.
- `atlas_manage_storage`, `atlas_manage_firewall`, and `atlas_manage_media_stack` remain false until their placeholders Never partition, force, destroy, roll back, or modify the vdev layout of an existing pool.
and Vault inputs are replaced; only then may the profile manage datasets, shares, LAN-restricted firewall rules, and - `atlas_manage_storage`, `atlas_manage_sharing`, and `atlas_manage_firewall` are enabled in Atlas host vars as
rootful media Quadlets. the declared steady state; set one false only for a deliberate suspension. `atlas_manage_media_stack` remains false
until the future rootful Immich stack has its required Vault inputs and target validation.
- Atlas requires `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash` for Cockpit - Atlas requires `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash` for Cockpit
and, when the relevant gates are enabled, `vault_atlas_samba_password` and and, while sharing is enabled, `vault_atlas_samba_password`. The future rootful media stack also requires
`vault_atlas_immich_db_password`. Never print these values. `vault_atlas_immich_db_password`. Never print these values.
- Atlas creates the complete declared hierarchy only under the verified pre-existing pool: `work`, `archive`, - Atlas creates the complete declared hierarchy only under the verified existing or explicitly bootstrapped pool: `work`, `archive`,
`archive/app_data`, `archive/app_data/navidrome`, `archive/app_data/syncthing`, `media`, `media/music`, `archive/app_data`, `archive/app_data/navidrome`, `archive/app_data/syncthing`, `media`, `media/music`,
`media/photobook`, `backups`, `backups/services`, and `backup_prometheus`. `backups/services` has a `500G` `media/photobook`, `backups`, `backups/services`, and `backup_prometheus`. `backups/services` has a `500G`
refreservation. There is no separate legacy `zpool/syncthing` dataset. refreservation. There is no separate legacy `zpool/syncthing` dataset.
@@ -157,14 +158,13 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
use `10.0.0.2:4533` for Navidrome and `10.0.0.2:8384` for the Syncthing GUI. Native Syncthing transfer/discovery does use `10.0.0.2:4533` for Navidrome and `10.0.0.2:8384` for the Syncthing GUI. Native Syncthing transfer/discovery does
not use the HTTP proxy. not use the HTTP proxy.
- `wireguard_overlay` manages the required `wg0` path between Prometheus and Atlas, persists private keys only on their - `wireguard_overlay` manages the required `wg0` path between Prometheus and Atlas, persists private keys only on their
respective hosts, and exchanges only derived public keys. The first gated run must include both hosts. Prometheus respective hosts, and exchanges only derived public keys. The initial run must include both hosts. Prometheus
opens `51820/udp`; the Atlas backend role admits service ports only in the WireGuard firewalld zone. opens `51820/udp`; the Atlas backend role admits service ports only in the WireGuard firewalld zone.
## Atlas NAS TODO ## Atlas NAS TODO
- Replace every Atlas `CHANGEME` value, provide the required Vault variables and validate the first - Provide the required Vault variables and validate the first remote bootstrap on the real Rocky Linux 9 host.
remote bootstrap on the real Rocky Linux 9 host. Enable `atlas_manage_storage` first and Before the first apply, confirm the pool, mountpoints, LAN subnet and firewalld zone. Keep
`atlas_manage_firewall` only after confirming the pool, mountpoints, LAN subnet and firewalld zone. Enable `atlas_manage_media_stack` disabled until `/dev/dri`, the container paths and the Immich database secret are validated.
`atlas_manage_media_stack` last, after validating `/dev/dri`, the container paths and the Immich database secret.
- Validate the complete baseline on the target: OpenZFS kmod loading, existing pool import, dataset - Validate the complete baseline on the target: OpenZFS kmod loading, existing pool import, dataset
mounts, SSH reconnect, Cockpit and all selected 45Drives plugins, NFSv4, SMB and Syncthing. mounts, SSH reconnect, Cockpit and all selected 45Drives plugins, NFSv4, SMB and Syncthing.
- Finalize dataset properties and the shared UID/GID, group and POSIX ACL model; test the same files - Finalize dataset properties and the shared UID/GID, group and POSIX ACL model; test the same files

View File

@@ -246,13 +246,14 @@ ansible-playbook ansible/site.yml --limit prometheus -e server_username=myuser -
## NAS ## NAS
`atlas` e un NAS Rocky Linux 9 raggiunto tramite SSH. Il pool ZFS esiste gia: il profilo gestisce `atlas` e un NAS Rocky Linux 9 raggiunto tramite SSH. Normalmente il pool ZFS esiste gia e il profilo
solo i dataset figli e non deve mai creare, partizionare, distruggere, fare rollback o modificare il gestisce solo i dataset figli. Un bootstrap RAIDZ2 una tantum e disponibile solo con conferma esplicita
pool. I client Linux usano NFSv4, quelli Windows/WSL SMB; entrambi restano limitati alla LAN (`atlas_create_pool=true`) e quattro percorsi reali e verificati `/dev/disk/by-id/...` in
`atlas_zpool_disks`. Non partiziona, forza, distrugge, esegue rollback o modifica il layout vdev di un
pool esistente. I client Linux usano NFSv4, quelli Windows/WSL SMB; entrambi restano limitati alla LAN
configurata. configurata.
Per il primo avvio sostituire i placeholder di host, pool, mount root, LAN e IP di Aegis e Per il primo avvio fornire `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`,
fornire `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`,
`vault_atlas_samba_password` e `vault_atlas_immich_db_password`. Eseguire il bootstrap tramite `vault_atlas_samba_password` e `vault_atlas_immich_db_password`. Eseguire il bootstrap tramite
l'amministratore esistente: l'amministratore esistente:
@@ -262,13 +263,12 @@ ansible-playbook ansible/site.yml --limit atlas \
``` ```
`vault_atlas_admin_password_hash` deve essere un hash compatibile con `/etc/shadow`, non una `vault_atlas_admin_password_hash` deve essere un hash compatibile con `/etc/shadow`, non una
password Cockpit in chiaro. Le esecuzioni successive usano `atlas_admin_username`. Abilitare password Cockpit in chiaro. Le esecuzioni successive usano `atlas_admin_username`. Atlas dichiara
`atlas_manage_storage` solo dopo aver verificato pool e mountpoint esistenti; abilitare abilitati storage, condivisioni e regole firewall LAN. Prima della prima applicazione verificare pool e
`atlas_manage_firewall` solo dopo aver verificato subnet LAN e zona firewalld attiva. Abilitare mountpoint esistenti, subnet LAN e zona firewalld attiva. `atlas_manage_media_stack` resta disabilitato
`atlas_manage_media_stack` per ultimo, dopo aver verificato `/dev/dri`, i percorsi dei container e il finche non saranno validati `/dev/dri`, i percorsi dei container e il segreto del database Immich.
segreto del database Immich.
Con la gestione storage attiva, Atlas crea l'intera gerarchia sotto il pool `zpool` preesistente: Con la gestione storage attiva, Atlas crea l'intera gerarchia sotto il pool `zpool` esistente o creato esplicitamente:
`work`, `archive`, `archive/app_data`, i dataset applicativi separati `work`, `archive`, `archive/app_data`, i dataset applicativi separati
`archive/app_data/navidrome` e `archive/app_data/syncthing`, `media`, `media/music`, `archive/app_data/navidrome` e `archive/app_data/syncthing`, `media`, `media/music`,
`media/photobook`, `backups`, `backups/services` e `backup_prometheus`. I dataset applicativi e `media/photobook`, `backups`, `backups/services` e `backup_prometheus`. I dataset applicativi e
@@ -284,15 +284,15 @@ Redis, PostgreSQL e NPM condividono una rete Podman. Immich viene eseguito come
ML ricevono `/dev/dri` e Photobook e montato in sola lettura su `/external/photobook`. NPM pubblica `80` e ML ricevono `/dev/dri` e Photobook e montato in sola lettura su `/external/photobook`. NPM pubblica `80` e
`443`, mentre l'amministrazione resta vincolata a `127.0.0.1:81` per l'accesso tramite tunnel SSH. `443`, mentre l'amministrazione resta vincolata a `127.0.0.1:81` per l'accesso tramite tunnel SSH.
La fase 1 e limitata ai Quadlet utente rootless di Navidrome e Syncthing su Atlas ed e protetta dal La fase 1 e limitata ai Quadlet utente rootless di Navidrome e Syncthing su Atlas. E abilitata nella
gate `backend_phase1_enabled`. Navidrome ufficiale `0.63.2` usa il database SQLite sotto `/data` e configurazione host di Atlas e puo essere impostata a `false` solo per una sospensione intenzionale. Navidrome ufficiale `0.63.2` usa il database SQLite sotto `/data` e
non supporta `ND_DATABASE_URL` ne un backend PostgreSQL esterno. Il servizio obsoleto `navidromedb` non supporta `ND_DATABASE_URL` ne un backend PostgreSQL esterno. Il servizio obsoleto `navidromedb`
e quindi rimosso da Prometheus invece di essere replicato su Atlas. Il ruolo deriva i percorsi dal e quindi rimosso da Prometheus invece di essere replicato su Atlas. Il ruolo deriva i percorsi dal
pool esistente `zpool`, montato in `/zpool`: musica in sola lettura da `/zpool/media/music`, stato pool `zpool`, montato in `/zpool`: musica in sola lettura da `/zpool/media/music`, stato
applicativo Navidrome e `navidrome.db` in `/zpool/archive/app_data/navidrome` e dati Syncthing in applicativo Navidrome e `navidrome.db` in `/zpool/archive/app_data/navidrome` e dati Syncthing in
`/zpool/archive/app_data/syncthing`. `profile_atlas` crea questi dataset quando `/zpool/archive/app_data/syncthing`. `profile_atlas` crea questi dataset quando
`atlas_manage_storage` e attivo; il ruolo backend verifica i mountpoint esatti prima di avviare i `atlas_manage_storage` e attivo; il ruolo backend verifica i mountpoint esatti prima di avviare i
container. Nessun ruolo crea il pool. Il ruolo separato `wireguard_overlay` container. Il ruolo backend non crea mai il pool. Il ruolo separato `wireguard_overlay`
gestisce `wg0` tra Prometheus (`10.0.0.1`) e Atlas (`10.0.0.2`), genera una sola volta le chiavi gestisce `wg0` tra Prometheus (`10.0.0.1`) e Atlas (`10.0.0.2`), genera una sola volta le chiavi
private sui rispettivi host e scambia tramite Ansible soltanto quelle pubbliche. Solo Prometheus apre private sui rispettivi host e scambia tramite Ansible soltanto quelle pubbliche. Solo Prometheus apre
pubblicamente `51820/udp`. Le porte backend sono ammesse esclusivamente nella zona firewalld WireGuard. pubblicamente `51820/udp`. Le porte backend sono ammesse esclusivamente nella zona firewalld WireGuard.
@@ -308,20 +308,16 @@ Validare e generare i servizi Atlas con:
```bash ```bash
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit atlas --tags storage \ ansible-playbook ansible/site.yml --limit atlas --tags storage
-e atlas_manage_storage=true
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard \ ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard
-e wireguard_overlay_enabled=true
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff \ ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
-e backend_phase1_enabled=true
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 \ ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1
-e backend_phase1_enabled=true
``` ```
Per il cutover, arrestare il vecchio Navidrome prima di copiare la sua directory dati, verificare Per il cutover, arrestare il vecchio Navidrome prima di copiare la sua directory dati, verificare
@@ -538,7 +534,7 @@ ansible-lint ansible/roles/<role>
yamllint ansible/path/to/file.yml yamllint ansible/path/to/file.yml
podman-compose -f /opt/docker/server/docker-compose.yml config podman-compose -f /opt/docker/server/docker-compose.yml config
ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff -e backend_phase1_enabled=true ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
``` ```
## Tag supportati dal playbook ## Tag supportati dal playbook

View File

@@ -204,13 +204,13 @@ ansible-playbook ansible/site.yml --limit aegis --check --diff --ask-become-pass
## NAS ## NAS
`atlas` is a Rocky Linux 9 NAS reached through SSH. Its pool already exists: the profile only `atlas` is a Rocky Linux 9 NAS reached through SSH. Normally its pool already exists and the profile
manages child datasets and must never create, partition, destroy, roll back, or otherwise alter the only manages child datasets. A one-time RAIDZ2 bootstrap is available only with explicit confirmation
pool itself. Linux clients use NFSv4 and Windows/WSL clients use SMB; both are restricted to the (`atlas_create_pool=true`) and exactly four verified `/dev/disk/by-id/...` paths in `atlas_zpool_disks`.
configured LAN. It never partitions, forces, destroys, rolls back, or changes the vdev layout of an existing pool. Linux
clients use NFSv4 and Windows/WSL clients use SMB; both are restricted to the configured LAN.
For the first run, replace the Atlas host, pool, mount-root, LAN, and Aegis-IP For the first run, provide `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`,
placeholders and provide `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`,
`vault_atlas_samba_password`, and `vault_atlas_immich_db_password`. Bootstrap the host through its `vault_atlas_samba_password`, and `vault_atlas_immich_db_password`. Bootstrap the host through its
existing administrator: existing administrator:
@@ -220,13 +220,13 @@ ansible-playbook ansible/site.yml --limit atlas \
``` ```
`vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text `vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text
Cockpit password. Subsequent runs use `atlas_admin_username`. Enable `atlas_manage_storage` only after Cockpit password. Subsequent runs use `atlas_admin_username`. Atlas declares storage, sharing, and its
checking the existing pool and mountpoints; enable `atlas_manage_firewall` only after checking the LAN LAN firewall rules enabled. Before the first apply, check the existing pool and mountpoints, LAN subnet,
subnet and active firewalld zone. Enable `atlas_manage_media_stack` last, after validating `/dev/dri`, and active firewalld zone. `atlas_manage_media_stack` remains disabled until `/dev/dri`, the container
the container paths and the Immich database secret. paths, and the Immich database secret are validated.
With storage management enabled, Atlas creates the complete dataset hierarchy below the pre-existing With storage management enabled, Atlas creates the complete dataset hierarchy below the existing or
`zpool`: `work`, `archive`, `archive/app_data`, the separate `archive/app_data/navidrome` and explicitly bootstrapped `zpool`: `work`, `archive`, `archive/app_data`, the separate `archive/app_data/navidrome` and
`archive/app_data/syncthing` application datasets, `media`, `media/music`, `media/photobook`, `archive/app_data/syncthing` application datasets, `media`, `media/music`, `media/photobook`,
`backups`, `backups/services`, and `backup_prometheus`. Application/archive datasets use `zstd`, `backups`, `backups/services`, and `backup_prometheus`. Application/archive datasets use `zstd`,
while media, Syncthing and service-backup datasets use `lz4`; `backups/services` also has a `500G` while media, Syncthing and service-backup datasets use `lz4`; `backups/services` also has a `500G`
@@ -240,16 +240,16 @@ and NPM Quadlets share one Podman network. Immich runs as `1100:1100`; Server an
and Photobook is mounted read-only at `/external/photobook`. NPM publishes ports `80` and `443`; its and Photobook is mounted read-only at `/external/photobook`. NPM publishes ports `80` and `443`; its
administration interface remains restricted to `127.0.0.1:81` for SSH-tunnel access. administration interface remains restricted to `127.0.0.1:81` for SSH-tunnel access.
Phase 1 is limited to rootless Navidrome and Syncthing user Quadlets on Atlas and is gated by Phase 1 is limited to rootless Navidrome and Syncthing user Quadlets on Atlas. It is enabled in the
`backend_phase1_enabled`. Official Navidrome `0.63.2` uses its SQLite database below `/data`; it does Atlas host configuration and can be set to `false` only for a deliberate suspension. Official Navidrome `0.63.2` uses its SQLite database below `/data`; it does
not support `ND_DATABASE_URL` or an external PostgreSQL backend. The obsolete `navidromedb` service not support `ND_DATABASE_URL` or an external PostgreSQL backend. The obsolete `navidromedb` service
was therefore removed from Prometheus instead of being reproduced on Atlas. The role derives all was therefore removed from Prometheus instead of being reproduced on Atlas. The role derives all
storage paths from the existing `zpool` mounted at `/zpool`: music is read-only at storage paths from the `zpool` mounted at `/zpool`: music is read-only at
`/zpool/media/music`, Navidrome application state and `navidrome.db` are stored at `/zpool/media/music`, Navidrome application state and `navidrome.db` are stored at
`/zpool/archive/app_data/navidrome`, and Syncthing persists at `/zpool/archive/app_data/navidrome`, and Syncthing persists at
`/zpool/archive/app_data/syncthing`. `profile_atlas` creates these datasets when `/zpool/archive/app_data/syncthing`. `profile_atlas` creates these datasets when
`atlas_manage_storage` is enabled; the backend role verifies their exact mountpoints before starting `atlas_manage_storage` is enabled; the backend role verifies their exact mountpoints before starting
containers. Neither role creates the pool. The separate `wireguard_overlay` role manages `wg0` containers. The backend role never creates the pool. The separate `wireguard_overlay` role manages `wg0`
between Prometheus (`10.0.0.1`) and Atlas (`10.0.0.2`), generating private keys once between Prometheus (`10.0.0.1`) and Atlas (`10.0.0.2`), generating private keys once
on their respective hosts and exchanging only public keys through Ansible. Prometheus alone opens on their respective hosts and exchanging only public keys through Ansible. Prometheus alone opens
`51820/udp` publicly. Backend ports are admitted only in the WireGuard firewalld zone. `51820/udp` publicly. Backend ports are admitted only in the WireGuard firewalld zone.
@@ -265,20 +265,16 @@ Validate and render the Atlas services with:
```bash ```bash
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit atlas --tags storage \ ansible-playbook ansible/site.yml --limit atlas --tags storage
-e atlas_manage_storage=true
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard \ ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard
-e wireguard_overlay_enabled=true
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff \ ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
-e backend_phase1_enabled=true
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 \ ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1
-e backend_phase1_enabled=true
``` ```
For the cutover, stop the old Navidrome writer before copying its data directory, verify ownership by For the cutover, stop the old Navidrome writer before copying its data directory, verify ownership by

View File

@@ -1,5 +1,5 @@
--- ---
# Keep Atlas management gates disabled until the NAS and required Vault inputs are ready. # Atlas declares its intended steady state; set a feature flag false only for a deliberate suspension.
hostname: atlas hostname: atlas
ansible_host: 192.168.178.55 ansible_host: 192.168.178.55
ansible_user: "{{ atlas_connection_username }}" ansible_user: "{{ atlas_connection_username }}"
@@ -17,9 +17,13 @@ atlas_admin_password_hash: "{{ vault_atlas_admin_password_hash | default('!') }}
atlas_lan_subnet: 192.168.178.0/24 atlas_lan_subnet: 192.168.178.0/24
atlas_aegis_ip: 192.168.178.54 atlas_aegis_ip: 192.168.178.54
atlas_manage_firewall: false atlas_manage_firewall: true
atlas_firewalld_zone: public atlas_firewalld_zone: public
atlas_zfs_pool: zpool atlas_zfs_pool: zpool
# Populate only for the first pool bootstrap with four real persistent disk paths.
# Example: /dev/disk/by-id/ata-Seagate_IronWolf_...
atlas_zpool_disks: []
atlas_create_pool: false
atlas_zfs_dataset_work: work atlas_zfs_dataset_work: work
atlas_zfs_dataset_archive: archive atlas_zfs_dataset_archive: archive
atlas_zfs_dataset_app_data: archive/app_data atlas_zfs_dataset_app_data: archive/app_data
@@ -32,10 +36,11 @@ atlas_zfs_dataset_photobook: media/photobook
atlas_zfs_dataset_backups: backups atlas_zfs_dataset_backups: backups
atlas_zfs_dataset_service_backups: backups/services atlas_zfs_dataset_service_backups: backups/services
atlas_mount_root: /zpool atlas_mount_root: /zpool
atlas_manage_storage: false atlas_manage_storage: true
atlas_manage_sharing: true
atlas_manage_media_stack: false atlas_manage_media_stack: false
wireguard_overlay_enabled: false wireguard_overlay_enabled: true
wireguard_address: 10.0.0.2/24 wireguard_address: 10.0.0.2/24
wireguard_peers: wireguard_peers:
- name: prometheus - name: prometheus
@@ -45,7 +50,7 @@ wireguard_peers:
- 10.0.0.0/24 - 10.0.0.0/24
persistent_keepalive: 25 persistent_keepalive: 25
backend_phase1_enabled: false backend_phase1_enabled: true
backend_phase1_start_services: false backend_phase1_start_services: false
backend_phase1_wireguard_address: 10.0.0.2 backend_phase1_wireguard_address: 10.0.0.2

View File

@@ -8,7 +8,7 @@ ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519
server_username: rocky server_username: rocky
server_duckdns_domain: fscotto server_duckdns_domain: fscotto
wireguard_overlay_enabled: false wireguard_overlay_enabled: true
wireguard_address: 10.0.0.1/24 wireguard_address: 10.0.0.1/24
wireguard_listen_port: 51820 wireguard_listen_port: 51820
wireguard_enable_ipv4_forwarding: true wireguard_enable_ipv4_forwarding: true

View File

@@ -1,5 +1,9 @@
--- ---
atlas_manage_storage: false atlas_manage_storage: false
atlas_manage_sharing: false
# Destructive first-boot action; normally false once the pool exists.
atlas_create_pool: false
atlas_zpool_disks: []
atlas_admin_username: CHANGEME_ATLAS_ADMIN atlas_admin_username: CHANGEME_ATLAS_ADMIN
atlas_admin_group: "{{ atlas_admin_username }}" atlas_admin_group: "{{ atlas_admin_username }}"
atlas_admin_home: "/home/{{ atlas_admin_username }}" atlas_admin_home: "/home/{{ atlas_admin_username }}"

View File

@@ -5,6 +5,9 @@
- name: Import Atlas 45Drives tasks - name: Import Atlas 45Drives tasks
ansible.builtin.import_tasks: cockpit.yml ansible.builtin.import_tasks: cockpit.yml
- name: Import Atlas pool bootstrap tasks
ansible.builtin.import_tasks: pool.yml
- name: Import Atlas storage tasks - name: Import Atlas storage tasks
ansible.builtin.import_tasks: storage.yml ansible.builtin.import_tasks: storage.yml

View File

@@ -0,0 +1,56 @@
---
- name: Bootstrap Atlas ZFS pool
tags: [atlas, storage, pool]
when: atlas_create_pool | bool
block:
- name: Validate Atlas pool bootstrap inputs
ansible.builtin.assert:
that:
- atlas_zpool_disks | length == 4
- atlas_zpool_disks | unique | length == 4
- atlas_zpool_disks | select('match', '^/dev/disk/by-id/') | list | length == 4
fail_msg: >-
Set exactly four distinct persistent /dev/disk/by-id paths in
atlas_zpool_disks before creating the RAIDZ2 pool.
- name: Inspect declared Atlas pool disks
ansible.builtin.stat:
path: "{{ item }}"
loop: "{{ atlas_zpool_disks }}"
loop_control:
label: "{{ item }}"
register: atlas_zpool_disk_stats
- name: Require every declared Atlas pool disk
ansible.builtin.assert:
that:
- item.stat.exists
- item.stat.isblk | default(false)
fail_msg: "Declared Atlas pool disk is unavailable or is not a block device: {{ item.item }}"
loop: "{{ atlas_zpool_disk_stats.results }}"
loop_control:
label: "{{ item.item }}"
- name: Check whether the Atlas ZFS pool already exists
ansible.builtin.command:
argv:
- zpool
- list
- -H
- -o
- name
- "{{ atlas_zfs_pool }}"
register: atlas_zpool_bootstrap_check
changed_when: false
failed_when: atlas_zpool_bootstrap_check.rc not in [0, 1]
- name: Create the Atlas RAIDZ2 pool when absent
community.general.zpool:
name: "{{ atlas_zfs_pool }}"
state: present
mountpoint: "{{ atlas_mount_root }}"
force: false
vdevs:
- type: raidz2
disks: "{{ atlas_zpool_disks }}"
when: atlas_zpool_bootstrap_check.rc == 1

View File

@@ -8,7 +8,7 @@
group: root group: root
mode: "0644" mode: "0644"
notify: Reload NFS exports notify: Reload NFS exports
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Enable Atlas file-sharing services - name: Enable Atlas file-sharing services
tags: [atlas, sharing, services] tags: [atlas, sharing, services]
@@ -21,7 +21,7 @@
- smb.service - smb.service
loop_control: loop_control:
label: "{{ item }}" label: "{{ item }}"
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Configure Atlas NFSv4-only service - name: Configure Atlas NFSv4-only service
tags: [atlas, sharing] tags: [atlas, sharing]
@@ -32,7 +32,7 @@
group: root group: root
mode: "0644" mode: "0644"
notify: Restart NFS server notify: Restart NFS server
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Mask Atlas NFSv3 RPC services - name: Mask Atlas NFSv3 RPC services
tags: [atlas, sharing, services] tags: [atlas, sharing, services]
@@ -47,7 +47,7 @@
- rpcbind.socket - rpcbind.socket
loop_control: loop_control:
label: "{{ item }}" label: "{{ item }}"
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Ensure Atlas NFS mount daemon drop-in directory exists - name: Ensure Atlas NFS mount daemon drop-in directory exists
tags: [atlas, sharing, services] tags: [atlas, sharing, services]
@@ -57,7 +57,7 @@
owner: root owner: root
group: root group: root
mode: "0755" mode: "0755"
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Disable Atlas NFSv3 mount daemon listeners - name: Disable Atlas NFSv3 mount daemon listeners
tags: [atlas, sharing, services] tags: [atlas, sharing, services]
@@ -68,7 +68,7 @@
group: root group: root
mode: "0644" mode: "0644"
notify: Restart NFS mount daemon notify: Restart NFS mount daemon
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Enable SELinux access for Atlas file sharing - name: Enable SELinux access for Atlas file sharing
tags: [atlas, sharing, services] tags: [atlas, sharing, services]
@@ -80,7 +80,7 @@
loop_control: loop_control:
label: "{{ item }}" label: "{{ item }}"
when: when:
- atlas_manage_storage | bool - atlas_manage_sharing | bool
- (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled' - (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled'
- name: Render Atlas Samba configuration - name: Render Atlas Samba configuration
@@ -93,7 +93,7 @@
mode: "0644" mode: "0644"
validate: "testparm --suppress-prompt %s" validate: "testparm --suppress-prompt %s"
notify: Restart Samba service notify: Restart Samba service
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Require Vault-backed Atlas Samba accounts - name: Require Vault-backed Atlas Samba accounts
tags: [atlas, sharing] tags: [atlas, sharing]
@@ -109,7 +109,7 @@
- atlas_samba_accounts | selectattr('password', 'equalto', '') | list | length == 0 - atlas_samba_accounts | selectattr('password', 'equalto', '') | list | length == 0
fail_msg: Define every authorized Samba account and its Vault-backed password. fail_msg: Define every authorized Samba account and its Vault-backed password.
no_log: true no_log: true
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Ensure Atlas Samba private state directory exists - name: Ensure Atlas Samba private state directory exists
tags: [atlas, sharing] tags: [atlas, sharing]
@@ -119,7 +119,7 @@
owner: root owner: root
group: root group: root
mode: "0700" mode: "0700"
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Manage Vault-backed Atlas Samba credentials - name: Manage Vault-backed Atlas Samba credentials
tags: [atlas, sharing] tags: [atlas, sharing]
@@ -129,7 +129,7 @@
loop_var: atlas_samba_account loop_var: atlas_samba_account
label: "{{ atlas_samba_account.username }}" label: "{{ atlas_samba_account.username }}"
no_log: true no_log: true
when: atlas_manage_storage | bool when: atlas_manage_sharing | bool
- name: Require completed Atlas firewall placeholders - name: Require completed Atlas firewall placeholders
tags: [atlas, sharing, services] tags: [atlas, sharing, services]