refactor(atlas): separate service and host backup datasets

This commit is contained in:
Fabio Scotto di Santolo
2026-09-17 00:06:46 +02:00
parent e46a2b22d7
commit add75d74e9
5 changed files with 56 additions and 53 deletions

View File

@@ -139,10 +139,10 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
- Atlas requires `vault_atlas_admin_password_hash` for Cockpit and, while sharing is enabled, - Atlas requires `vault_atlas_admin_password_hash` for Cockpit and, while sharing is enabled,
`vault_atlas_samba_password`. The future rootful media stack also requires `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 existing or explicitly bootstrapped pool: `work`, `archive`, - Atlas creates the complete declared hierarchy only under the verified existing or explicitly bootstrapped pool: `archive`,
`archive/app_data`, `archive/app_data/navidrome`, `archive/app_data/syncthing`, `media`, `media/music`, `services`, `services/data`, `services/data/navidrome`, `services/data/syncthing`, `media`, `media/music`,
`media/photobook`, `backups`, `backups/services`, and `backup_prometheus`. `backups/services` has a `500G` `media/photobook`, `backup`, `backup/hosts`, and `backup/hosts/prometheus`. `backup` has a `500G`
refreservation. There is no separate legacy `zpool/syncthing` dataset. reservation covering its descendants. `archive` is the SMB-shared raw-data namespace; container state is never beneath it.
- The `immich` system account is fixed to UID/GID `1100`, has no login shell or `wheel` membership, and receives only - The `immich` system account is fixed to UID/GID `1100`, has no login shell or `wheel` membership, and receives only
the `video` and `render` supplementary groups. Immich's rootful Quadlets run as `1100:1100`; Server and ML receive the `video` and `render` supplementary groups. Immich's rootful Quadlets run as `1100:1100`; Server and ML receive
`/dev/dri`, while the Photobook external library is read-only at `/external/photobook`. `/dev/dri`, while the Photobook external library is read-only at `/external/photobook`.
@@ -154,7 +154,7 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
- `profile_backend_phase1` is limited to rootless Navidrome and Syncthing user Quadlets on Atlas. Official Navidrome - `profile_backend_phase1` is limited to rootless Navidrome and Syncthing user Quadlets on Atlas. Official Navidrome
`0.63.2` uses SQLite below `/data` and does not support `ND_DATABASE_URL` or an external PostgreSQL backend; do not `0.63.2` uses SQLite below `/data` and does not support `ND_DATABASE_URL` or an external PostgreSQL backend; do not
recreate the obsolete Prometheus `navidromedb` service. The role requires the storage role's `zpool/media/music`, recreate the obsolete Prometheus `navidromedb` service. The role requires the storage role's `zpool/media/music`,
`zpool/archive/app_data`, `zpool/archive/app_data/navidrome`, and `zpool/archive/app_data/syncthing` datasets at `zpool/services/data`, `zpool/services/data/navidrome`, and `zpool/services/data/syncthing` datasets at
their exact paths. It never creates the pool. their exact paths. It never creates the pool.
- Keep `backend_phase1_start_services` false until the stopped Prometheus Navidrome data directory has been copied to - Keep `backend_phase1_start_services` false until the stopped Prometheus Navidrome data directory has been copied to
Atlas and its SQLite database verified. The playbook renders the target but never migrates or deletes application Atlas and its SQLite database verified. The playbook renders the target but never migrates or deletes application
@@ -176,7 +176,7 @@ the Prometheus--Atlas WireGuard path are operational. Aegis has validated NFSv4.
and `all_squash` mapping to UID/GID `1100` end-to-end. and `all_squash` mapping to UID/GID `1100` end-to-end.
- Complete the Phase 1 Navidrome cutover: stop the Prometheus writer, copy and verify its complete - Complete the Phase 1 Navidrome cutover: stop the Prometheus writer, copy and verify its complete
`/opt/navidrome/data/` directory (including SQLite sidecars) under `/opt/navidrome/data/` directory (including SQLite sidecars) under
`/zpool/archive/app_data/navidrome/`, then set `backend_phase1_start_services: true` and validate `/zpool/services/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. 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, - 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 folder modes, ignore rules, and GUI/API protection are declared. Validate its GUI and native transfer

View File

@@ -263,11 +263,11 @@ paths, and the Immich database secret are validated. Atlas reads its declared SS
separate files below `~/.ssh/authorized_keys.d/`. separate files below `~/.ssh/authorized_keys.d/`.
With storage management enabled, Atlas creates the complete dataset hierarchy below the existing or With storage management enabled, Atlas creates the complete dataset hierarchy below the existing or
explicitly bootstrapped `zpool`: `work`, `archive`, `archive/app_data`, the separate `archive/app_data/navidrome` and explicitly bootstrapped `zpool`: SMB-shared `archive`, private `services/data` with separate
`archive/app_data/syncthing` application datasets, `media`, `media/music`, `media/photobook`, `services/data/navidrome` and `services/data/syncthing` application datasets, `media`, `media/music`,
`backups`, `backups/services`, and `backup_prometheus`. Application/archive datasets use `zstd`, `media/photobook`, and `backup/hosts/prometheus`. Application/archive datasets use `zstd`, while media,
while media, Syncthing and service-backup datasets use `lz4`; `backups/services` also has a `500G` Syncthing, and host-backup datasets use `lz4`; `backup` has a `500G` reservation covering its descendants.
refreservation. Atlas enforces targeted SELinux persistently and reports, without initiating, any reboot required to activate it. It assigns its primary LAN interface explicitly to the managed firewalld zone and applies persistent kernel network hardening: redirects and source routes are rejected, martians logged, reverse-path filtering remains loose for WireGuard, and IPv4 forwarding is disabled. SSH permits only the declared administrator using public-key authentication; root login, passwords, Atlas enforces targeted SELinux persistently and reports, without initiating, any reboot required to activate it. It assigns its primary LAN interface explicitly to the managed firewalld zone and applies persistent kernel network hardening: redirects and source routes are rejected, martians logged, reverse-path filtering remains loose for WireGuard, and IPv4 forwarding is disabled. SSH permits only the declared administrator using public-key authentication; root login, passwords,
agent and remote forwarding are disabled, while local forwarding remains available for private administrative tunnels. SMB3 exposes `Archive` only to the configured Vault-backed agent and remote forwarding are disabled, while local forwarding remains available for private administrative tunnels. SMB3 exposes `Archive` only to the configured Vault-backed
Samba accounts on encrypted, signed SMB3 over TCP/445 only and admits the configured LAN without host-specific Samba accounts on encrypted, signed SMB3 over TCP/445 only and admits the configured LAN without host-specific
exclusions. NFSv4 exports only `media/photobook` to the configured Aegis IP over TCP/2049, using exclusions. NFSv4 exports only `media/photobook` to the configured Aegis IP over TCP/2049, using
@@ -285,8 +285,8 @@ not support `ND_DATABASE_URL` or an external PostgreSQL backend. The obsolete `n
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 `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/services/data/navidrome`, and Syncthing persists at
`/zpool/archive/app_data/syncthing`. `profile_atlas` creates these datasets when `/zpool/services/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. The backend role never 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
@@ -298,7 +298,7 @@ connectivity. Backend ports are admitted only in the WireGuard firewalld zone.
`backend_phase1_start_services` stays false during the application-state transfer, so the first real `backend_phase1_start_services` stays false during the application-state transfer, so the first real
backend run renders the Quadlets without creating an empty Atlas database. After stopping Navidrome backend run renders the Quadlets without creating an empty Atlas database. After stopping Navidrome
on Prometheus, copy the complete `/opt/navidrome/data/` directory into on Prometheus, copy the complete `/opt/navidrome/data/` directory into
`/zpool/archive/app_data/navidrome/`, preserving `navidrome.db` and any SQLite sidecar files. Then set `/zpool/services/data/navidrome/`, preserving `navidrome.db` and any SQLite sidecar files. Then set
this variable to true and rerun the role to enable and start Navidrome and Syncthing. The playbook this variable to true and rerun the role to enable and start Navidrome and Syncthing. The playbook
never copies or deletes application data. never copies or deletes application data.

View File

@@ -35,17 +35,18 @@ atlas_zpool_disks:
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6A6VJK - /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6A6VJK
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6AC1LM - /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6AC1LM
atlas_create_pool: false atlas_create_pool: false
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_services: services
atlas_zfs_dataset_navidrome: archive/app_data/navidrome atlas_zfs_dataset_app_data: services/data
atlas_zfs_dataset_syncthing: archive/app_data/syncthing atlas_zfs_dataset_navidrome: services/data/navidrome
atlas_zfs_dataset_syncthing: services/data/syncthing
atlas_zfs_dataset_media: media atlas_zfs_dataset_media: media
atlas_zfs_dataset_music: media/music atlas_zfs_dataset_music: media/music
atlas_zfs_dataset_backup_prometheus: backup_prometheus atlas_zfs_dataset_backup: backup
atlas_zfs_dataset_host_backups: backup/hosts
atlas_zfs_dataset_backup_prometheus: backup/hosts/prometheus
atlas_zfs_backup_reservation: 500G
atlas_zfs_dataset_photobook: media/photobook atlas_zfs_dataset_photobook: media/photobook
atlas_zfs_dataset_backups: backups
atlas_zfs_dataset_service_backups: backups/services
atlas_mount_root: /zpool atlas_mount_root: /zpool
atlas_manage_storage: true atlas_manage_storage: true
atlas_manage_sharing: true atlas_manage_sharing: true

View File

@@ -47,31 +47,31 @@ atlas_hardening_sysctls:
net.ipv6.conf.all.accept_source_route: "0" net.ipv6.conf.all.accept_source_route: "0"
net.ipv6.conf.default.accept_source_route: "0" net.ipv6.conf.default.accept_source_route: "0"
atlas_zfs_pool: CHANGEME_ZFS_POOL atlas_zfs_pool: CHANGEME_ZFS_POOL
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_services: services
atlas_zfs_dataset_navidrome: archive/app_data/navidrome atlas_zfs_dataset_app_data: services/data
atlas_zfs_dataset_syncthing: archive/app_data/syncthing atlas_zfs_dataset_navidrome: services/data/navidrome
atlas_zfs_dataset_syncthing: services/data/syncthing
atlas_zfs_dataset_media: media atlas_zfs_dataset_media: media
atlas_zfs_dataset_music: media/music atlas_zfs_dataset_music: media/music
atlas_zfs_dataset_backup_prometheus: backup_prometheus atlas_zfs_dataset_backup: backup
atlas_zfs_dataset_host_backups: backup/hosts
atlas_zfs_dataset_backup_prometheus: backup/hosts/prometheus
atlas_zfs_backup_reservation: 500G
atlas_zfs_dataset_photobook: media/photobook atlas_zfs_dataset_photobook: media/photobook
atlas_zfs_dataset_backups: backups
atlas_zfs_dataset_service_backups: backups/services
atlas_zfs_service_backups_refreservation: 500G
atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT
atlas_archive_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_archive }}" atlas_archive_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_archive }}"
atlas_services_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_services }}"
atlas_app_data_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_app_data }}" atlas_app_data_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_app_data }}"
atlas_navidrome_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_navidrome }}" atlas_navidrome_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_navidrome }}"
atlas_syncthing_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}" atlas_syncthing_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}"
atlas_media_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_media }}" atlas_media_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_media }}"
atlas_music_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_music }}" atlas_music_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_music }}"
atlas_work_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_work }}" atlas_backup_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backup }}"
atlas_host_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_host_backups }}"
atlas_backup_prometheus_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backup_prometheus }}" atlas_backup_prometheus_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backup_prometheus }}"
atlas_photobook_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_photobook }}" atlas_photobook_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_photobook }}"
atlas_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backups }}"
atlas_service_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_service_backups }}"
atlas_45drives_repo_url: https://repo.45drives.com/repofiles/rocky/45drives-enterprise.repo atlas_45drives_repo_url: https://repo.45drives.com/repofiles/rocky/45drives-enterprise.repo
atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo

View File

@@ -3,12 +3,6 @@
tags: [atlas, storage] tags: [atlas, storage]
ansible.builtin.set_fact: ansible.builtin.set_fact:
atlas_zfs_datasets: atlas_zfs_datasets:
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_work }}"
mountpoint: "{{ atlas_work_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
mountpoint: "{{ atlas_work_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_archive }}" - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_archive }}"
mountpoint: "{{ atlas_archive_mountpoint }}" mountpoint: "{{ atlas_archive_mountpoint }}"
owner: "{{ atlas_admin_username }}" owner: "{{ atlas_admin_username }}"
@@ -16,6 +10,13 @@
properties: properties:
compression: zstd compression: zstd
mountpoint: "{{ atlas_archive_mountpoint }}" mountpoint: "{{ atlas_archive_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_services }}"
mountpoint: "{{ atlas_services_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
compression: zstd
mountpoint: "{{ atlas_services_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}" - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
mountpoint: "{{ atlas_app_data_mountpoint }}" mountpoint: "{{ atlas_app_data_mountpoint }}"
owner: "{{ atlas_admin_username }}" owner: "{{ atlas_admin_username }}"
@@ -51,11 +52,27 @@
properties: properties:
compression: lz4 compression: lz4
mountpoint: "{{ atlas_music_mountpoint }}" mountpoint: "{{ atlas_music_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backup }}"
mountpoint: "{{ atlas_backup_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
compression: lz4
mountpoint: "{{ atlas_backup_mountpoint }}"
reservation: "{{ atlas_zfs_backup_reservation }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_host_backups }}"
mountpoint: "{{ atlas_host_backups_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
compression: lz4
mountpoint: "{{ atlas_host_backups_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backup_prometheus }}" - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backup_prometheus }}"
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}" mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
owner: "{{ atlas_admin_username }}" owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}" group: "{{ atlas_admin_group }}"
properties: properties:
compression: lz4
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}" mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_photobook }}" - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_photobook }}"
mountpoint: "{{ atlas_photobook_mountpoint }}" mountpoint: "{{ atlas_photobook_mountpoint }}"
@@ -64,21 +81,6 @@
properties: properties:
compression: lz4 compression: lz4
mountpoint: "{{ atlas_photobook_mountpoint }}" mountpoint: "{{ atlas_photobook_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backups }}"
mountpoint: "{{ atlas_backups_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
compression: lz4
mountpoint: "{{ atlas_backups_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_service_backups }}"
mountpoint: "{{ atlas_service_backups_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
compression: lz4
mountpoint: "{{ atlas_service_backups_mountpoint }}"
refreservation: "{{ atlas_zfs_service_backups_refreservation }}"
- name: Require completed Atlas storage placeholders - name: Require completed Atlas storage placeholders
tags: [atlas, storage] tags: [atlas, storage]