mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 19:03:47 +00:00
Compare commits
9 Commits
64aebe8c34
...
feature/at
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76f3e10ee4 | ||
|
|
c899bb7192 | ||
|
|
2ab5ba6818 | ||
|
|
a347880d4c | ||
|
|
b05a2b4e74 | ||
|
|
75d9081a11 | ||
|
|
b4b3bd10b8 | ||
|
|
db10d1296e | ||
|
|
8c35ef63c9 |
81
AGENTS.md
81
AGENTS.md
@@ -55,8 +55,12 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora
|
|||||||
- Server compose render: `podman-compose -f /opt/docker/server/docker-compose.yml config` and `systemctl status podman-compose-server`
|
- Server compose render: `podman-compose -f /opt/docker/server/docker-compose.yml config` and `systemctl status podman-compose-server`
|
||||||
- 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`
|
||||||
- Prometheus media mount:
|
- Atlas network/share hardening:
|
||||||
`ansible-playbook ansible/site.yml --limit prometheus --tags rclone,navidrome --check --diff`
|
`ansible-playbook ansible/site.yml --limit atlas --tags hardening,sharing --check --diff`
|
||||||
|
- Atlas phase-one rootless services:
|
||||||
|
`ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff`
|
||||||
|
- Prometheus/Atlas WireGuard overlay:
|
||||||
|
`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`
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
@@ -108,16 +112,13 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
|
|||||||
- `rocky_server` is a child of both `platform_rocky` and `server`; `prometheus` is its active target.
|
- `rocky_server` is a child of both `platform_rocky` and `server`; `prometheus` is its active target.
|
||||||
- The target must already provide `server_username` with local sudo access before the profile runs.
|
- The target must already provide `server_username` with local sudo access before the profile runs.
|
||||||
- The Rocky profile installs Podman and podman-compose, uses firewalld, preserves SELinux enforcement, and renders the
|
- The Rocky profile installs Podman and podman-compose, uses firewalld, preserves SELinux enforcement, and renders the
|
||||||
Nginx Proxy Manager/Gitea/Navidrome-PostgreSQL Compose stack with a `podman-compose-server` systemd unit. It does not
|
existing Nginx Proxy Manager/Gitea Compose stack with a `podman-compose-server` systemd unit. PostgreSQL and
|
||||||
start or enable that Compose stack, transfer data, update DNS, or cut over traffic; activating it remains manual.
|
Navidrome are no longer part of the desired Prometheus configuration. The role does not stop or remove legacy
|
||||||
- Prometheus has a gated system `rclone-music.service` and rootless Navidrome Quadlet. They remain disabled until the
|
containers, delete `/opt/postgres/data`, start the Compose stack, update DNS, or cut over traffic.
|
||||||
Atlas WireGuard address, pinned SSH host key and Vault-backed SFTP private key are configured. The rclone mount is
|
|
||||||
read-only at `/mnt/music_atlas`; Navidrome must not start against the underlying empty mountpoint or while the legacy
|
|
||||||
rootful Navidrome container is still running. The role never removes that legacy container or its data.
|
|
||||||
- Firewalld enables SSH, Cockpit (`9090/tcp`), HTTP and HTTPS. Nginx Proxy Manager publishes `80/tcp` and
|
- Firewalld enables SSH, Cockpit (`9090/tcp`), HTTP and HTTPS. Nginx Proxy Manager publishes `80/tcp` and
|
||||||
`443/tcp`; bind its administration interface only to `127.0.0.1:81` and use `npm-tunnel` from Ikaros or Nymph.
|
`443/tcp`; bind its administration interface only to `127.0.0.1:81` and use `npm-tunnel` from Ikaros or Nymph.
|
||||||
Nextcloud remains disabled; do not provision `/srv/nextcloud` directories.
|
Nextcloud remains disabled; do not provision `/srv/nextcloud` directories.
|
||||||
- `scripts/migrate_prometheus_data.sh` is the separate, source-host-run migration path. It dry-runs by
|
- `scripts/migrate_prometheus_data.sh` is the separate, source-host-run NPM/Gitea migration path. It dry-runs by
|
||||||
default and requires explicit source-stack quiescing before copying persistent Docker data with rsync.
|
default and requires explicit source-stack quiescing before copying persistent Docker data with rsync.
|
||||||
- Atlas-only OpenZFS, NFS, Samba, and Syncthing stay selected through Atlas host variables and must not
|
- Atlas-only OpenZFS, NFS, Samba, and Syncthing stay selected through Atlas host variables and must not
|
||||||
leak into `rocky_server`. Cockpit plus its Navigator and Podman extensions are selected explicitly for
|
leak into `rocky_server`. Cockpit plus its Navigator and Podman extensions are selected explicitly for
|
||||||
@@ -127,30 +128,49 @@ 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
|
||||||
- Atlas requires `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash` for Cockpit
|
until the future rootful Immich stack has its required Vault inputs and target validation.
|
||||||
and, when the relevant gates are enabled, `vault_atlas_samba_password` and `vault_atlas_immich_db_password`. Never
|
- Atlas requires `vault_atlas_admin_password_hash` for Cockpit and, while sharing is enabled,
|
||||||
print these values.
|
`vault_atlas_samba_password`. The future rootful media stack also requires
|
||||||
- Atlas creates `archive`, `media/music`, `media/icloud_photos`, and `backups/services` only under the verified
|
`vault_atlas_immich_db_password`. Never print these values.
|
||||||
pre-existing pool; `backups/services` has a `500G` refreservation. Existing Work, Syncthing, and
|
- Atlas creates the complete declared hierarchy only under the verified existing or explicitly bootstrapped pool: `work`, `archive`,
|
||||||
Prometheus-backup datasets remain managed and separate.
|
`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`
|
||||||
|
refreservation. There is no separate legacy `zpool/syncthing` dataset.
|
||||||
- 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 iCloud Photos external library is read-only.
|
`/dev/dri`, while the Photobook external library is read-only at `/external/photobook`.
|
||||||
- Atlas exports iCloud Photos only to the configured Aegis IP with all access squashed to UID/GID `1100`. SMB3 exposes
|
- Atlas 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
|
||||||
`Archive` to Vault-backed authorized accounts and admits the configured LAN without host-specific exclusions.
|
are disabled, while local forwarding remains available for private administrative tunnels. Photobook is exported only to the configured Aegis IP with all access squashed to UID/GID
|
||||||
|
`1100`. Targeted SELinux is enforced persistently; a required reboot is reported but never initiated automatically. The primary LAN interface is assigned explicitly to the managed firewalld zone, and firewall rules are applied before NFS or SMB are started; their service state and TCP listeners are then verified. SMB3 exposes `Archive` to Vault-backed authorized accounts on mandatory encrypted, signed SMB3 over TCP/445 only and admits the configured LAN without host-specific exclusions.
|
||||||
- Atlas NPM and Immich share a rootful Podman network. NPM publishes HTTP/HTTPS, but its administration port remains
|
- Atlas NPM and Immich share a rootful Podman network. NPM publishes HTTP/HTTPS, but its administration port remains
|
||||||
bound to `127.0.0.1:81`; do not expose it directly to the LAN or Internet.
|
bound to `127.0.0.1:81`; do not expose it directly to the LAN or Internet.
|
||||||
|
- `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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
Atlas and its SQLite database verified. The playbook renders the target but never migrates or deletes application
|
||||||
|
data; after cutover, set the flag true to enable and start Navidrome and Syncthing.
|
||||||
|
- Phase 1 must not change Prometheus' existing NPM deployment. NPM continues to be managed exactly by `profile_server`;
|
||||||
|
use `10.0.0.2:4533` for Navidrome and `10.0.0.2:8384` for the Syncthing GUI. Syncthing does not use host networking:
|
||||||
|
its GUI, transfer, QUIC and discovery ports are explicitly published only on `10.0.0.2`; native transfer/discovery does not use the HTTP proxy.
|
||||||
|
- `wireguard_overlay` manages the required `wg0` path between Prometheus and Atlas, persists private keys only on their
|
||||||
|
respective hosts, exchanges only derived public keys, and verifies a real peer handshake. The initial run must
|
||||||
|
include both hosts. Prometheus
|
||||||
|
opens `51820/udp`; after creating the WireGuard firewalld zone, restore Prometheus' rootful Podman networking with
|
||||||
|
`podman network reload --all` so the existing proxy stack retains container DNS. 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
|
||||||
@@ -159,10 +179,11 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
|
|||||||
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
|
- Manage the Syncthing star topology, device IDs, folders, folder modes, ignore rules and protected GUI
|
||||||
or API access for the selected clients.
|
or API access for the selected clients.
|
||||||
- Validate the existing WireGuard path and add its LAN/VPN-only firewalld rules before enabling remote services;
|
- 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.
|
never expose SSH, Cockpit, NFS, SMB or Syncthing through public port forwarding.
|
||||||
- Add the least-privilege Prometheus backup flow: remote dump generation, dedicated SSH identity,
|
- Add the Atlas-initiated least-privilege Prometheus backup pull: Prometheus exposes only prepared
|
||||||
pinned host key, atomic pull, verification, retention and an Atlas systemd service/timer.
|
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.
|
||||||
- Add the encrypted offsite backup with Borg to a Hetzner Storage Box: use a dedicated SSH identity,
|
- Add the encrypted offsite backup with Borg to a Hetzner Storage Box: use a dedicated SSH identity,
|
||||||
pin the host key, keep Borg repository credentials and encryption material in Vault, use
|
pin the host key, keep Borg repository credentials and encryption material in Vault, use
|
||||||
snapshot-consistent sources, and manage retries, logging, pruning, repository checks and restores.
|
snapshot-consistent sources, and manage retries, logging, pruning, repository checks and restores.
|
||||||
|
|||||||
125
README.it.md
125
README.it.md
@@ -180,28 +180,24 @@ Lo stato attuale del profilo server include:
|
|||||||
- installazione pacchetti Rocky via DNF, EPEL e CRB
|
- installazione pacchetti Rocky via DNF, EPEL e CRB
|
||||||
- installazione di Podman e podman-compose
|
- installazione di Podman e podman-compose
|
||||||
- abilitazione dei servizi systemd dichiarati in inventory/group vars
|
- abilitazione dei servizi systemd dichiarati in inventory/group vars
|
||||||
- copia dei dotfiles server e rendering del `docker-compose.yml` per Nginx Proxy Manager, Gitea e il
|
- copia dei dotfiles server e rendering del `docker-compose.yml` per Nginx Proxy Manager e Gitea,
|
||||||
database PostgreSQL esistente di Navidrome, piu l'unita `podman-compose-server` (attivazione manuale)
|
piu l'unita `podman-compose-server` (attivazione manuale)
|
||||||
- mount di `/pool/media/music` da Atlas su `/mnt/music_atlas` tramite il servizio di sistema
|
|
||||||
`rclone-music.service`, e Navidrome tramite Quadlet utente rootless
|
|
||||||
- attivazione di firewalld con SSH, Cockpit (`9090/tcp`), HTTP e HTTPS abilitati
|
- attivazione di firewalld con SSH, Cockpit (`9090/tcp`), HTTP e HTTPS abilitati
|
||||||
- Syncthing escluso dal profilo server Rocky
|
- Syncthing escluso dal profilo server Rocky
|
||||||
|
|
||||||
|
Il Compose desiderato su Prometheus non include piu Navidrome ne il database PostgreSQL obsoleto.
|
||||||
|
Navidrome e Syncthing appartengono ad Atlas; Navidrome ufficiale usa invece SQLite. Il profilo non
|
||||||
|
arresta o rimuove automaticamente eventuali container legacy e non elimina `/opt/postgres/data`.
|
||||||
|
|
||||||
Nginx Proxy Manager pubblica solo `80/tcp` e `443/tcp`; la sua interfaccia di amministrazione e
|
Nginx Proxy Manager pubblica solo `80/tcp` e `443/tcp`; la sua interfaccia di amministrazione e
|
||||||
associata a `127.0.0.1:81` ed e raggiungibile da Ikaros o Nymph con l'alias Bash `npm-tunnel`.
|
associata a `127.0.0.1:81` ed e raggiungibile da Ikaros o Nymph con l'alias Bash `npm-tunnel`.
|
||||||
Nextcloud resta disabilitato e il profilo non crea directory `/srv/nextcloud`.
|
Nextcloud resta disabilitato e il profilo non crea directory `/srv/nextcloud`.
|
||||||
|
|
||||||
Il mount musicale e protetto da `server_atlas_music_enabled`. Prima di abilitarlo, sostituire
|
La fase 1 su Atlas non modifica questo deployment NPM ne i suoi dati persistenti. Dopo aver attivato
|
||||||
l'indirizzo WireGuard e la chiave host SSH fissata in `host_vars/prometheus.yml`, quindi fornire
|
WireGuard e i servizi Atlas, configurare i proxy host NPM correnti con upstream Navidrome
|
||||||
`vault_prometheus_atlas_sftp_private_key` tramite Vault cifrato o variabili locali non tracciate. La
|
`http://10.0.0.2:4533` e upstream per la GUI Syncthing `http://10.0.0.2:8384`. Solo la GUI web di
|
||||||
chiave pubblica corrispondente deve essere gia presente nelle chiavi autorizzate gestite su Atlas.
|
Syncthing usa NPM; il traffico di sincronizzazione resta sulle porte native pubblicate esplicitamente solo
|
||||||
Rclone usa il percorso remoto esatto `/pool/media/music` in sola lettura e una cache VFS completa da
|
sull'indirizzo WireGuard di Atlas. Configurare l'autenticazione Syncthing e una policy di accesso NPM adeguata prima di pubblicare la GUI.
|
||||||
`15G`; systemd lingering mantiene disponibile il manager utente per il Quadlet rootless.
|
|
||||||
Configurare il proxy host NPM di Prometheus per Navidrome come `host.containers.internal:4533`; la
|
|
||||||
porta Navidrome non viene aperta in firewalld.
|
|
||||||
Prima della prima attivazione, arrestare il vecchio container rootful `navidrome`. Il ruolo rifiuta
|
|
||||||
di avviare il sostituto rootless mentre il container precedente e in esecuzione e non rimuove mai
|
|
||||||
automaticamente il container o i dati esistenti.
|
|
||||||
|
|
||||||
### DuckDNS
|
### DuckDNS
|
||||||
|
|
||||||
@@ -226,8 +222,9 @@ salvare separatamente eventuali modifiche non committate senza copiare segreti.
|
|||||||
|
|
||||||
Dopo il provisioning Rocky, eseguire `scripts/migrate_prometheus_data.sh` **sul server Ubuntu
|
Dopo il provisioning Rocky, eseguire `scripts/migrate_prometheus_data.sh` **sul server Ubuntu
|
||||||
sorgente**. Lo script usa rsync, e in dry-run di default; richiede `--quiesce-source --execute` per
|
sorgente**. Lo script usa rsync, e in dry-run di default; richiede `--quiesce-source --execute` per
|
||||||
fermare lo stack sorgente e copiare in modo consistente i dati PostgreSQL. Non avvia container, non
|
fermare lo stack sorgente e copiare in modo consistente soltanto i dati di Nginx Proxy Manager e
|
||||||
cancella dati e non esegue il cutover.
|
Gitea. Non sposta Navidrome o Syncthing, non avvia container, non cancella dati e non esegue il
|
||||||
|
cutover.
|
||||||
|
|
||||||
Utente del profilo server:
|
Utente del profilo server:
|
||||||
|
|
||||||
@@ -249,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:
|
||||||
|
|
||||||
@@ -265,25 +263,70 @@ 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 `archive` (`zstd`), `media/music` (`lz4`),
|
Con la gestione storage attiva, Atlas crea l'intera gerarchia sotto il pool `zpool` esistente o creato esplicitamente:
|
||||||
`media/icloud_photos` (`lz4`) e `backups/services` (`lz4`, `refreservation=500G`) sotto il pool
|
`work`, `archive`, `archive/app_data`, i dataset applicativi separati
|
||||||
preesistente. I dataset esistenti Work, Syncthing e backup Prometheus restano gestiti e separati.
|
`archive/app_data/navidrome` e `archive/app_data/syncthing`, `media`, `media/music`,
|
||||||
SMB3 pubblica `Archive` solo agli account Samba configurati con password in Vault e ammette la LAN
|
`media/photobook`, `backups`, `backups/services` e `backup_prometheus`. I dataset applicativi e
|
||||||
configurata senza esclusioni specifiche per host. NFSv4 esporta soltanto
|
di archivio usano `zstd`; media, Syncthing e backup dei servizi usano `lz4`;
|
||||||
`media/icloud_photos` all'IP configurato di Aegis con `all_squash` verso UID/GID anonimi `1100`.
|
`backups/services` mantiene inoltre una `refreservation` di `500G`.
|
||||||
|
Atlas impone SELinux targeted in modo persistente e segnala, senza avviarlo, l’eventuale reboot necessario per attivarlo. Assegna esplicitamente l’interfaccia LAN primaria alla zona firewalld gestita e applica hardening persistente del kernel di rete: rifiuta redirect e source-route, registra i martian, usa reverse-path filtering loose per WireGuard e disabilita il forwarding IPv4. SSH consente solo l’amministratore dichiarato tramite chiave pubblica; root, password, agent e forwarding
|
||||||
|
remoto sono disabilitati, mentre il forwarding locale resta disponibile per tunnel amministrativi privati. SMB3 pubblica `Archive` solo agli account Samba configurati con password in Vault e
|
||||||
|
ammette la LAN configurata su SMB3 cifrato e firmato, esclusivamente su TCP/445. NFSv4 esporta soltanto
|
||||||
|
`media/photobook` all'IP configurato di Aegis su TCP/2049, con `all_squash` verso UID/GID anonimi `1100`.
|
||||||
|
|
||||||
L'account di sistema `immich` usa UID/GID `1100`, shell senza login, nessuna appartenenza a `wheel` e
|
L'account di sistema `immich` usa UID/GID `1100`, shell senza login, nessuna appartenenza a `wheel` e
|
||||||
i gruppi supplementari `video` e `render`. I Quadlet rootful di Immich Server, ML, cache compatibile
|
i gruppi supplementari `video` e `render`. I Quadlet rootful di Immich Server, ML, cache compatibile
|
||||||
Redis, PostgreSQL e NPM condividono una rete Podman. Immich viene eseguito come `1100:1100`; Server e
|
Redis, PostgreSQL e NPM condividono una rete Podman. Immich viene eseguito come `1100:1100`; Server e
|
||||||
ML ricevono `/dev/dri` e la libreria iCloud Photos e montata in sola lettura. 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. E abilitata nella
|
||||||
|
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`
|
||||||
|
e quindi rimosso da Prometheus invece di essere replicato su Atlas. Il ruolo deriva i percorsi dal
|
||||||
|
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
|
||||||
|
`/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
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
|
`backend_phase1_start_services` resta falso durante il trasferimento dello stato applicativo, quindi
|
||||||
|
la prima esecuzione reale del backend genera i Quadlet senza creare un database Atlas vuoto. Dopo aver
|
||||||
|
arrestato Navidrome su Prometheus, copiare l'intera directory `/opt/navidrome/data/` in
|
||||||
|
`/zpool/archive/app_data/navidrome/`, preservando `navidrome.db` e gli eventuali file SQLite laterali.
|
||||||
|
Impostare quindi questa variabile a vero e rieseguire il ruolo per abilitare e avviare Navidrome e
|
||||||
|
Syncthing. Il playbook non copia e non elimina mai i dati applicativi.
|
||||||
|
|
||||||
|
Validare e generare i servizi Atlas con:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit atlas --tags storage
|
||||||
|
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard
|
||||||
|
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
|
||||||
|
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1
|
||||||
|
```
|
||||||
|
|
||||||
|
Per il cutover, arrestare il vecchio Navidrome prima di copiare la sua directory dati, verificare
|
||||||
|
l'ownership dell'account `admin` su Atlas e confermare la presenza del database SQLite copiato prima
|
||||||
|
di impostare `backend_phase1_start_services: true` in `host_vars/atlas.yml`. Conservare i dati sorgente
|
||||||
|
e il container legacy `navidromedb` fermo finche Navidrome su Atlas e una prova di restore non sono
|
||||||
|
stati validati.
|
||||||
|
|
||||||
Restano da completare retention delle snapshot, topologia Syncthing, validazione WireGuard/firewall,
|
Restano da completare retention delle snapshot, topologia Syncthing, validazione WireGuard/firewall,
|
||||||
pull di backup da Prometheus, backup cifrati con Borg su una Hetzner Storage Box, backup USB,
|
pull di backup da Prometheus, backup cifrati con Borg su una Hetzner Storage Box, backup USB,
|
||||||
monitoraggio e test di disaster recovery. Il backlog operativo dettagliato e in `AGENTS.md`.
|
monitoraggio e test di disaster recovery. Il backlog operativo dettagliato e in `AGENTS.md`.
|
||||||
@@ -348,6 +391,8 @@ I principali ruoli attualmente presenti sono:
|
|||||||
| profile_workstation_dev_wsl | configurazione WSL condivisa per sviluppo |
|
| profile_workstation_dev_wsl | configurazione WSL condivisa per sviluppo |
|
||||||
| profile_server | configurazione server |
|
| profile_server | configurazione server |
|
||||||
| profile_atlas | configurazione NAS Rocky Linux 9 |
|
| profile_atlas | configurazione NAS Rocky Linux 9 |
|
||||||
|
| profile_backend_phase1 | Navidrome e Syncthing rootless su Atlas |
|
||||||
|
| wireguard_overlay | overlay WireGuard Prometheus/Atlas |
|
||||||
| dotfiles_common | distribuzione dotfiles comuni |
|
| dotfiles_common | distribuzione dotfiles comuni |
|
||||||
| dotfiles | distribuzione configurazioni utente |
|
| dotfiles | distribuzione configurazioni utente |
|
||||||
|
|
||||||
@@ -363,7 +408,9 @@ platform_void -> packages_void + services_runit
|
|||||||
platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host
|
platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host
|
||||||
platform_fedora -> packages_fedora + services_systemd
|
platform_fedora -> packages_fedora + services_systemd
|
||||||
platform_rocky -> packages_rocky + services_systemd
|
platform_rocky -> packages_rocky + services_systemd
|
||||||
|
wireguard_overlay -> wireguard_overlay (dopo platform_rocky)
|
||||||
atlas -> profile_atlas
|
atlas -> profile_atlas
|
||||||
|
role_backend_phase1 -> profile_backend_phase1 (dopo atlas)
|
||||||
platform_fedora & role_personal_workstation -> profile_personal_workstation
|
platform_fedora & role_personal_workstation -> profile_personal_workstation
|
||||||
platform_fedora & desktop_gnome -> profile_desktop_gnome
|
platform_fedora & desktop_gnome -> profile_desktop_gnome
|
||||||
workstation_dev_fedora -> profile_workstation_dev_common
|
workstation_dev_fedora -> profile_workstation_dev_common
|
||||||
@@ -379,8 +426,8 @@ Questo significa che, allo stato attuale:
|
|||||||
- `deadalus` riceve il profilo Fedora WSL tramite play dev dedicati
|
- `deadalus` riceve il profilo Fedora WSL tramite play dev dedicati
|
||||||
- il server Rocky (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewalld
|
- il server Rocky (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewalld
|
||||||
- il NAS Rocky (`atlas`) usa un pool ZFS gia esistente, condivisioni NFSv4/SMB limitate alla LAN e Cockpit/45Drives
|
- il NAS Rocky (`atlas`) usa un pool ZFS gia esistente, condivisioni NFSv4/SMB limitate alla LAN e Cockpit/45Drives
|
||||||
- lo stack Compose server include `gitea`, `nginx-proxy-manager` e il database PostgreSQL di
|
- lo stack Compose server include soltanto `gitea` e `nginx-proxy-manager`; Navidrome e Syncthing
|
||||||
Navidrome; Navidrome usa un Quadlet rootless separato e legge il mount rclone di Atlas
|
della fase 1 sono Quadlet rootless su Atlas
|
||||||
|
|
||||||
# Dotfiles
|
# Dotfiles
|
||||||
|
|
||||||
@@ -488,7 +535,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 prometheus --tags rclone,navidrome --check --diff
|
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tag supportati dal playbook
|
## Tag supportati dal playbook
|
||||||
@@ -506,6 +553,7 @@ Allo stato attuale `ansible/site.yml` espone questi tag:
|
|||||||
| `always` | pre-task sempre eseguiti, inclusi caricamento vault e validazioni preliminari | common |
|
| `always` | pre-task sempre eseguiti, inclusi caricamento vault e validazioni preliminari | common |
|
||||||
| `ai_agents` | installazione agenti AI condivisi | Fedora, WSL |
|
| `ai_agents` | installazione agenti AI condivisi | Fedora, WSL |
|
||||||
| `atlas` | account, storage, condivisioni e container Atlas | NAS Atlas |
|
| `atlas` | account, storage, condivisioni e container Atlas | NAS Atlas |
|
||||||
|
| `backend_phase1` | Quadlet rootless Navidrome e Syncthing | NAS Atlas |
|
||||||
| `containers` | Quadlet rootful Atlas | NAS Atlas |
|
| `containers` | Quadlet rootful Atlas | NAS Atlas |
|
||||||
| `dotfiles` | distribuzione/configurazione dotfiles | tutti i profili |
|
| `dotfiles` | distribuzione/configurazione dotfiles | tutti i profili |
|
||||||
| `dotfiles:common` | dotfiles comuni condivisi | common, workstation, server |
|
| `dotfiles:common` | dotfiles comuni condivisi | common, workstation, server |
|
||||||
@@ -521,14 +569,12 @@ Allo stato attuale `ansible/site.yml` espone questi tag:
|
|||||||
| `git` | configurazione Git e GPG desktop | Fedora/GNOME, desktop Void |
|
| `git` | configurazione Git e GPG desktop | Fedora/GNOME, desktop Void |
|
||||||
| `gnome` | configurazione host GNOME | Fedora/GNOME desktop |
|
| `gnome` | configurazione host GNOME | Fedora/GNOME desktop |
|
||||||
| `immich` | account e Quadlet Immich | NAS Atlas |
|
| `immich` | account e Quadlet Immich | NAS Atlas |
|
||||||
| `navidrome` | mount rclone e Quadlet Navidrome rootless | Prometheus |
|
|
||||||
| `sway` | sessione/configurazione sway / SwayFX (Wayland) | desktop Void |
|
| `sway` | sessione/configurazione sway / SwayFX (Wayland) | desktop Void |
|
||||||
| `niri` | sessione/configurazione Niri (Wayland) | desktop Void |
|
| `niri` | sessione/configurazione Niri (Wayland) | desktop Void |
|
||||||
| `npm` | installazione pacchetti npm globali | Fedora/GNOME, desktop Void, WSL |
|
| `npm` | installazione pacchetti npm globali | Fedora/GNOME, desktop Void, WSL |
|
||||||
| `nvidia` | componenti NVIDIA desktop | desktop Void |
|
| `nvidia` | componenti NVIDIA desktop | desktop Void |
|
||||||
| `packages` | installazione e aggiornamento pacchetti | tutti i profili |
|
| `packages` | installazione e aggiornamento pacchetti | tutti i profili |
|
||||||
| `podman` | integrazione Podman Compose e Quadlet rootless | server |
|
| `podman` | integrazione Podman Compose e Quadlet rootless | server |
|
||||||
| `rclone` | mount musica Atlas | Prometheus |
|
|
||||||
| `portal` | configurazione xdg-desktop-portal | desktop Void |
|
| `portal` | configurazione xdg-desktop-portal | desktop Void |
|
||||||
| `services` | gestione servizi runit/systemd | tutti i profili |
|
| `services` | gestione servizi runit/systemd | tutti i profili |
|
||||||
| `sharing` | condivisioni NFSv4 e SMB3 | NAS Atlas |
|
| `sharing` | condivisioni NFSv4 e SMB3 | NAS Atlas |
|
||||||
@@ -536,6 +582,7 @@ Allo stato attuale `ansible/site.yml` espone questi tag:
|
|||||||
| `theme` | configurazione del tema GTK/Qt | desktop Void |
|
| `theme` | configurazione del tema GTK/Qt | desktop Void |
|
||||||
| `tmux` | configurazione e plugin tmux | desktop Fedora/Void, WSL |
|
| `tmux` | configurazione e plugin tmux | desktop Fedora/Void, WSL |
|
||||||
| `vim` | configurazione Vim | dotfiles comuni |
|
| `vim` | configurazione Vim | dotfiles comuni |
|
||||||
|
| `wireguard` | overlay WireGuard Prometheus/Atlas | Prometheus, NAS Atlas |
|
||||||
| `wsl` | bootstrap e configurazione WSL | WSL |
|
| `wsl` | bootstrap e configurazione WSL | WSL |
|
||||||
|
|
||||||
Esempi pratici:
|
Esempi pratici:
|
||||||
|
|||||||
154
README.md
154
README.md
@@ -105,29 +105,24 @@ dotfiles and templates. The profile provisions configuration only: it does not t
|
|||||||
the Compose stack, update DNS, or perform a cutover.
|
the Compose stack, update DNS, or perform a cutover.
|
||||||
|
|
||||||
The server profile installs platform-specific packages, Podman and podman-compose, declared systemd
|
The server profile installs platform-specific packages, Podman and podman-compose, declared systemd
|
||||||
services, and firewalld. The manually activated `podman-compose-server` unit now contains Nginx Proxy
|
services, and firewalld. The manually activated `podman-compose-server` unit contains the existing
|
||||||
Manager, Gitea, and the existing Navidrome PostgreSQL database. Navidrome itself runs as a rootless
|
Nginx Proxy Manager and Gitea services. The desired Compose file no longer includes Navidrome,
|
||||||
user Quadlet and reads the Atlas music dataset from the system `rclone-music.service` mount at
|
Syncthing, or the obsolete Navidrome PostgreSQL database. Navidrome and Syncthing belong to Atlas;
|
||||||
`/mnt/music_atlas`. The Rocky server excludes Syncthing.
|
official Navidrome uses SQLite instead. Applying the profile does not stop or remove legacy
|
||||||
Rocky bind mounts use private SELinux relabeling where supported; the read-only FUSE music mount is
|
containers and does not delete `/opt/postgres/data`.
|
||||||
passed to Navidrome without relabeling.
|
|
||||||
|
|
||||||
The Atlas music path is gated by `server_atlas_music_enabled`. Before enabling it, replace the
|
|
||||||
WireGuard address and pinned SSH host-key placeholders in `host_vars/prometheus.yml`, and provide
|
|
||||||
`vault_prometheus_atlas_sftp_private_key` through encrypted Vault or untracked local vars. The SFTP
|
|
||||||
key's public half must already be present in Atlas' managed authorized keys. Rclone mounts the exact
|
|
||||||
remote path `/pool/media/music` read-only and uses a `15G` full VFS cache; the rootless user manager is
|
|
||||||
kept alive through systemd lingering.
|
|
||||||
Configure the Prometheus NPM proxy host for Navidrome as `host.containers.internal:4533`; the
|
|
||||||
Navidrome port is not opened through firewalld.
|
|
||||||
Before the first enablement, stop the legacy rootful `navidrome` container. The role refuses to start
|
|
||||||
the rootless replacement while that container is running and never removes the old container or data.
|
|
||||||
|
|
||||||
Firewalld enables SSH, Cockpit (`9090/tcp`), HTTP and HTTPS. Nginx Proxy Manager publishes only
|
Firewalld enables SSH, Cockpit (`9090/tcp`), HTTP and HTTPS. Nginx Proxy Manager publishes only
|
||||||
`80/tcp` and `443/tcp`; its administration interface is bound to `127.0.0.1:81` and can be reached
|
`80/tcp` and `443/tcp`; its administration interface is bound to `127.0.0.1:81` and can be reached
|
||||||
from Ikaros or Nymph with the `npm-tunnel` Bash alias. Nextcloud remains disabled and the profile
|
from Ikaros or Nymph with the `npm-tunnel` Bash alias. Nextcloud remains disabled and the profile
|
||||||
does not provision any `/srv/nextcloud` directories.
|
does not provision any `/srv/nextcloud` directories.
|
||||||
|
|
||||||
|
The Atlas phase-one work does not change this NPM deployment or its persistent data. Once WireGuard
|
||||||
|
and the Atlas services are active, configure the current NPM proxy hosts with Navidrome upstream
|
||||||
|
`http://10.0.0.2:4533` and Syncthing GUI upstream `http://10.0.0.2:8384`. Only the Syncthing web GUI
|
||||||
|
uses NPM; synchronization traffic remains on explicitly published native ports bound only to the Atlas
|
||||||
|
WireGuard address. Configure both Syncthing authentication and an appropriate NPM access policy before
|
||||||
|
publishing its GUI.
|
||||||
|
|
||||||
Server identity comes from `server_username`, `server_user_group`, and `server_user_home` in `ansible/inventory/group_vars/server.yml`. `server_username` defaults to `username`, but it can be overridden, for example:
|
Server identity comes from `server_username`, `server_user_group`, and `server_user_home` in `ansible/inventory/group_vars/server.yml`. `server_username` defaults to `username`, but it can be overridden, for example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -138,6 +133,8 @@ ansible-playbook ansible/site.yml --limit prometheus \
|
|||||||
```
|
```
|
||||||
|
|
||||||
The target must already provide `server_username` with local sudo access.
|
The target must already provide `server_username` with local sudo access.
|
||||||
|
Prometheus authorizes its declared SSH public keys through separate files below
|
||||||
|
`~/.ssh/authorized_keys.d/`, while `sshd` is configured to read those files directly.
|
||||||
|
|
||||||
### DuckDNS
|
### DuckDNS
|
||||||
|
|
||||||
@@ -160,7 +157,7 @@ back in; preserve any uncommitted work separately without copying secrets.
|
|||||||
### Data migration
|
### Data migration
|
||||||
|
|
||||||
Provision Rocky first, then run the migration script **on the retired Ubuntu source host**. It is
|
Provision Rocky first, then run the migration script **on the retired Ubuntu source host**. It is
|
||||||
dry-run by default and requires an explicit source-stack stop before it can copy PostgreSQL data:
|
dry-run by default and requires an explicit source-stack stop before it can copy application data:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./scripts/migrate_prometheus_data.sh \
|
sudo ./scripts/migrate_prometheus_data.sh \
|
||||||
@@ -173,11 +170,11 @@ sudo ./scripts/migrate_prometheus_data.sh \
|
|||||||
--quiesce-source --execute
|
--quiesce-source --execute
|
||||||
```
|
```
|
||||||
|
|
||||||
The script copies Navidrome, music, Nginx Proxy Manager, PostgreSQL and Gitea data. It does not
|
The script copies only Nginx Proxy Manager and Gitea data. It does not delete data, move
|
||||||
delete data, move Syncthing, copy `/home/git/.ssh`, start containers, update DNS, or perform a
|
Navidrome/Syncthing, copy `/home/git/.ssh`, start containers, update DNS, or perform a cutover. The
|
||||||
cutover. The destination SSH host key must already be trusted and the destination account needs
|
destination SSH host key must already be trusted and the destination account needs passwordless sudo
|
||||||
passwordless sudo for `rsync`. It preserves ACLs but not extended attributes, so source SELinux labels
|
for `rsync`. It preserves ACLs but not extended attributes, so source SELinux labels are not
|
||||||
are not transferred; the Rocky Compose bind mounts apply their own `:Z` labels when containers start.
|
transferred; the Rocky Compose bind mounts apply their own `:Z` labels when containers start.
|
||||||
|
|
||||||
## DNS Filter
|
## DNS Filter
|
||||||
|
|
||||||
@@ -201,6 +198,10 @@ for AdGuard while retaining DNS learned from the router. 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.
|
||||||
|
|
||||||
|
New Aegis images create the `admin` account in Butane. Before configuring a newly imaged node, run its
|
||||||
|
first playbook execution with `-e ansible_user=admin`; the SSH hardening role then permits that same
|
||||||
|
account. Keep the inventory on `pi` until the existing node has been replaced.
|
||||||
|
|
||||||
Validate the profile before deployment:
|
Validate the profile before deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -210,40 +211,94 @@ 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_admin_password_hash`, `vault_atlas_samba_password`, and
|
||||||
placeholders and provide `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`,
|
`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. Open `51820/udp` towards Prometheus in the provider firewall first, then
|
||||||
existing administrator:
|
include both WireGuard peers in the same idempotent playbook run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook ansible/site.yml --limit atlas \
|
ansible-playbook ansible/site.yml --limit prometheus,atlas \
|
||||||
-e atlas_connection_username=<existing-admin>
|
-e atlas_connection_username=<existing-admin> \
|
||||||
|
-e atlas_create_pool=true
|
||||||
```
|
```
|
||||||
|
|
||||||
`vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text
|
The explicit pool gate is safe to repeat: the role creates the RAIDZ2 pool only when it is absent.
|
||||||
Cockpit password. Subsequent runs use `atlas_admin_username`. Enable `atlas_manage_storage` only after
|
WireGuard waits for a real peer handshake before the play continues.
|
||||||
checking the existing pool and mountpoints; enable `atlas_manage_firewall` only after checking the LAN
|
|
||||||
subnet and active firewalld zone. Enable `atlas_manage_media_stack` last, after validating `/dev/dri`,
|
|
||||||
the container paths and the Immich database secret.
|
|
||||||
|
|
||||||
With storage management enabled, Atlas creates `archive` (`zstd`), `media/music` (`lz4`),
|
`vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text
|
||||||
`media/icloud_photos` (`lz4`), and `backups/services` (`lz4`, `refreservation=500G`) beneath the
|
Cockpit password. Subsequent runs use `atlas_admin_username`. Atlas declares storage, sharing, and its
|
||||||
pre-existing pool. The existing Work, Syncthing, and Prometheus-backup datasets remain managed and
|
LAN firewall rules enabled. Before the first apply, check the existing pool and mountpoints, LAN subnet,
|
||||||
separate. SMB3 exposes `Archive` only to the configured Vault-backed Samba accounts and admits the
|
and active firewalld zone. `atlas_manage_media_stack` remains disabled until `/dev/dri`, the container
|
||||||
configured LAN without host-specific exclusions. NFSv4 exports only
|
paths, and the Immich database secret are validated. Atlas reads its declared SSH public keys from
|
||||||
`media/icloud_photos` to the configured Aegis IP, using `all_squash` with anonymous UID/GID `1100`.
|
separate files below `~/.ssh/authorized_keys.d/`.
|
||||||
|
|
||||||
|
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
|
||||||
|
`archive/app_data/syncthing` application datasets, `media`, `media/music`, `media/photobook`,
|
||||||
|
`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`
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
exclusions. NFSv4 exports only `media/photobook` to the configured Aegis IP over TCP/2049, using
|
||||||
|
`all_squash` with anonymous UID/GID `1100`.
|
||||||
|
|
||||||
The `immich` system account is fixed to UID/GID `1100`, has no login shell or `wheel` membership, and
|
The `immich` system account is fixed to UID/GID `1100`, has no login shell or `wheel` membership, and
|
||||||
receives `video` and `render` access. The rootful Immich Server, ML, Redis-compatible cache, PostgreSQL,
|
receives `video` and `render` access. The rootful Immich Server, ML, Redis-compatible cache, PostgreSQL,
|
||||||
and NPM Quadlets share one Podman network. Immich runs as `1100:1100`; Server and ML receive `/dev/dri`,
|
and NPM Quadlets share one Podman network. Immich runs as `1100:1100`; Server and ML receive `/dev/dri`,
|
||||||
and iCloud Photos is mounted read-only as an external library. 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. It is enabled in the
|
||||||
|
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
|
||||||
|
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
|
||||||
|
`/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/syncthing`. `profile_atlas` creates these datasets when
|
||||||
|
`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`
|
||||||
|
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
|
||||||
|
`51820/udp` publicly. When the WireGuard zone is created, Ansible reloads firewalld and immediately
|
||||||
|
reloads Prometheus' rootful Podman networks so the existing proxy stack retains container DNS and
|
||||||
|
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 run renders the Quadlets without creating an empty Atlas database. After stopping Navidrome
|
||||||
|
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
|
||||||
|
this variable to true and rerun the role to enable and start Navidrome and Syncthing. The playbook
|
||||||
|
never copies or deletes application data.
|
||||||
|
|
||||||
|
Validate and render the Atlas services with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit atlas --tags storage
|
||||||
|
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit prometheus,atlas --tags wireguard
|
||||||
|
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1 --check --diff
|
||||||
|
|
||||||
|
ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||||
|
ansible-playbook ansible/site.yml --limit atlas --tags backend_phase1
|
||||||
|
```
|
||||||
|
|
||||||
|
For the cutover, stop the old Navidrome writer before copying its data directory, verify ownership by
|
||||||
|
the Atlas `admin` account and confirm that the copied SQLite database is present before changing
|
||||||
|
`backend_phase1_start_services: true` in `host_vars/atlas.yml`. Keep the source data and the stopped
|
||||||
|
legacy `navidromedb` container until Navidrome on Atlas and a restore test have been validated.
|
||||||
|
|
||||||
Snapshot retention, Syncthing topology, WireGuard/firewall validation, Prometheus backup pulls,
|
Snapshot retention, Syncthing topology, WireGuard/firewall validation, Prometheus backup pulls,
|
||||||
encrypted Borg backups to a Hetzner Storage Box, USB backup, monitoring, and disaster-recovery tests
|
encrypted Borg backups to a Hetzner Storage Box, USB backup, monitoring, and disaster-recovery tests
|
||||||
remain follow-up work. The detailed operational backlog is kept in `AGENTS.md`.
|
remain follow-up work. The detailed operational backlog is kept in `AGENTS.md`.
|
||||||
@@ -332,6 +387,8 @@ ansible-playbook ansible/site.yml --limit deadalus --tags ai_agents --check --di
|
|||||||
| `profile_workstation_dev_wsl` | WSL development setup. |
|
| `profile_workstation_dev_wsl` | WSL development setup. |
|
||||||
| `profile_server` | Server setup. |
|
| `profile_server` | Server setup. |
|
||||||
| `profile_atlas` | Rocky Linux 9 NAS setup. |
|
| `profile_atlas` | Rocky Linux 9 NAS setup. |
|
||||||
|
| `profile_backend_phase1` | Rootless Navidrome and Syncthing on Atlas. |
|
||||||
|
| `wireguard_overlay` | Prometheus/Atlas WireGuard overlay. |
|
||||||
| `profile_aegis` | Fedora IoT always-on LAN node. |
|
| `profile_aegis` | Fedora IoT always-on LAN node. |
|
||||||
| `dotfiles_common` | Shared user dotfiles. |
|
| `dotfiles_common` | Shared user dotfiles. |
|
||||||
|
|
||||||
@@ -343,8 +400,10 @@ platform_void -> packages_void + services_runit
|
|||||||
platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host
|
platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host
|
||||||
platform_fedora -> packages_fedora + services_systemd
|
platform_fedora -> packages_fedora + services_systemd
|
||||||
platform_rocky -> packages_rocky + services_systemd
|
platform_rocky -> packages_rocky + services_systemd
|
||||||
|
wireguard_overlay -> wireguard_overlay (after platform_rocky)
|
||||||
role_aegis -> profile_aegis
|
role_aegis -> profile_aegis
|
||||||
atlas -> profile_atlas
|
atlas -> profile_atlas
|
||||||
|
role_backend_phase1 -> profile_backend_phase1 (after atlas)
|
||||||
rocky_server -> dotfiles_common + profile_server (after platform_rocky)
|
rocky_server -> dotfiles_common + profile_server (after platform_rocky)
|
||||||
platform_fedora & role_personal_workstation -> profile_personal_workstation
|
platform_fedora & role_personal_workstation -> profile_personal_workstation
|
||||||
platform_fedora & desktop_gnome -> profile_desktop_gnome
|
platform_fedora & desktop_gnome -> profile_desktop_gnome
|
||||||
@@ -414,7 +473,6 @@ 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 prometheus --tags rclone,navidrome --check --diff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tags
|
## Tags
|
||||||
@@ -430,6 +488,7 @@ ansible-playbook ansible/site.yml --list-tags
|
|||||||
| `always` | Common pre-tasks, including optional vault loading. |
|
| `always` | Common pre-tasks, including optional vault loading. |
|
||||||
| `ai_agents` | AI coding-agent install, configuration deployment, and managed-binary removal. |
|
| `ai_agents` | AI coding-agent install, configuration deployment, and managed-binary removal. |
|
||||||
| `atlas` | Atlas NAS account, storage, sharing, and container configuration. |
|
| `atlas` | Atlas NAS account, storage, sharing, and container configuration. |
|
||||||
|
| `backend_phase1` | Rootless Atlas Navidrome and Syncthing Quadlets. |
|
||||||
| `containers` | Rootful Atlas Quadlets. |
|
| `containers` | Rootful Atlas Quadlets. |
|
||||||
| `dotfiles` | User configuration across all profiles. |
|
| `dotfiles` | User configuration across all profiles. |
|
||||||
| `dotfiles:common` | Shared dotfiles. |
|
| `dotfiles:common` | Shared dotfiles. |
|
||||||
@@ -440,15 +499,14 @@ ansible-playbook ansible/site.yml --list-tags
|
|||||||
| `emacs` | Shared Emacs setup and authoring dependencies. |
|
| `emacs` | Shared Emacs setup and authoring dependencies. |
|
||||||
| `gnome` | Fedora/GNOME desktop configuration. |
|
| `gnome` | Fedora/GNOME desktop configuration. |
|
||||||
| `immich` | Atlas Immich account and Quadlets. |
|
| `immich` | Atlas Immich account and Quadlets. |
|
||||||
| `navidrome` | Prometheus rclone mount and rootless Navidrome Quadlet. |
|
|
||||||
| `npm` | Global npm packages. |
|
| `npm` | Global npm packages. |
|
||||||
| `packages` | Package installation and updates. |
|
| `packages` | Package installation and updates. |
|
||||||
| `podman` | Podman Compose and rootless Quadlet integration. |
|
| `podman` | Podman Compose and rootless Quadlet integration. |
|
||||||
| `rclone` | Prometheus Atlas music mount. |
|
|
||||||
| `services` | runit and systemd services. |
|
| `services` | runit and systemd services. |
|
||||||
| `sharing` | Atlas NFSv4 and SMB3 configuration. |
|
| `sharing` | Atlas NFSv4 and SMB3 configuration. |
|
||||||
| `storage` | Atlas child ZFS datasets. |
|
| `storage` | Atlas child ZFS datasets. |
|
||||||
| `tmux` | tmux configuration and plugins. |
|
| `tmux` | tmux configuration and plugins. |
|
||||||
|
| `wireguard` | Prometheus/Atlas WireGuard overlay. |
|
||||||
| `wsl` | WSL bootstrap and configuration. |
|
| `wsl` | WSL bootstrap and configuration. |
|
||||||
|
|
||||||
## Bootstrapping a new machine
|
## Bootstrapping a new machine
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ variant: fiot
|
|||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
passwd:
|
passwd:
|
||||||
users:
|
users:
|
||||||
- name: pi
|
- name: admin
|
||||||
password_hash: "$6$bNDsU0XC5BxNJS5U$ENDGpdOUPJM3wcXBgNESCQkOqqQ9gN72/xEQoJsAI6QdsaY6mD4G5DBVIv7nHwHQOP35IH1oGRl.Uy3R2iUYQ1"
|
password_hash: "$6$bNDsU0XC5BxNJS5U$ENDGpdOUPJM3wcXBgNESCQkOqqQ9gN72/xEQoJsAI6QdsaY6mD4G5DBVIv7nHwHQOP35IH1oGRl.Uy3R2iUYQ1"
|
||||||
groups:
|
groups:
|
||||||
- wheel
|
- wheel
|
||||||
ssh_authorized_keys:
|
ssh_authorized_keys:
|
||||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||||
|
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||||
|
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||||
systemd:
|
systemd:
|
||||||
units:
|
units:
|
||||||
- name: sshd.service
|
- name: sshd.service
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ platform_package_manager: dnf
|
|||||||
platform_service_manager: systemd
|
platform_service_manager: systemd
|
||||||
|
|
||||||
rocky_openzfs_release_rpm: https://zfsonlinux.org/epel/zfs-release-3-0.el9.noarch.rpm
|
rocky_openzfs_release_rpm: https://zfsonlinux.org/epel/zfs-release-3-0.el9.noarch.rpm
|
||||||
|
rocky_openzfs_gpg_key_url: >-
|
||||||
|
https://raw.githubusercontent.com/zfsonlinux/zfsonlinux.github.com/master/zfs-release/RPM-GPG-KEY-openzfs-key2
|
||||||
|
rocky_openzfs_gpg_key_fingerprint: 7DC7 299D CF7C 7FD9 CD87 701B A599 FD5E 9DB8 4141
|
||||||
rocky_syncthing_version: 2.1.3
|
rocky_syncthing_version: 2.1.3
|
||||||
rocky_syncthing_archive_checksum: sha256:f929eb8e5b72a85543eeeefb2c38f34a68e0c530e70758a2905b78840c76602c
|
rocky_syncthing_archive_checksum: sha256:f929eb8e5b72a85543eeeefb2c38f34a68e0c530e70758a2905b78840c76602c
|
||||||
rocky_syncthing_archive_url: >-
|
rocky_syncthing_archive_url: >-
|
||||||
|
|||||||
@@ -6,29 +6,6 @@ effective_username: "{{ server_username }}"
|
|||||||
effective_user_group: "{{ server_user_group }}"
|
effective_user_group: "{{ server_user_group }}"
|
||||||
effective_user_home: "{{ server_user_home }}"
|
effective_user_home: "{{ server_user_home }}"
|
||||||
server_container_stack_dir: /opt/docker/server
|
server_container_stack_dir: /opt/docker/server
|
||||||
server_atlas_music_enabled: false
|
|
||||||
server_atlas_sftp_remote_name: atlas
|
|
||||||
server_atlas_sftp_host: CHANGEME_ATLAS_WIREGUARD_IP
|
|
||||||
server_atlas_sftp_port: 22
|
|
||||||
server_atlas_sftp_username: admin
|
|
||||||
server_atlas_sftp_remote_path: /pool/media/music
|
|
||||||
server_atlas_sftp_private_key: "{{ vault_prometheus_atlas_sftp_private_key | default('') }}"
|
|
||||||
server_atlas_sftp_private_key_file: /etc/rclone/atlas_ed25519
|
|
||||||
server_atlas_sftp_known_host: ""
|
|
||||||
server_atlas_sftp_known_hosts_file: /etc/rclone/known_hosts
|
|
||||||
server_atlas_wireguard_unit: ""
|
|
||||||
server_rclone_config_dir: /etc/rclone
|
|
||||||
server_rclone_config_file: /etc/rclone/rclone.conf
|
|
||||||
server_rclone_music_service: rclone-music.service
|
|
||||||
server_rclone_music_mountpoint: /mnt/music_atlas
|
|
||||||
server_rclone_music_cache_dir: /var/cache/rclone-music
|
|
||||||
server_navidrome_data_dir: /opt/navidrome/data
|
|
||||||
server_navidrome_quadlet_dir: "{{ server_user_home }}/.config/containers/systemd"
|
|
||||||
server_navidrome_env_dir: "{{ server_user_home }}/.config/navidrome"
|
|
||||||
server_navidrome_env_file: "{{ server_navidrome_env_dir }}/navidrome.env"
|
|
||||||
server_navidrome_image: docker.io/deluan/navidrome:latest
|
|
||||||
server_navidrome_port: 4533
|
|
||||||
server_navidrome_db_password: "{{ vault_navidrome_db_password | default('') }}"
|
|
||||||
ai_agents: {}
|
ai_agents: {}
|
||||||
vim_plugins_enabled: false
|
vim_plugins_enabled: false
|
||||||
|
|
||||||
@@ -85,10 +62,6 @@ server_directories:
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
- path: "{{ server_navidrome_data_dir }}"
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0755"
|
|
||||||
- path: /opt/npm/data
|
- path: /opt/npm/data
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
@@ -97,10 +70,6 @@ server_directories:
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
- path: /opt/postgres/data
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0755"
|
|
||||||
- path: /opt/gitea/data
|
- path: /opt/gitea/data
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
@@ -126,3 +95,5 @@ server_sshd_settings:
|
|||||||
|
|
||||||
server_sshd_allow_users:
|
server_sshd_allow_users:
|
||||||
- "{{ server_username }}"
|
- "{{ server_username }}"
|
||||||
|
server_ssh_authorized_keys: []
|
||||||
|
server_ssh_authorized_key_directory: "{{ server_user_home }}/.ssh/authorized_keys.d"
|
||||||
|
|||||||
@@ -9,5 +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_ssh_authorized_keys:
|
||||||
|
- name: ikaros
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||||
|
- name: nymph
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||||
|
- name: siren
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||||
|
|
||||||
aegis_icloudpd_apple_id: "{{ vault_aegis_icloudpd_apple_id | default('') }}"
|
aegis_icloudpd_apple_id: "{{ vault_aegis_icloudpd_apple_id | default('') }}"
|
||||||
|
|||||||
@@ -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 }}"
|
||||||
@@ -12,27 +12,61 @@ atlas_admin_group: "{{ atlas_admin_username }}"
|
|||||||
atlas_admin_home: "/home/{{ atlas_admin_username }}"
|
atlas_admin_home: "/home/{{ atlas_admin_username }}"
|
||||||
atlas_admin_uid: 1000
|
atlas_admin_uid: 1000
|
||||||
atlas_admin_gid: 1000
|
atlas_admin_gid: 1000
|
||||||
atlas_admin_ssh_keys: "{{ vault_atlas_authorized_ssh_keys | default([]) }}"
|
atlas_admin_ssh_keys:
|
||||||
|
- name: ikaros
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||||
|
- name: nymph
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||||
|
- name: siren
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||||
atlas_admin_password_hash: "{{ vault_atlas_admin_password_hash | default('!') }}"
|
atlas_admin_password_hash: "{{ vault_atlas_admin_password_hash | default('!') }}"
|
||||||
|
atlas_ssh_allow_tcp_forwarding: local
|
||||||
|
|
||||||
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.
|
||||||
|
# Confirmed empty 4 TB IronWolf data disks; the NVMe system disk is intentionally excluded.
|
||||||
|
atlas_zpool_disks:
|
||||||
|
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6AC1XM
|
||||||
|
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6A73T2
|
||||||
|
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6A6VJK
|
||||||
|
- /dev/disk/by-id/ata-ST4000VN006-3CW104_WW6AC1LM
|
||||||
|
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_navidrome: archive/app_data/navidrome
|
||||||
|
atlas_zfs_dataset_syncthing: archive/app_data/syncthing
|
||||||
|
atlas_zfs_dataset_media: media
|
||||||
atlas_zfs_dataset_music: media/music
|
atlas_zfs_dataset_music: media/music
|
||||||
atlas_zfs_dataset_syncthing: syncthing
|
|
||||||
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
||||||
atlas_zfs_dataset_icloud_photos: media/icloud_photos
|
atlas_zfs_dataset_photobook: media/photobook
|
||||||
|
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: true
|
||||||
|
wireguard_address: 10.0.0.2/24
|
||||||
|
wireguard_peers:
|
||||||
|
- name: prometheus
|
||||||
|
host: prometheus
|
||||||
|
endpoint: "{{ hostvars['prometheus']['ansible_host'] }}:{{ hostvars['prometheus']['wireguard_listen_port'] }}"
|
||||||
|
allowed_ips:
|
||||||
|
- 10.0.0.0/24
|
||||||
|
persistent_keepalive: 25
|
||||||
|
|
||||||
|
backend_phase1_enabled: true
|
||||||
|
backend_phase1_start_services: false
|
||||||
|
backend_phase1_wireguard_address: 10.0.0.2
|
||||||
|
|
||||||
rocky_manage_openzfs_repo: true
|
rocky_manage_openzfs_repo: true
|
||||||
rocky_manage_syncthing_binary: true
|
rocky_manage_syncthing_binary: false
|
||||||
rocky_manage_podman: true
|
rocky_manage_podman: true
|
||||||
rocky_podman_packages:
|
rocky_podman_packages:
|
||||||
- podman
|
- podman
|
||||||
@@ -40,14 +74,16 @@ rocky_podman_packages:
|
|||||||
host_packages:
|
host_packages:
|
||||||
- cockpit
|
- cockpit
|
||||||
- nfs-utils
|
- nfs-utils
|
||||||
|
- policycoreutils
|
||||||
- policycoreutils-python-utils
|
- policycoreutils-python-utils
|
||||||
|
- python3-libselinux
|
||||||
- samba
|
- samba
|
||||||
- samba-client
|
- samba-client
|
||||||
- samba-common-tools
|
- samba-common-tools
|
||||||
- zfs
|
- zfs
|
||||||
|
|
||||||
atlas_nfs_exports:
|
atlas_nfs_exports:
|
||||||
- path: "{{ atlas_icloud_photos_mountpoint }}"
|
- path: "{{ atlas_photobook_mountpoint }}"
|
||||||
client: "{{ atlas_aegis_ip }}"
|
client: "{{ atlas_aegis_ip }}"
|
||||||
options:
|
options:
|
||||||
- rw
|
- rw
|
||||||
@@ -59,6 +95,7 @@ atlas_nfs_exports:
|
|||||||
|
|
||||||
atlas_samba_share_name: Archive
|
atlas_samba_share_name: Archive
|
||||||
atlas_samba_workgroup: WORKGROUP
|
atlas_samba_workgroup: WORKGROUP
|
||||||
|
atlas_samba_encryption: required
|
||||||
# Append any pre-existing laptop Unix account here and keep its Samba password in Vault.
|
# Append any pre-existing laptop Unix account here and keep its Samba password in Vault.
|
||||||
atlas_samba_accounts:
|
atlas_samba_accounts:
|
||||||
- username: "{{ atlas_admin_username }}"
|
- username: "{{ atlas_admin_username }}"
|
||||||
@@ -68,12 +105,10 @@ atlas_samba_valid_users: "{{ atlas_samba_accounts | map(attribute='username') |
|
|||||||
atlas_immich_db_password: "{{ vault_atlas_immich_db_password | default('') }}"
|
atlas_immich_db_password: "{{ vault_atlas_immich_db_password | default('') }}"
|
||||||
|
|
||||||
atlas_firewalld_rich_rules:
|
atlas_firewalld_rich_rules:
|
||||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="ssh" accept'
|
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="22" protocol="tcp" accept'
|
||||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="cockpit" accept'
|
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="9090" protocol="tcp" accept'
|
||||||
- 'rule family="ipv4" source address="{{ atlas_aegis_ip }}" service name="nfs" accept'
|
- 'rule family="ipv4" source address="{{ atlas_aegis_ip }}" port port="2049" protocol="tcp" accept'
|
||||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="samba" accept'
|
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="445" protocol="tcp" accept'
|
||||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="http" accept'
|
|
||||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="https" accept'
|
|
||||||
|
|
||||||
host_enabled_services:
|
host_enabled_services:
|
||||||
- sshd
|
- sshd
|
||||||
|
|||||||
@@ -7,19 +7,29 @@ ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519
|
|||||||
|
|
||||||
server_username: rocky
|
server_username: rocky
|
||||||
server_duckdns_domain: fscotto
|
server_duckdns_domain: fscotto
|
||||||
server_atlas_music_enabled: false
|
server_ssh_authorized_keys:
|
||||||
server_atlas_sftp_host: CHANGEME_ATLAS_WIREGUARD_IP
|
- name: ikaros
|
||||||
server_atlas_sftp_username: admin
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||||
server_atlas_sftp_known_host: CHANGEME_ATLAS_SSH_HOST_KEY
|
- name: nymph
|
||||||
server_atlas_wireguard_unit: wg-quick@wg0.service
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
|
||||||
server_atlas_sftp_private_key: "{{ vault_prometheus_atlas_sftp_private_key | default('') }}"
|
- name: siren
|
||||||
|
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
|
||||||
|
|
||||||
|
wireguard_overlay_enabled: true
|
||||||
|
wireguard_address: 10.0.0.1/24
|
||||||
|
wireguard_listen_port: 51820
|
||||||
|
wireguard_enable_ipv4_forwarding: true
|
||||||
|
wireguard_reload_rootful_podman_networks: true
|
||||||
|
wireguard_peers:
|
||||||
|
- name: atlas
|
||||||
|
host: atlas
|
||||||
|
allowed_ips:
|
||||||
|
- 10.0.0.2/32
|
||||||
|
|
||||||
host_packages:
|
host_packages:
|
||||||
- cockpit
|
- cockpit
|
||||||
- cockpit-navigator
|
- cockpit-navigator
|
||||||
- cockpit-podman
|
- cockpit-podman
|
||||||
- fuse3
|
|
||||||
- rclone
|
|
||||||
|
|
||||||
host_enabled_services:
|
host_enabled_services:
|
||||||
- cockpit.socket
|
- cockpit.socket
|
||||||
|
|||||||
@@ -95,3 +95,12 @@ all:
|
|||||||
rocky_server:
|
rocky_server:
|
||||||
hosts:
|
hosts:
|
||||||
prometheus:
|
prometheus:
|
||||||
|
|
||||||
|
wireguard_overlay:
|
||||||
|
hosts:
|
||||||
|
prometheus:
|
||||||
|
atlas:
|
||||||
|
|
||||||
|
role_backend_phase1:
|
||||||
|
hosts:
|
||||||
|
atlas:
|
||||||
|
|||||||
@@ -27,6 +27,14 @@
|
|||||||
name: epel-release
|
name: epel-release
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
- name: Import official OpenZFS EL9+ signing key
|
||||||
|
tags: [packages, storage]
|
||||||
|
ansible.builtin.rpm_key:
|
||||||
|
state: present
|
||||||
|
key: "{{ rocky_openzfs_gpg_key_url }}"
|
||||||
|
fingerprint: "{{ rocky_openzfs_gpg_key_fingerprint }}"
|
||||||
|
when: rocky_manage_openzfs_repo | bool
|
||||||
|
|
||||||
- name: Install official OpenZFS repository package
|
- name: Install official OpenZFS repository package
|
||||||
tags: [packages, storage]
|
tags: [packages, storage]
|
||||||
ansible.builtin.dnf:
|
ansible.builtin.dnf:
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ aegis_lan_subnet: CHANGEME_LAN_SUBNET
|
|||||||
aegis_firewalld_zone: public
|
aegis_firewalld_zone: public
|
||||||
aegis_adguard_web_port: 80
|
aegis_adguard_web_port: 80
|
||||||
aegis_ssh_allowed_users:
|
aegis_ssh_allowed_users:
|
||||||
- pi
|
- "{{ ansible_user }}"
|
||||||
aegis_ssh_user_home: "/var/home/{{ ansible_user }}"
|
aegis_ssh_user_home: "/var/home/{{ ansible_user }}"
|
||||||
|
aegis_ssh_authorized_keys: []
|
||||||
|
|||||||
@@ -165,6 +165,29 @@
|
|||||||
path: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys"
|
path: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys"
|
||||||
register: aegis_authorized_keys
|
register: aegis_authorized_keys
|
||||||
|
|
||||||
|
- name: Ensure Aegis SSH authorized key fragments directory exists
|
||||||
|
tags: [aegis, ssh, services]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys.d"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: "0700"
|
||||||
|
when: aegis_ssh_authorized_keys | length > 0
|
||||||
|
|
||||||
|
- name: Manage Aegis SSH authorized key fragments
|
||||||
|
tags: [aegis, ssh, services]
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.key }}\n"
|
||||||
|
dest: "{{ aegis_ssh_user_home }}/.ssh/authorized_keys.d/{{ item.name }}"
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: "0600"
|
||||||
|
loop: "{{ aegis_ssh_authorized_keys }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: aegis_ssh_authorized_keys | length > 0
|
||||||
|
|
||||||
- name: Find Aegis SSH authorized key fragments
|
- name: Find Aegis SSH authorized key fragments
|
||||||
tags: [aegis, ssh, services]
|
tags: [aegis, ssh, services]
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -178,7 +201,8 @@
|
|||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- >-
|
- >-
|
||||||
(aegis_authorized_keys.stat.exists and aegis_authorized_keys.stat.size | int > 0)
|
(aegis_ssh_authorized_keys | length > 0)
|
||||||
|
or (aegis_authorized_keys.stat.exists and aegis_authorized_keys.stat.size | int > 0)
|
||||||
or aegis_authorized_key_fragments.matched | int > 0
|
or aegis_authorized_key_fragments.matched | int > 0
|
||||||
fail_msg: Add a public key for the Ansible SSH user before disabling password authentication.
|
fail_msg: Add a public key for the Ansible SSH user before disabling password authentication.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
---
|
---
|
||||||
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 }}"
|
||||||
atlas_admin_uid: 1000
|
atlas_admin_uid: 1000
|
||||||
atlas_admin_gid: 1000
|
atlas_admin_gid: 1000
|
||||||
atlas_admin_ssh_keys: []
|
atlas_admin_ssh_keys: []
|
||||||
|
atlas_admin_ssh_key_directory: "{{ atlas_admin_home }}/.ssh/authorized_keys.d"
|
||||||
atlas_admin_password_hash: "!"
|
atlas_admin_password_hash: "!"
|
||||||
|
# Local forwarding permits an administrator to tunnel a private service without allowing remote forwards.
|
||||||
|
atlas_ssh_allow_tcp_forwarding: local
|
||||||
|
|
||||||
atlas_immich_username: immich
|
atlas_immich_username: immich
|
||||||
atlas_immich_group: immich
|
atlas_immich_group: immich
|
||||||
@@ -20,26 +27,51 @@ atlas_lan_subnet: CHANGEME_LAN_SUBNET
|
|||||||
atlas_aegis_ip: CHANGEME_AEGIS_IP
|
atlas_aegis_ip: CHANGEME_AEGIS_IP
|
||||||
atlas_manage_firewall: false
|
atlas_manage_firewall: false
|
||||||
atlas_firewalld_zone: public
|
atlas_firewalld_zone: public
|
||||||
|
atlas_hardening_sysctls:
|
||||||
|
net.ipv4.conf.all.accept_redirects: "0"
|
||||||
|
net.ipv4.conf.default.accept_redirects: "0"
|
||||||
|
net.ipv4.conf.all.send_redirects: "0"
|
||||||
|
net.ipv4.conf.default.send_redirects: "0"
|
||||||
|
net.ipv4.conf.all.accept_source_route: "0"
|
||||||
|
net.ipv4.conf.default.accept_source_route: "0"
|
||||||
|
net.ipv4.conf.all.log_martians: "1"
|
||||||
|
net.ipv4.conf.default.log_martians: "1"
|
||||||
|
net.ipv4.conf.all.rp_filter: "2"
|
||||||
|
net.ipv4.conf.default.rp_filter: "2"
|
||||||
|
net.ipv4.tcp_syncookies: "1"
|
||||||
|
net.ipv4.icmp_echo_ignore_broadcasts: "1"
|
||||||
|
net.ipv4.icmp_ignore_bogus_error_responses: "1"
|
||||||
|
net.ipv4.ip_forward: "0"
|
||||||
|
net.ipv6.conf.all.accept_redirects: "0"
|
||||||
|
net.ipv6.conf.default.accept_redirects: "0"
|
||||||
|
net.ipv6.conf.all.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_work: work
|
||||||
atlas_zfs_dataset_archive: archive
|
atlas_zfs_dataset_archive: archive
|
||||||
|
atlas_zfs_dataset_app_data: archive/app_data
|
||||||
|
atlas_zfs_dataset_navidrome: archive/app_data/navidrome
|
||||||
|
atlas_zfs_dataset_syncthing: archive/app_data/syncthing
|
||||||
|
atlas_zfs_dataset_media: media
|
||||||
atlas_zfs_dataset_music: media/music
|
atlas_zfs_dataset_music: media/music
|
||||||
atlas_zfs_dataset_syncthing: syncthing
|
|
||||||
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
||||||
atlas_zfs_dataset_icloud_photos: media/icloud_photos
|
atlas_zfs_dataset_photobook: media/photobook
|
||||||
|
atlas_zfs_dataset_backups: backups
|
||||||
atlas_zfs_dataset_service_backups: backups/services
|
atlas_zfs_dataset_service_backups: backups/services
|
||||||
atlas_zfs_service_backups_refreservation: 500G
|
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_app_data_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_app_data }}"
|
||||||
|
atlas_navidrome_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||||
|
atlas_syncthing_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||||
|
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_work_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_work }}"
|
||||||
atlas_syncthing_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}"
|
|
||||||
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_icloud_photos_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_icloud_photos }}"
|
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_service_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||||
atlas_syncthing_config_dir: "{{ atlas_admin_home }}/.local/state/syncthing"
|
|
||||||
atlas_syncthing_default_dir: "{{ atlas_syncthing_mountpoint }}/Sync"
|
|
||||||
|
|
||||||
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
|
||||||
@@ -51,7 +83,7 @@ atlas_45drives_packages:
|
|||||||
- cockpit-scheduler
|
- cockpit-scheduler
|
||||||
|
|
||||||
atlas_nfs_exports:
|
atlas_nfs_exports:
|
||||||
- path: "{{ atlas_icloud_photos_mountpoint }}"
|
- path: "{{ atlas_photobook_mountpoint }}"
|
||||||
client: "{{ atlas_aegis_ip }}"
|
client: "{{ atlas_aegis_ip }}"
|
||||||
options:
|
options:
|
||||||
- rw
|
- rw
|
||||||
@@ -62,6 +94,8 @@ atlas_nfs_exports:
|
|||||||
- "anongid={{ atlas_immich_gid }}"
|
- "anongid={{ atlas_immich_gid }}"
|
||||||
atlas_samba_share_name: Archive
|
atlas_samba_share_name: Archive
|
||||||
atlas_samba_workgroup: WORKGROUP
|
atlas_samba_workgroup: WORKGROUP
|
||||||
|
# Keep encryption mandatory unless a verified client-compatibility exception is explicitly required.
|
||||||
|
atlas_samba_encryption: required
|
||||||
atlas_samba_valid_users: []
|
atlas_samba_valid_users: []
|
||||||
atlas_samba_accounts: []
|
atlas_samba_accounts: []
|
||||||
atlas_samba_password_marker_dir: /var/lib/samba/private/ansible-passwords
|
atlas_samba_password_marker_dir: /var/lib/samba/private/ansible-passwords
|
||||||
|
|||||||
@@ -28,11 +28,6 @@
|
|||||||
name: smb
|
name: smb
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|
||||||
- name: Restart Atlas Syncthing service
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: atlas-syncthing
|
|
||||||
state: restarted
|
|
||||||
|
|
||||||
- name: Restart Atlas media Quadlets
|
- name: Restart Atlas media Quadlets
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: "{{ item }}"
|
name: "{{ item }}"
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
- atlas_admin_username != 'CHANGEME_ATLAS_ADMIN'
|
- atlas_admin_username != 'CHANGEME_ATLAS_ADMIN'
|
||||||
- (atlas_admin_ssh_keys | default([])) | length > 0
|
- (atlas_admin_ssh_keys | default([])) | length > 0
|
||||||
- atlas_admin_password_hash not in ['', '!', '*']
|
- atlas_admin_password_hash not in ['', '!', '*']
|
||||||
|
- atlas_ssh_allow_tcp_forwarding in ['no', 'yes', 'local', 'remote', 'all']
|
||||||
- "'wheel' not in atlas_immich_supplementary_groups"
|
- "'wheel' not in atlas_immich_supplementary_groups"
|
||||||
fail_msg: >-
|
fail_msg: >-
|
||||||
Define atlas_admin_username, vault_atlas_authorized_ssh_keys and
|
Define atlas_admin_username, atlas_admin_ssh_keys and vault_atlas_admin_password_hash
|
||||||
vault_atlas_admin_password_hash before applying the Atlas profile.
|
before applying the Atlas profile.
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
- name: Create Atlas administrator group
|
- name: Create Atlas administrator group
|
||||||
@@ -77,13 +78,26 @@
|
|||||||
mode: "0440"
|
mode: "0440"
|
||||||
validate: "visudo -cf %s"
|
validate: "visudo -cf %s"
|
||||||
|
|
||||||
- name: Manage Atlas administrator authorized SSH keys exclusively
|
- name: Ensure Atlas administrator SSH authorized key fragments directory exists
|
||||||
tags: [atlas, services]
|
tags: [atlas, services]
|
||||||
ansible.posix.authorized_key:
|
ansible.builtin.file:
|
||||||
user: "{{ atlas_admin_username }}"
|
path: "{{ atlas_admin_ssh_key_directory }}"
|
||||||
key: "{{ atlas_admin_ssh_keys | join('\n') }}"
|
state: directory
|
||||||
state: present
|
owner: "{{ atlas_admin_username }}"
|
||||||
exclusive: true
|
group: "{{ atlas_admin_group }}"
|
||||||
|
mode: "0700"
|
||||||
|
|
||||||
|
- name: Manage Atlas administrator SSH authorized key fragments
|
||||||
|
tags: [atlas, services]
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.key }}\n"
|
||||||
|
dest: "{{ atlas_admin_ssh_key_directory }}/{{ item.name }}"
|
||||||
|
owner: "{{ atlas_admin_username }}"
|
||||||
|
group: "{{ atlas_admin_group }}"
|
||||||
|
mode: "0600"
|
||||||
|
loop: "{{ atlas_admin_ssh_keys }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
- name: Check whether the Atlas SSH host key exists
|
- name: Check whether the Atlas SSH host key exists
|
||||||
tags: [atlas, services]
|
tags: [atlas, services]
|
||||||
@@ -149,6 +163,18 @@
|
|||||||
- "'pubkeyauthentication yes' in atlas_sshd_effective_configuration.stdout_lines"
|
- "'pubkeyauthentication yes' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
- "'passwordauthentication no' in atlas_sshd_effective_configuration.stdout_lines"
|
- "'passwordauthentication no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
- "'kbdinteractiveauthentication no' in atlas_sshd_effective_configuration.stdout_lines"
|
- "'kbdinteractiveauthentication no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'authenticationmethods publickey' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'x11forwarding no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "('allowtcpforwarding ' ~ atlas_ssh_allow_tcp_forwarding) in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'allowagentforwarding no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'gatewayports no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'permittunnel no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'permituserenvironment no' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'maxauthtries 3' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'logingracetime 30' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'clientaliveinterval 300' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'clientalivecountmax 2' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
|
- "'loglevel VERBOSE' in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
- "('allowusers ' ~ atlas_admin_username) in atlas_sshd_effective_configuration.stdout_lines"
|
- "('allowusers ' ~ atlas_admin_username) in atlas_sshd_effective_configuration.stdout_lines"
|
||||||
fail_msg: The effective Atlas SSH configuration does not match the required hardening.
|
fail_msg: The effective Atlas SSH configuration does not match the required hardening.
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|||||||
45
ansible/roles/profile_atlas/tasks/hardening.yml
Normal file
45
ansible/roles/profile_atlas/tasks/hardening.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
- name: Enforce targeted SELinux on Atlas
|
||||||
|
tags: [atlas, hardening, security]
|
||||||
|
ansible.posix.selinux:
|
||||||
|
policy: targeted
|
||||||
|
state: enforcing
|
||||||
|
update_kernel_param: true
|
||||||
|
register: atlas_selinux_enforcement
|
||||||
|
|
||||||
|
- name: Report when Atlas requires a reboot for SELinux enforcement
|
||||||
|
tags: [atlas, hardening, security]
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: Reboot Atlas before relying on SELinux enforcement.
|
||||||
|
when: atlas_selinux_enforcement.reboot_required | default(false)
|
||||||
|
|
||||||
|
- name: Apply Atlas network-kernel hardening
|
||||||
|
tags: [atlas, hardening, security]
|
||||||
|
ansible.posix.sysctl:
|
||||||
|
name: "{{ item.key }}"
|
||||||
|
value: "{{ item.value }}"
|
||||||
|
state: present
|
||||||
|
sysctl_set: true
|
||||||
|
reload: true
|
||||||
|
loop: "{{ atlas_hardening_sysctls | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}={{ item.value }}"
|
||||||
|
|
||||||
|
- name: Read active Atlas SELinux mode
|
||||||
|
tags: [atlas, hardening, security]
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- getenforce
|
||||||
|
register: atlas_selinux_mode
|
||||||
|
changed_when: false
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Require active SELinux enforcement on Atlas
|
||||||
|
tags: [atlas, hardening, security]
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- atlas_selinux_mode.stdout == 'Enforcing'
|
||||||
|
fail_msg: >-
|
||||||
|
Atlas SELinux is not enforcing. Reboot if requested by the preceding
|
||||||
|
SELinux task, then rerun the hardening role before enabling services.
|
||||||
|
when: not ansible_check_mode
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
- 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 network hardening tasks
|
||||||
|
ansible.builtin.import_tasks: hardening.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
|
||||||
|
|
||||||
@@ -13,6 +19,3 @@
|
|||||||
|
|
||||||
- name: Import Atlas media-container tasks
|
- name: Import Atlas media-container tasks
|
||||||
ansible.builtin.import_tasks: containers.yml
|
ansible.builtin.import_tasks: containers.yml
|
||||||
|
|
||||||
- name: Import Atlas Syncthing tasks
|
|
||||||
ansible.builtin.import_tasks: syncthing.yml
|
|
||||||
|
|||||||
57
ansible/roles/profile_atlas/tasks/pool.yml
Normal file
57
ansible/roles/profile_atlas/tasks/pool.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
- 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 }}"
|
||||||
|
follow: true
|
||||||
|
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
|
||||||
@@ -1,136 +1,3 @@
|
|||||||
---
|
|
||||||
- name: Render Atlas NFS exports
|
|
||||||
tags: [atlas, sharing]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: atlas.exports.j2
|
|
||||||
dest: /etc/exports.d/atlas.exports
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
notify: Reload NFS exports
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Enable Atlas file-sharing services
|
|
||||||
tags: [atlas, sharing, services]
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "{{ item }}"
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
loop:
|
|
||||||
- nfs-server.service
|
|
||||||
- smb.service
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Configure Atlas NFSv4-only service
|
|
||||||
tags: [atlas, sharing]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: atlas-nfs.conf.j2
|
|
||||||
dest: /etc/nfs.conf.d/atlas.conf
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
notify: Restart NFS server
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Mask Atlas NFSv3 RPC services
|
|
||||||
tags: [atlas, sharing, services]
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "{{ item }}"
|
|
||||||
enabled: false
|
|
||||||
state: stopped
|
|
||||||
masked: true
|
|
||||||
loop:
|
|
||||||
- rpc-statd.service
|
|
||||||
- rpcbind.service
|
|
||||||
- rpcbind.socket
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Ensure Atlas NFS mount daemon drop-in directory exists
|
|
||||||
tags: [atlas, sharing, services]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/systemd/system/nfs-mountd.service.d
|
|
||||||
state: directory
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0755"
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Disable Atlas NFSv3 mount daemon listeners
|
|
||||||
tags: [atlas, sharing, services]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: nfs-mountd-v4only.conf.j2
|
|
||||||
dest: /etc/systemd/system/nfs-mountd.service.d/v4only.conf
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
notify: Restart NFS mount daemon
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Enable SELinux access for Atlas file sharing
|
|
||||||
tags: [atlas, sharing, services]
|
|
||||||
ansible.posix.seboolean:
|
|
||||||
name: "{{ item }}"
|
|
||||||
state: true
|
|
||||||
persistent: true
|
|
||||||
loop: "{{ atlas_selinux_booleans }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
when:
|
|
||||||
- atlas_manage_storage | bool
|
|
||||||
- (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled'
|
|
||||||
|
|
||||||
- name: Render Atlas Samba configuration
|
|
||||||
tags: [atlas, sharing]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: smb.conf.j2
|
|
||||||
dest: /etc/samba/smb.conf
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
validate: "testparm --suppress-prompt %s"
|
|
||||||
notify: Restart Samba service
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Require Vault-backed Atlas Samba accounts
|
|
||||||
tags: [atlas, sharing]
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- atlas_samba_accounts | length > 0
|
|
||||||
- >-
|
|
||||||
atlas_samba_accounts | map(attribute='username') | list
|
|
||||||
| difference(atlas_samba_valid_users) | length == 0
|
|
||||||
- >-
|
|
||||||
atlas_samba_valid_users
|
|
||||||
| difference(atlas_samba_accounts | map(attribute='username') | list) | length == 0
|
|
||||||
- atlas_samba_accounts | selectattr('password', 'equalto', '') | list | length == 0
|
|
||||||
fail_msg: Define every authorized Samba account and its Vault-backed password.
|
|
||||||
no_log: true
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Ensure Atlas Samba private state directory exists
|
|
||||||
tags: [atlas, sharing]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ atlas_samba_password_marker_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0700"
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Manage Vault-backed Atlas Samba credentials
|
|
||||||
tags: [atlas, sharing]
|
|
||||||
ansible.builtin.include_tasks: samba_account.yml
|
|
||||||
loop: "{{ atlas_samba_accounts }}"
|
|
||||||
loop_control:
|
|
||||||
loop_var: atlas_samba_account
|
|
||||||
label: "{{ atlas_samba_account.username }}"
|
|
||||||
no_log: true
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Require completed Atlas firewall placeholders
|
- name: Require completed Atlas firewall placeholders
|
||||||
tags: [atlas, sharing, services]
|
tags: [atlas, sharing, services]
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
@@ -138,6 +5,7 @@
|
|||||||
- atlas_lan_subnet != 'CHANGEME_LAN_SUBNET'
|
- atlas_lan_subnet != 'CHANGEME_LAN_SUBNET'
|
||||||
- atlas_aegis_ip != 'CHANGEME_AEGIS_IP'
|
- atlas_aegis_ip != 'CHANGEME_AEGIS_IP'
|
||||||
- atlas_firewalld_zone | length > 0
|
- atlas_firewalld_zone | length > 0
|
||||||
|
- ansible_facts.default_ipv4.interface | default('') | length > 0
|
||||||
fail_msg: Replace the Atlas LAN, Aegis and firewall-zone placeholders.
|
fail_msg: Replace the Atlas LAN, Aegis and firewall-zone placeholders.
|
||||||
when: atlas_manage_firewall | bool
|
when: atlas_manage_firewall | bool
|
||||||
|
|
||||||
@@ -154,6 +22,16 @@
|
|||||||
label: "{{ item }}"
|
label: "{{ item }}"
|
||||||
when: atlas_manage_firewall | bool
|
when: atlas_manage_firewall | bool
|
||||||
|
|
||||||
|
- name: Assign primary Atlas LAN interface to managed firewalld zone
|
||||||
|
tags: [atlas, sharing, services]
|
||||||
|
ansible.posix.firewalld:
|
||||||
|
interface: "{{ ansible_facts.default_ipv4.interface }}"
|
||||||
|
zone: "{{ atlas_firewalld_zone }}"
|
||||||
|
state: enabled
|
||||||
|
permanent: true
|
||||||
|
immediate: true
|
||||||
|
when: atlas_manage_firewall | bool
|
||||||
|
|
||||||
- name: Remove unrestricted Atlas services from firewalld zone
|
- name: Remove unrestricted Atlas services from firewalld zone
|
||||||
tags: [atlas, sharing, services]
|
tags: [atlas, sharing, services]
|
||||||
ansible.posix.firewalld:
|
ansible.posix.firewalld:
|
||||||
@@ -166,3 +44,228 @@
|
|||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item }}"
|
label: "{{ item }}"
|
||||||
when: atlas_manage_firewall | bool
|
when: atlas_manage_firewall | bool
|
||||||
|
|
||||||
|
- name: Render Atlas NFS exports
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: atlas.exports.j2
|
||||||
|
dest: /etc/exports.d/atlas.exports
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload NFS exports
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Ensure Atlas NFS configuration drop-in directory exists
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/nfs.conf.d
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Configure Atlas NFSv4-only service
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: atlas-nfs.conf.j2
|
||||||
|
dest: /etc/nfs.conf.d/atlas.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart NFS server
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Mask Atlas NFSv3 RPC services
|
||||||
|
tags: [atlas, sharing, services]
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: false
|
||||||
|
state: stopped
|
||||||
|
masked: true
|
||||||
|
loop:
|
||||||
|
- rpc-statd.service
|
||||||
|
- rpcbind.service
|
||||||
|
- rpcbind.socket
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Ensure Atlas NFS mount daemon drop-in directory exists
|
||||||
|
tags: [atlas, sharing, services]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/systemd/system/nfs-mountd.service.d
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Disable Atlas NFSv3 mount daemon listeners
|
||||||
|
tags: [atlas, sharing, services]
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: nfs-mountd-v4only.conf.j2
|
||||||
|
dest: /etc/systemd/system/nfs-mountd.service.d/v4only.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart NFS mount daemon
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Enable SELinux access for Atlas file sharing
|
||||||
|
tags: [atlas, sharing, services]
|
||||||
|
ansible.posix.seboolean:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: true
|
||||||
|
persistent: true
|
||||||
|
loop: "{{ atlas_selinux_booleans }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
when:
|
||||||
|
- atlas_manage_sharing | bool
|
||||||
|
- (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled'
|
||||||
|
|
||||||
|
- name: Render Atlas Samba configuration
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: smb.conf.j2
|
||||||
|
dest: /etc/samba/smb.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
validate: "testparm --suppress-prompt %s"
|
||||||
|
notify: Restart Samba service
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Require Vault-backed Atlas Samba accounts
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- atlas_samba_encryption in ['required', 'desired']
|
||||||
|
- atlas_samba_accounts | length > 0
|
||||||
|
- >-
|
||||||
|
atlas_samba_accounts | map(attribute='username') | list
|
||||||
|
| difference(atlas_samba_valid_users) | length == 0
|
||||||
|
- >-
|
||||||
|
atlas_samba_valid_users
|
||||||
|
| difference(atlas_samba_accounts | map(attribute='username') | list) | length == 0
|
||||||
|
- atlas_samba_accounts | selectattr('password', 'equalto', '') | list | length == 0
|
||||||
|
fail_msg: Define every authorized Samba account and its Vault-backed password.
|
||||||
|
no_log: true
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Ensure Atlas Samba private state directory exists
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ atlas_samba_password_marker_dir }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0700"
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Manage Vault-backed Atlas Samba credentials
|
||||||
|
tags: [atlas, sharing]
|
||||||
|
ansible.builtin.include_tasks: samba_account.yml
|
||||||
|
loop: "{{ atlas_samba_accounts }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: atlas_samba_account
|
||||||
|
label: "{{ atlas_samba_account.username }}"
|
||||||
|
no_log: true
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Enable Atlas file-sharing services
|
||||||
|
tags: [atlas, sharing, services]
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
loop:
|
||||||
|
- nfs-server.service
|
||||||
|
- smb.service
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
when: atlas_manage_sharing | bool
|
||||||
|
|
||||||
|
- name: Gather effective Atlas firewalld zone configuration
|
||||||
|
tags: [atlas, sharing, services, security]
|
||||||
|
ansible.posix.firewalld_info:
|
||||||
|
zones:
|
||||||
|
- "{{ atlas_firewalld_zone }}"
|
||||||
|
register: atlas_firewalld_info
|
||||||
|
when:
|
||||||
|
- atlas_manage_firewall | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Verify effective Atlas firewalld restrictions
|
||||||
|
tags: [atlas, sharing, services, security]
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- atlas_firewalld_zone in atlas_firewalld_info.firewalld_info.zones
|
||||||
|
- >-
|
||||||
|
ansible_facts.default_ipv4.interface
|
||||||
|
in atlas_firewalld_info.firewalld_info.zones[atlas_firewalld_zone].interfaces
|
||||||
|
- >-
|
||||||
|
atlas_firewalld_restricted_services
|
||||||
|
| intersect(atlas_firewalld_info.firewalld_info.zones[atlas_firewalld_zone].services)
|
||||||
|
| length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
The effective Atlas firewalld zone does not restrict the primary LAN
|
||||||
|
interface and unmanaged service exposure as declared.
|
||||||
|
when:
|
||||||
|
- atlas_manage_firewall | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Apply pending Atlas sharing handlers before verification
|
||||||
|
tags: [atlas, sharing, services, security]
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
|
when:
|
||||||
|
- atlas_manage_sharing | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Check active Atlas file-sharing services
|
||||||
|
tags: [atlas, sharing, services, security]
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- systemctl
|
||||||
|
- is-active
|
||||||
|
- --quiet
|
||||||
|
- "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- nfs-server.service
|
||||||
|
- smb.service
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
register: atlas_file_sharing_service_activity
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
when:
|
||||||
|
- atlas_manage_sharing | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Require active Atlas file-sharing services
|
||||||
|
tags: [atlas, sharing, services, security]
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- atlas_file_sharing_service_activity.results | map(attribute='rc') | list == [0, 0]
|
||||||
|
fail_msg: Atlas NFSv4 or SMB3 did not start after its managed configuration was applied.
|
||||||
|
when:
|
||||||
|
- atlas_manage_sharing | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Require Atlas file-sharing TCP listeners
|
||||||
|
tags: [atlas, sharing, services, security]
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: "{{ item }}"
|
||||||
|
state: started
|
||||||
|
timeout: 15
|
||||||
|
loop:
|
||||||
|
- 2049
|
||||||
|
- 445
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}/tcp"
|
||||||
|
when:
|
||||||
|
- atlas_manage_sharing | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|||||||
@@ -16,13 +16,20 @@
|
|||||||
properties:
|
properties:
|
||||||
compression: zstd
|
compression: zstd
|
||||||
mountpoint: "{{ atlas_archive_mountpoint }}"
|
mountpoint: "{{ atlas_archive_mountpoint }}"
|
||||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_music }}"
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
|
||||||
mountpoint: "{{ atlas_music_mountpoint }}"
|
mountpoint: "{{ atlas_app_data_mountpoint }}"
|
||||||
owner: "{{ atlas_admin_username }}"
|
owner: "{{ atlas_admin_username }}"
|
||||||
group: "{{ atlas_admin_group }}"
|
group: "{{ atlas_admin_group }}"
|
||||||
properties:
|
properties:
|
||||||
compression: lz4
|
compression: zstd
|
||||||
mountpoint: "{{ atlas_music_mountpoint }}"
|
mountpoint: "{{ atlas_app_data_mountpoint }}"
|
||||||
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||||
|
mountpoint: "{{ atlas_navidrome_mountpoint }}"
|
||||||
|
owner: "{{ atlas_admin_username }}"
|
||||||
|
group: "{{ atlas_admin_group }}"
|
||||||
|
properties:
|
||||||
|
compression: zstd
|
||||||
|
mountpoint: "{{ atlas_navidrome_mountpoint }}"
|
||||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_syncthing }}"
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||||
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
||||||
owner: "{{ atlas_admin_username }}"
|
owner: "{{ atlas_admin_username }}"
|
||||||
@@ -30,19 +37,40 @@
|
|||||||
properties:
|
properties:
|
||||||
compression: lz4
|
compression: lz4
|
||||||
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
||||||
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_media }}"
|
||||||
|
mountpoint: "{{ atlas_media_mountpoint }}"
|
||||||
|
owner: "{{ atlas_admin_username }}"
|
||||||
|
group: "{{ atlas_admin_group }}"
|
||||||
|
properties:
|
||||||
|
compression: lz4
|
||||||
|
mountpoint: "{{ atlas_media_mountpoint }}"
|
||||||
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_music }}"
|
||||||
|
mountpoint: "{{ atlas_music_mountpoint }}"
|
||||||
|
owner: "{{ atlas_admin_username }}"
|
||||||
|
group: "{{ atlas_admin_group }}"
|
||||||
|
properties:
|
||||||
|
compression: lz4
|
||||||
|
mountpoint: "{{ atlas_music_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:
|
||||||
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
|
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
|
||||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_icloud_photos }}"
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_photobook }}"
|
||||||
mountpoint: "{{ atlas_icloud_photos_mountpoint }}"
|
mountpoint: "{{ atlas_photobook_mountpoint }}"
|
||||||
owner: "{{ atlas_immich_username }}"
|
owner: "{{ atlas_immich_username }}"
|
||||||
group: "{{ atlas_immich_group }}"
|
group: "{{ atlas_immich_group }}"
|
||||||
properties:
|
properties:
|
||||||
compression: lz4
|
compression: lz4
|
||||||
mountpoint: "{{ atlas_icloud_photos_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 }}"
|
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||||
mountpoint: "{{ atlas_service_backups_mountpoint }}"
|
mountpoint: "{{ atlas_service_backups_mountpoint }}"
|
||||||
owner: "{{ atlas_admin_username }}"
|
owner: "{{ atlas_admin_username }}"
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create Atlas Syncthing configuration directory
|
|
||||||
tags: [atlas, syncthing]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ atlas_syncthing_config_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ atlas_admin_username }}"
|
|
||||||
group: "{{ atlas_admin_group }}"
|
|
||||||
mode: "0700"
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Create Atlas Syncthing default data directory
|
|
||||||
tags: [atlas, syncthing]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ atlas_syncthing_default_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ atlas_admin_username }}"
|
|
||||||
group: "{{ atlas_admin_group }}"
|
|
||||||
mode: "0770"
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Render Atlas Syncthing systemd service
|
|
||||||
tags: [atlas, syncthing]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: atlas-syncthing.service.j2
|
|
||||||
dest: /etc/systemd/system/atlas-syncthing.service
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
notify: Restart Atlas Syncthing service
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
|
|
||||||
- name: Enable Atlas Syncthing service
|
|
||||||
tags: [atlas, syncthing]
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: atlas-syncthing
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
daemon_reload: true
|
|
||||||
when: atlas_manage_storage | bool
|
|
||||||
@@ -3,4 +3,22 @@ PermitRootLogin no
|
|||||||
PubkeyAuthentication yes
|
PubkeyAuthentication yes
|
||||||
PasswordAuthentication no
|
PasswordAuthentication no
|
||||||
KbdInteractiveAuthentication no
|
KbdInteractiveAuthentication no
|
||||||
|
AuthenticationMethods publickey
|
||||||
|
AuthorizedKeysFile {{
|
||||||
|
atlas_admin_ssh_keys
|
||||||
|
| map(attribute='name')
|
||||||
|
| map('regex_replace', '^', '%h/.ssh/authorized_keys.d/')
|
||||||
|
| join(' ')
|
||||||
|
}}
|
||||||
|
X11Forwarding no
|
||||||
|
AllowTcpForwarding {{ atlas_ssh_allow_tcp_forwarding }}
|
||||||
|
AllowAgentForwarding no
|
||||||
|
GatewayPorts no
|
||||||
|
PermitTunnel no
|
||||||
|
PermitUserEnvironment no
|
||||||
|
MaxAuthTries 3
|
||||||
|
LoginGraceTime 30
|
||||||
|
ClientAliveInterval 300
|
||||||
|
ClientAliveCountMax 2
|
||||||
|
LogLevel VERBOSE
|
||||||
AllowUsers {{ atlas_admin_username }}
|
AllowUsers {{ atlas_admin_username }}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Network=atlas-media.network
|
|||||||
NetworkAlias=atlas-immich-server
|
NetworkAlias=atlas-immich-server
|
||||||
EnvironmentFile=/etc/immich/immich.env
|
EnvironmentFile=/etc/immich/immich.env
|
||||||
Volume={{ atlas_immich_upload_dir }}:/data:Z
|
Volume={{ atlas_immich_upload_dir }}:/data:Z
|
||||||
Volume={{ atlas_icloud_photos_mountpoint }}:/external/icloud_photos:ro,Z
|
Volume={{ atlas_photobook_mountpoint }}:/external/photobook:ro,Z
|
||||||
Volume=/etc/localtime:/etc/localtime:ro
|
Volume=/etc/localtime:/etc/localtime:ro
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
DropCapability=NET_RAW
|
DropCapability=NET_RAW
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Atlas Syncthing service
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User={{ atlas_admin_username }}
|
|
||||||
Group={{ atlas_admin_group }}
|
|
||||||
WorkingDirectory={{ atlas_syncthing_mountpoint }}
|
|
||||||
Environment=HOME={{ atlas_syncthing_mountpoint }}
|
|
||||||
ExecStart=/usr/local/bin/syncthing serve --no-browser --no-restart --no-upgrade --home={{ atlas_syncthing_config_dir }}
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -4,6 +4,10 @@
|
|||||||
security = user
|
security = user
|
||||||
map to guest = Never
|
map to guest = Never
|
||||||
server min protocol = SMB3
|
server min protocol = SMB3
|
||||||
|
server signing = mandatory
|
||||||
|
smb encrypt = {{ atlas_samba_encryption }}
|
||||||
|
disable netbios = yes
|
||||||
|
smb ports = 445
|
||||||
hosts allow = {{ atlas_lan_subnet }}
|
hosts allow = {{ atlas_lan_subnet }}
|
||||||
include = registry
|
include = registry
|
||||||
|
|
||||||
|
|||||||
28
ansible/roles/profile_backend_phase1/defaults/main.yml
Normal file
28
ansible/roles/profile_backend_phase1/defaults/main.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
backend_phase1_enabled: false
|
||||||
|
backend_phase1_start_services: false
|
||||||
|
backend_phase1_username: "{{ atlas_admin_username }}"
|
||||||
|
backend_phase1_user_group: "{{ atlas_admin_group }}"
|
||||||
|
backend_phase1_user_home: "{{ atlas_admin_home }}"
|
||||||
|
backend_phase1_quadlet_dir: "{{ backend_phase1_user_home }}/.config/containers/systemd"
|
||||||
|
backend_phase1_wireguard_interface: wg0
|
||||||
|
backend_phase1_wireguard_address: CHANGEME_ATLAS_WIREGUARD_ADDRESS
|
||||||
|
backend_phase1_wireguard_firewalld_zone: wireguard
|
||||||
|
backend_phase1_music_dir: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_music }}"
|
||||||
|
backend_phase1_app_data_root: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_app_data }}"
|
||||||
|
backend_phase1_navidrome_data_dir: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||||
|
backend_phase1_syncthing_root: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||||
|
backend_phase1_music_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_music }}"
|
||||||
|
backend_phase1_app_data_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
|
||||||
|
backend_phase1_navidrome_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_navidrome }}"
|
||||||
|
backend_phase1_syncthing_dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_syncthing }}"
|
||||||
|
backend_phase1_navidrome_image: docker.io/deluan/navidrome:0.63.2
|
||||||
|
backend_phase1_syncthing_image: docker.io/syncthing/syncthing:2.1.3
|
||||||
|
backend_phase1_navidrome_port: 4533
|
||||||
|
backend_phase1_syncthing_gui_port: 8384
|
||||||
|
backend_phase1_syncthing_transfer_port: 22000
|
||||||
|
backend_phase1_syncthing_discovery_port: 21027
|
||||||
|
backend_phase1_timezone: Europe/Rome
|
||||||
|
backend_phase1_services:
|
||||||
|
- atlas-navidrome.service
|
||||||
|
- atlas-syncthing.service
|
||||||
32
ansible/roles/profile_backend_phase1/handlers/main.yml
Normal file
32
ansible/roles/profile_backend_phase1/handlers/main.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
- name: Restart Atlas Navidrome Quadlet
|
||||||
|
tags: [backend_phase1, navidrome, podman, services]
|
||||||
|
become_user: "{{ backend_phase1_username }}"
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: atlas-navidrome.service
|
||||||
|
scope: user
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: true
|
||||||
|
environment:
|
||||||
|
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||||
|
when:
|
||||||
|
- backend_phase1_enabled | bool
|
||||||
|
- backend_phase1_start_services | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Restart Atlas Syncthing Quadlet
|
||||||
|
tags: [backend_phase1, syncthing, podman, services]
|
||||||
|
become_user: "{{ backend_phase1_username }}"
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: atlas-syncthing.service
|
||||||
|
scope: user
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: true
|
||||||
|
environment:
|
||||||
|
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||||
|
when:
|
||||||
|
- backend_phase1_enabled | bool
|
||||||
|
- backend_phase1_start_services | bool
|
||||||
|
- not ansible_check_mode
|
||||||
163
ansible/roles/profile_backend_phase1/tasks/main.yml
Normal file
163
ansible/roles/profile_backend_phase1/tasks/main.yml
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
- name: Configure Atlas phase-one backend services
|
||||||
|
tags: [backend_phase1, podman]
|
||||||
|
when: backend_phase1_enabled | bool
|
||||||
|
block:
|
||||||
|
- name: Validate phase-one backend inputs
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- not (atlas_manage_media_stack | bool)
|
||||||
|
- backend_phase1_wireguard_interface in ansible_facts.interfaces
|
||||||
|
- backend_phase1_wireguard_address != 'CHANGEME_ATLAS_WIREGUARD_ADDRESS'
|
||||||
|
- backend_phase1_music_dir.startswith('/')
|
||||||
|
- backend_phase1_app_data_root.startswith('/')
|
||||||
|
- backend_phase1_navidrome_data_dir.startswith(backend_phase1_app_data_root + '/')
|
||||||
|
- backend_phase1_syncthing_root.startswith(backend_phase1_app_data_root + '/')
|
||||||
|
fail_msg: >-
|
||||||
|
Disable the rootful media-stack gate and provide the active
|
||||||
|
WireGuard interface/address and absolute ZFS-backed paths before
|
||||||
|
enabling phase one. This role does not manage Prometheus or migrate
|
||||||
|
Navidrome application data.
|
||||||
|
|
||||||
|
- name: Read the rootless service account
|
||||||
|
ansible.builtin.getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ backend_phase1_username }}"
|
||||||
|
|
||||||
|
- name: Record rootless service account IDs
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
backend_phase1_uid: "{{ ansible_facts['getent_passwd'][backend_phase1_username][1] }}"
|
||||||
|
backend_phase1_gid: "{{ ansible_facts['getent_passwd'][backend_phase1_username][2] }}"
|
||||||
|
|
||||||
|
- name: Read system service state before starting rootless Syncthing
|
||||||
|
ansible.builtin.service_facts:
|
||||||
|
|
||||||
|
- name: Refuse to overlap a system-level Atlas Syncthing service
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- >-
|
||||||
|
ansible_facts.services['atlas-syncthing.service'] is not defined
|
||||||
|
or ansible_facts.services['atlas-syncthing.service'].state != 'running'
|
||||||
|
fail_msg: >-
|
||||||
|
Remove or stop the system-level atlas-syncthing.service before
|
||||||
|
enabling the rootless Syncthing Quadlet.
|
||||||
|
|
||||||
|
- name: Inspect required ZFS datasets
|
||||||
|
community.general.zfs_facts:
|
||||||
|
name: "{{ item.dataset }}"
|
||||||
|
properties: name,mounted,mountpoint
|
||||||
|
loop:
|
||||||
|
- dataset: "{{ backend_phase1_music_dataset }}"
|
||||||
|
mountpoint: "{{ backend_phase1_music_dir }}"
|
||||||
|
- dataset: "{{ backend_phase1_app_data_dataset }}"
|
||||||
|
mountpoint: "{{ backend_phase1_app_data_root }}"
|
||||||
|
- dataset: "{{ backend_phase1_navidrome_dataset }}"
|
||||||
|
mountpoint: "{{ backend_phase1_navidrome_data_dir }}"
|
||||||
|
- dataset: "{{ backend_phase1_syncthing_dataset }}"
|
||||||
|
mountpoint: "{{ backend_phase1_syncthing_root }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.dataset }}"
|
||||||
|
register: backend_phase1_zfs_facts
|
||||||
|
|
||||||
|
- name: Require mounted datasets at the declared paths
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- item.ansible_facts.ansible_zfs_datasets | length == 1
|
||||||
|
- item.ansible_facts.ansible_zfs_datasets[0].mounted == 'yes'
|
||||||
|
- item.ansible_facts.ansible_zfs_datasets[0].mountpoint == item.item.mountpoint
|
||||||
|
fail_msg: >-
|
||||||
|
{{ item.item.dataset }} must already be mounted at
|
||||||
|
{{ item.item.mountpoint }}. The phase-one role never creates or
|
||||||
|
imports the pool.
|
||||||
|
loop: "{{ backend_phase1_zfs_facts.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.dataset }}"
|
||||||
|
|
||||||
|
- name: Enable lingering for the rootless service account
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- loginctl
|
||||||
|
- enable-linger
|
||||||
|
- "{{ backend_phase1_username }}"
|
||||||
|
creates: "/var/lib/systemd/linger/{{ backend_phase1_username }}"
|
||||||
|
|
||||||
|
- name: Start the rootless user systemd manager
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "user@{{ backend_phase1_uid }}.service"
|
||||||
|
state: started
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Create rootless Quadlet and application directories
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ backend_phase1_username }}"
|
||||||
|
group: "{{ backend_phase1_user_group }}"
|
||||||
|
mode: "{{ item.mode }}"
|
||||||
|
loop:
|
||||||
|
- path: "{{ backend_phase1_quadlet_dir }}"
|
||||||
|
mode: "0700"
|
||||||
|
- path: "{{ backend_phase1_navidrome_data_dir }}"
|
||||||
|
mode: "0750"
|
||||||
|
- path: "{{ backend_phase1_syncthing_root }}"
|
||||||
|
mode: "0750"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
|
||||||
|
- name: Render the rootless Navidrome Quadlet
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: atlas-navidrome.container.j2
|
||||||
|
dest: "{{ backend_phase1_quadlet_dir }}/atlas-navidrome.container"
|
||||||
|
owner: "{{ backend_phase1_username }}"
|
||||||
|
group: "{{ backend_phase1_user_group }}"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart Atlas Navidrome Quadlet
|
||||||
|
|
||||||
|
- name: Render the rootless Syncthing Quadlet
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: atlas-syncthing.container.j2
|
||||||
|
dest: "{{ backend_phase1_quadlet_dir }}/atlas-syncthing.container"
|
||||||
|
owner: "{{ backend_phase1_username }}"
|
||||||
|
group: "{{ backend_phase1_user_group }}"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart Atlas Syncthing Quadlet
|
||||||
|
|
||||||
|
- name: Reload the rootless user systemd manager
|
||||||
|
become_user: "{{ backend_phase1_username }}"
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
scope: user
|
||||||
|
daemon_reload: true
|
||||||
|
environment:
|
||||||
|
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Permit phase-one services only through the WireGuard zone
|
||||||
|
ansible.posix.firewalld:
|
||||||
|
port: "{{ item }}"
|
||||||
|
zone: "{{ backend_phase1_wireguard_firewalld_zone }}"
|
||||||
|
state: enabled
|
||||||
|
permanent: true
|
||||||
|
immediate: true
|
||||||
|
loop:
|
||||||
|
- "{{ backend_phase1_navidrome_port }}/tcp"
|
||||||
|
- "{{ backend_phase1_syncthing_gui_port }}/tcp"
|
||||||
|
- "{{ backend_phase1_syncthing_transfer_port }}/tcp"
|
||||||
|
- "{{ backend_phase1_syncthing_transfer_port }}/udp"
|
||||||
|
- "{{ backend_phase1_syncthing_discovery_port }}/udp"
|
||||||
|
|
||||||
|
- name: Start rootless phase-one Quadlets
|
||||||
|
become_user: "{{ backend_phase1_username }}"
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
scope: user
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
daemon_reload: true
|
||||||
|
environment:
|
||||||
|
XDG_RUNTIME_DIR: "/run/user/{{ backend_phase1_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ backend_phase1_uid }}/bus"
|
||||||
|
loop: "{{ backend_phase1_services }}"
|
||||||
|
when:
|
||||||
|
- backend_phase1_start_services | bool
|
||||||
|
- not ansible_check_mode
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Managed by Ansible. Do not edit manually.
|
||||||
|
[Unit]
|
||||||
|
Description=Atlas rootless Navidrome backend
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=atlas-navidrome
|
||||||
|
Image={{ backend_phase1_navidrome_image }}
|
||||||
|
UserNS=keep-id
|
||||||
|
User={{ backend_phase1_uid }}
|
||||||
|
Group={{ backend_phase1_gid }}
|
||||||
|
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_navidrome_port }}:4533
|
||||||
|
Environment=ND_LOGLEVEL=info
|
||||||
|
Environment=ND_SCANSCHEDULE=1h
|
||||||
|
Environment=ND_SESSIONTIMEOUT=24h
|
||||||
|
Environment=ND_ENABLETRANSCODING=true
|
||||||
|
Environment=TZ={{ backend_phase1_timezone }}
|
||||||
|
Volume={{ backend_phase1_navidrome_data_dir }}:/data:Z
|
||||||
|
Volume={{ backend_phase1_music_dir }}:/music:ro,z
|
||||||
|
NoNewPrivileges=true
|
||||||
|
DropCapability=all
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
TimeoutStartSec=900
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Managed by Ansible. Do not edit manually.
|
||||||
|
[Unit]
|
||||||
|
Description=Atlas rootless Syncthing backend
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=atlas-syncthing
|
||||||
|
Image={{ backend_phase1_syncthing_image }}
|
||||||
|
HostName=atlas-syncthing
|
||||||
|
UserNS=keep-id
|
||||||
|
User={{ backend_phase1_uid }}
|
||||||
|
Group={{ backend_phase1_gid }}
|
||||||
|
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_gui_port }}:{{ backend_phase1_syncthing_gui_port }}
|
||||||
|
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_transfer_port }}:{{ backend_phase1_syncthing_transfer_port }}
|
||||||
|
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_transfer_port }}:{{ backend_phase1_syncthing_transfer_port }}/udp
|
||||||
|
PublishPort={{ backend_phase1_wireguard_address }}:{{ backend_phase1_syncthing_discovery_port }}:{{ backend_phase1_syncthing_discovery_port }}/udp
|
||||||
|
Environment=HOME=/var/syncthing
|
||||||
|
Environment=STHOMEDIR=/var/syncthing/config
|
||||||
|
Environment=STGUIADDRESS=0.0.0.0:{{ backend_phase1_syncthing_gui_port }}
|
||||||
|
Environment=TZ={{ backend_phase1_timezone }}
|
||||||
|
Volume={{ backend_phase1_syncthing_root }}:/var/syncthing:Z
|
||||||
|
NoNewPrivileges=true
|
||||||
|
DropCapability=all
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
TimeoutStartSec=900
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -9,29 +9,3 @@
|
|||||||
tags: [services]
|
tags: [services]
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
daemon_reload: true
|
daemon_reload: true
|
||||||
|
|
||||||
- name: Restart rclone music mount
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "{{ server_rclone_music_service }}"
|
|
||||||
state: restarted
|
|
||||||
daemon_reload: true
|
|
||||||
when:
|
|
||||||
- server_atlas_music_enabled | bool
|
|
||||||
- not ansible_check_mode
|
|
||||||
|
|
||||||
- name: Restart rootless Navidrome
|
|
||||||
tags: [services, podman, rclone, navidrome]
|
|
||||||
become_user: "{{ server_username }}"
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: navidrome.service
|
|
||||||
scope: user
|
|
||||||
state: restarted
|
|
||||||
daemon_reload: true
|
|
||||||
environment:
|
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}"
|
|
||||||
DBUS_SESSION_BUS_ADDRESS: >-
|
|
||||||
unix:path=/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}/bus
|
|
||||||
when:
|
|
||||||
- server_atlas_music_enabled | bool
|
|
||||||
- not ansible_check_mode
|
|
||||||
|
|||||||
@@ -8,17 +8,6 @@
|
|||||||
fail_msg: >-
|
fail_msg: >-
|
||||||
server_firewall_backend must be firewalld for the Rocky server profile.
|
server_firewall_backend must be firewalld for the Rocky server profile.
|
||||||
|
|
||||||
- name: Require server database secret variables
|
|
||||||
tags: [dotfiles, dotfiles:server, services, navidrome]
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- server_navidrome_db_password | length > 0
|
|
||||||
- (vault_postgres_root_password | default('')) | length > 0
|
|
||||||
fail_msg: >-
|
|
||||||
Define vault_navidrome_db_password and vault_postgres_root_password in Vault
|
|
||||||
before rendering the Navidrome database configuration.
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Configure DuckDNS updater
|
- name: Configure DuckDNS updater
|
||||||
tags: [dotfiles, dotfiles:server, duckdns]
|
tags: [dotfiles, dotfiles:server, duckdns]
|
||||||
ansible.builtin.import_tasks: duckdns.yml
|
ansible.builtin.import_tasks: duckdns.yml
|
||||||
@@ -72,11 +61,41 @@
|
|||||||
tags: [services, podman]
|
tags: [services, podman]
|
||||||
ansible.builtin.include_tasks: podman-compose.yml
|
ansible.builtin.include_tasks: podman-compose.yml
|
||||||
|
|
||||||
- name: Configure Atlas music mount
|
- name: Ensure server SSH authorized key fragments directory exists
|
||||||
ansible.builtin.import_tasks: rclone-music.yml
|
tags: [services, ssh]
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ server_ssh_authorized_key_directory }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ server_username }}"
|
||||||
|
group: "{{ server_user_group }}"
|
||||||
|
mode: "0700"
|
||||||
|
when: server_ssh_authorized_keys | length > 0
|
||||||
|
|
||||||
- name: Configure rootless Navidrome
|
- name: Manage server SSH authorized key fragments
|
||||||
ansible.builtin.import_tasks: navidrome.yml
|
tags: [services, ssh]
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.key }}\n"
|
||||||
|
dest: "{{ server_ssh_authorized_key_directory }}/{{ item.name }}"
|
||||||
|
owner: "{{ server_username }}"
|
||||||
|
group: "{{ server_user_group }}"
|
||||||
|
mode: "0600"
|
||||||
|
loop: "{{ server_ssh_authorized_keys }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
when: server_ssh_authorized_keys | length > 0
|
||||||
|
|
||||||
|
- name: Configure server SSH authorized key fragments
|
||||||
|
tags: [services, ssh]
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/ssh/sshd_config
|
||||||
|
regexp: '^\s*AuthorizedKeysFile\s+'
|
||||||
|
line: >-
|
||||||
|
AuthorizedKeysFile {{ server_ssh_authorized_keys | map(attribute='name')
|
||||||
|
| map('regex_replace', '^', '%h/.ssh/authorized_keys.d/') | join(' ') }}
|
||||||
|
state: present
|
||||||
|
validate: "sshd -t -f %s"
|
||||||
|
notify: Reload SSH service
|
||||||
|
when: server_ssh_authorized_keys | length > 0
|
||||||
|
|
||||||
- name: Disable SSH root login on server
|
- name: Disable SSH root login on server
|
||||||
tags: [services]
|
tags: [services]
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Read server account details for rootless Navidrome
|
|
||||||
tags: [services, podman, rclone, navidrome]
|
|
||||||
ansible.builtin.getent:
|
|
||||||
database: passwd
|
|
||||||
key: "{{ server_username }}"
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Check for a running legacy rootful Navidrome container
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- podman
|
|
||||||
- container
|
|
||||||
- inspect
|
|
||||||
- --format
|
|
||||||
- "{{ '{{.State.Running}}' }}"
|
|
||||||
- navidrome
|
|
||||||
register: server_legacy_navidrome_container
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
check_mode: false
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Refuse concurrent legacy and rootless Navidrome
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- >-
|
|
||||||
server_legacy_navidrome_container.rc != 0
|
|
||||||
or server_legacy_navidrome_container.stdout | trim != 'true'
|
|
||||||
fail_msg: >-
|
|
||||||
Stop the legacy rootful Navidrome container before enabling the rootless Quadlet.
|
|
||||||
The playbook does not remove the old container or its data automatically.
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Enable lingering for rootless Navidrome
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- loginctl
|
|
||||||
- enable-linger
|
|
||||||
- "{{ server_username }}"
|
|
||||||
creates: "/var/lib/systemd/linger/{{ server_username }}"
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Start server user systemd manager
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "user@{{ ansible_facts['getent_passwd'][server_username][1] }}.service"
|
|
||||||
state: started
|
|
||||||
when:
|
|
||||||
- server_atlas_music_enabled | bool
|
|
||||||
- not ansible_check_mode
|
|
||||||
|
|
||||||
- name: Create rootless Navidrome Quadlet directory
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ server_navidrome_quadlet_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0700"
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Create private rootless Navidrome configuration directory
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ server_navidrome_env_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0700"
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Render Vault-backed rootless Navidrome environment
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: navidrome.env.j2
|
|
||||||
dest: "{{ server_navidrome_env_file }}"
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0600"
|
|
||||||
no_log: true
|
|
||||||
diff: false
|
|
||||||
notify: Restart rootless Navidrome
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Render rootless Navidrome Quadlet
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: navidrome.container.j2
|
|
||||||
dest: "{{ server_navidrome_quadlet_dir }}/navidrome.container"
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0644"
|
|
||||||
notify: Restart rootless Navidrome
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Start rootless Navidrome Quadlet
|
|
||||||
tags: [services, podman, navidrome]
|
|
||||||
become_user: "{{ server_username }}"
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: navidrome.service
|
|
||||||
scope: user
|
|
||||||
state: started
|
|
||||||
daemon_reload: true
|
|
||||||
environment:
|
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}"
|
|
||||||
DBUS_SESSION_BUS_ADDRESS: >-
|
|
||||||
unix:path=/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}/bus
|
|
||||||
when:
|
|
||||||
- server_atlas_music_enabled | bool
|
|
||||||
- not ansible_check_mode
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Require completed Atlas SFTP mount configuration
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- server_atlas_sftp_host != 'CHANGEME_ATLAS_WIREGUARD_IP'
|
|
||||||
- server_atlas_sftp_known_host != 'CHANGEME_ATLAS_SSH_HOST_KEY'
|
|
||||||
- server_atlas_sftp_known_host | length > 0
|
|
||||||
- server_atlas_sftp_private_key | length > 0
|
|
||||||
- server_atlas_sftp_remote_path == '/pool/media/music'
|
|
||||||
fail_msg: >-
|
|
||||||
Define the Atlas WireGuard address, pinned SSH host key and Vault-backed
|
|
||||||
SFTP private key before enabling the music mount.
|
|
||||||
no_log: true
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Create private rclone configuration directory
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ server_rclone_config_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: root
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Install Vault-backed Atlas SFTP private key
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: "{{ server_atlas_sftp_private_key | trim }}\n"
|
|
||||||
dest: "{{ server_atlas_sftp_private_key_file }}"
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0600"
|
|
||||||
no_log: true
|
|
||||||
diff: false
|
|
||||||
notify:
|
|
||||||
- Restart rclone music mount
|
|
||||||
- Restart rootless Navidrome
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Install pinned Atlas SSH host key
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: "{{ server_atlas_sftp_known_host | trim }}\n"
|
|
||||||
dest: "{{ server_atlas_sftp_known_hosts_file }}"
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0640"
|
|
||||||
notify:
|
|
||||||
- Restart rclone music mount
|
|
||||||
- Restart rootless Navidrome
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Render Atlas SFTP rclone configuration
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: rclone.conf.j2
|
|
||||||
dest: "{{ server_rclone_config_file }}"
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0600"
|
|
||||||
notify:
|
|
||||||
- Restart rclone music mount
|
|
||||||
- Restart rootless Navidrome
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Create rclone music directories
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ server_username }}"
|
|
||||||
group: "{{ server_user_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
loop:
|
|
||||||
- "{{ server_rclone_music_mountpoint }}"
|
|
||||||
- "{{ server_rclone_music_cache_dir }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Render rclone music system service
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: rclone-music.service.j2
|
|
||||||
dest: "/etc/systemd/system/{{ server_rclone_music_service }}"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
notify:
|
|
||||||
- Restart rclone music mount
|
|
||||||
- Restart rootless Navidrome
|
|
||||||
when: server_atlas_music_enabled | bool
|
|
||||||
|
|
||||||
- name: Enable and start rclone music mount
|
|
||||||
tags: [services, rclone, navidrome]
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "{{ server_rclone_music_service }}"
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
daemon_reload: true
|
|
||||||
when:
|
|
||||||
- server_atlas_music_enabled | bool
|
|
||||||
- not ansible_check_mode
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Managed by Ansible. Do not edit manually.
|
|
||||||
[Unit]
|
|
||||||
Description=Rootless Navidrome music server
|
|
||||||
|
|
||||||
[Container]
|
|
||||||
ContainerName=navidrome
|
|
||||||
Image={{ server_navidrome_image }}
|
|
||||||
Network=host
|
|
||||||
EnvironmentFile={{ server_navidrome_env_file }}
|
|
||||||
Volume={{ server_navidrome_data_dir }}:/data
|
|
||||||
Volume={{ server_rclone_music_mountpoint }}:/music:ro
|
|
||||||
SecurityLabelDisable=true
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStartPre=/usr/bin/mountpoint -q {{ server_rclone_music_mountpoint }}
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10s
|
|
||||||
TimeoutStartSec=900
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Managed by Ansible. Contains a Vault secret; do not edit or copy.
|
|
||||||
ND_DATABASE_URL={{ ('postgres://navidrome:' ~ server_navidrome_db_password ~ '@127.0.0.1:5432/navidrome_db?sslmode=disable') | to_json }}
|
|
||||||
ND_PORT={{ server_navidrome_port }}
|
|
||||||
ND_SCANSCHEDULE="1h"
|
|
||||||
ND_SESSIONTIMEOUT="24h"
|
|
||||||
ND_ENABLETRANSCODING="true"
|
|
||||||
ND_LOGLEVEL="info"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Read-only Atlas music mount via rclone SFTP
|
|
||||||
Wants=network-online.target
|
|
||||||
After=network-online.target
|
|
||||||
{% if server_atlas_wireguard_unit | length > 0 %}
|
|
||||||
Wants={{ server_atlas_wireguard_unit }}
|
|
||||||
After={{ server_atlas_wireguard_unit }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=notify
|
|
||||||
User={{ server_username }}
|
|
||||||
Group={{ server_user_group }}
|
|
||||||
ExecStart=/usr/bin/rclone mount \
|
|
||||||
{{ server_atlas_sftp_remote_name }}:{{ server_atlas_sftp_remote_path }} \
|
|
||||||
{{ server_rclone_music_mountpoint }} \
|
|
||||||
--config {{ server_rclone_config_file }} \
|
|
||||||
--cache-dir {{ server_rclone_music_cache_dir }} \
|
|
||||||
--read-only \
|
|
||||||
--vfs-cache-mode full \
|
|
||||||
--vfs-cache-max-size 15G \
|
|
||||||
--vfs-read-chunk-size 5M \
|
|
||||||
--dir-cache-time 24h
|
|
||||||
ExecStop=-/usr/bin/fusermount3 -uz {{ server_rclone_music_mountpoint }}
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=10s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Managed by Ansible. Authentication uses the separately deployed Vault-backed key.
|
|
||||||
[{{ server_atlas_sftp_remote_name }}]
|
|
||||||
type = sftp
|
|
||||||
host = {{ server_atlas_sftp_host }}
|
|
||||||
user = {{ server_atlas_sftp_username }}
|
|
||||||
port = {{ server_atlas_sftp_port }}
|
|
||||||
key_file = {{ server_atlas_sftp_private_key_file }}
|
|
||||||
known_hosts_file = {{ server_atlas_sftp_known_hosts_file }}
|
|
||||||
shell_type = unix
|
|
||||||
18
ansible/roles/wireguard_overlay/defaults/main.yml
Normal file
18
ansible/roles/wireguard_overlay/defaults/main.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
wireguard_overlay_enabled: false
|
||||||
|
wireguard_interface: wg0
|
||||||
|
wireguard_config_dir: /etc/wireguard
|
||||||
|
wireguard_private_key_path: "{{ wireguard_config_dir }}/{{ wireguard_interface }}.key"
|
||||||
|
wireguard_config_path: "{{ wireguard_config_dir }}/{{ wireguard_interface }}.conf"
|
||||||
|
wireguard_address: CHANGEME_WIREGUARD_ADDRESS
|
||||||
|
wireguard_listen_port: 0
|
||||||
|
wireguard_mtu: 1420
|
||||||
|
wireguard_firewalld_zone: wireguard
|
||||||
|
wireguard_public_firewalld_zone: public
|
||||||
|
wireguard_enable_ipv4_forwarding: false
|
||||||
|
wireguard_reload_rootful_podman_networks: false
|
||||||
|
wireguard_handshake_retries: 12
|
||||||
|
wireguard_handshake_delay: 5
|
||||||
|
wireguard_peers: []
|
||||||
|
wireguard_packages:
|
||||||
|
- wireguard-tools
|
||||||
10
ansible/roles/wireguard_overlay/handlers/main.yml
Normal file
10
ansible/roles/wireguard_overlay/handlers/main.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
- name: Restart WireGuard interface
|
||||||
|
tags: [wireguard, services]
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "wg-quick@{{ wireguard_interface }}.service"
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: true
|
||||||
|
when:
|
||||||
|
- wireguard_overlay_enabled | bool
|
||||||
|
- not ansible_check_mode
|
||||||
192
ansible/roles/wireguard_overlay/tasks/main.yml
Normal file
192
ansible/roles/wireguard_overlay/tasks/main.yml
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
---
|
||||||
|
- name: Configure WireGuard overlay
|
||||||
|
tags: [wireguard, services]
|
||||||
|
when: wireguard_overlay_enabled | bool
|
||||||
|
block:
|
||||||
|
- name: Validate WireGuard host configuration
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- wireguard_address != 'CHANGEME_WIREGUARD_ADDRESS'
|
||||||
|
- wireguard_address is match('^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$')
|
||||||
|
- wireguard_peers | length > 0
|
||||||
|
- wireguard_peers | map(attribute='host') | difference(ansible_play_hosts_all) | length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
Configure this host's WireGuard address and peers, and run the first
|
||||||
|
key bootstrap against every peer in the same play.
|
||||||
|
|
||||||
|
- name: Install WireGuard userspace tools
|
||||||
|
ansible.builtin.dnf:
|
||||||
|
name: "{{ wireguard_packages }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create private WireGuard configuration directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ wireguard_config_dir }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0700"
|
||||||
|
|
||||||
|
- name: Check for an existing WireGuard private key
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ wireguard_private_key_path }}"
|
||||||
|
register: wireguard_private_key_stat
|
||||||
|
|
||||||
|
- name: Generate a missing WireGuard private key
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- wg
|
||||||
|
- genkey
|
||||||
|
register: wireguard_generated_private_key
|
||||||
|
changed_when: true
|
||||||
|
no_log: true
|
||||||
|
when:
|
||||||
|
- not wireguard_private_key_stat.stat.exists
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Persist the generated WireGuard private key
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ wireguard_generated_private_key.stdout }}\n"
|
||||||
|
dest: "{{ wireguard_private_key_path }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0600"
|
||||||
|
no_log: true
|
||||||
|
when:
|
||||||
|
- not wireguard_private_key_stat.stat.exists
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Require a private key during check mode
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- wireguard_private_key_stat.stat.exists
|
||||||
|
fail_msg: >-
|
||||||
|
The initial WireGuard key generation cannot be simulated safely in
|
||||||
|
check mode. Run the gated WireGuard play once without --check.
|
||||||
|
when: ansible_check_mode
|
||||||
|
|
||||||
|
- name: Read the persisted WireGuard private key
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: "{{ wireguard_private_key_path }}"
|
||||||
|
register: wireguard_private_key_material
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Derive this host's WireGuard public key
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- wg
|
||||||
|
- pubkey
|
||||||
|
stdin: "{{ wireguard_private_key_material.content | b64decode | trim }}"
|
||||||
|
register: wireguard_derived_public_key
|
||||||
|
changed_when: false
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Publish this host's WireGuard public key
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
wireguard_public_key: "{{ wireguard_derived_public_key.stdout }}"
|
||||||
|
|
||||||
|
- name: Require every peer's generated public key
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- hostvars[item.host].wireguard_public_key is defined
|
||||||
|
- hostvars[item.host].wireguard_public_key | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
The public key for {{ item.host }} is unavailable. The first
|
||||||
|
WireGuard run must include every overlay host.
|
||||||
|
loop: "{{ wireguard_peers }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
- name: Render the private WireGuard interface configuration
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: wg.conf.j2
|
||||||
|
dest: "{{ wireguard_config_path }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0600"
|
||||||
|
diff: false
|
||||||
|
no_log: true
|
||||||
|
notify: Restart WireGuard interface
|
||||||
|
|
||||||
|
- name: Enable IPv4 forwarding for the ingress host
|
||||||
|
ansible.posix.sysctl:
|
||||||
|
name: net.ipv4.ip_forward
|
||||||
|
value: "1"
|
||||||
|
state: present
|
||||||
|
sysctl_set: true
|
||||||
|
reload: true
|
||||||
|
when: wireguard_enable_ipv4_forwarding | bool
|
||||||
|
|
||||||
|
- name: Create the WireGuard firewalld zone
|
||||||
|
ansible.posix.firewalld:
|
||||||
|
zone: "{{ wireguard_firewalld_zone }}"
|
||||||
|
state: present
|
||||||
|
permanent: true
|
||||||
|
register: wireguard_firewalld_zone_result
|
||||||
|
|
||||||
|
- name: Reload firewalld after creating the WireGuard zone
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: firewalld.service
|
||||||
|
state: reloaded
|
||||||
|
when:
|
||||||
|
- wireguard_firewalld_zone_result is changed
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Restore rootful Podman networking after firewalld reload
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- podman
|
||||||
|
- network
|
||||||
|
- reload
|
||||||
|
- --all
|
||||||
|
register: wireguard_podman_network_reload
|
||||||
|
changed_when: wireguard_podman_network_reload.stdout_lines | length > 0
|
||||||
|
when:
|
||||||
|
- wireguard_firewalld_zone_result is changed
|
||||||
|
- wireguard_reload_rootful_podman_networks | bool
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Assign the WireGuard interface to its firewalld zone
|
||||||
|
ansible.posix.firewalld:
|
||||||
|
interface: "{{ wireguard_interface }}"
|
||||||
|
zone: "{{ wireguard_firewalld_zone }}"
|
||||||
|
state: enabled
|
||||||
|
permanent: true
|
||||||
|
immediate: true
|
||||||
|
|
||||||
|
- name: Permit this host's public WireGuard listener
|
||||||
|
ansible.posix.firewalld:
|
||||||
|
port: "{{ wireguard_listen_port }}/udp"
|
||||||
|
zone: "{{ wireguard_public_firewalld_zone }}"
|
||||||
|
state: enabled
|
||||||
|
permanent: true
|
||||||
|
immediate: true
|
||||||
|
when: wireguard_listen_port | int > 0
|
||||||
|
|
||||||
|
- name: Enable the WireGuard interface
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "wg-quick@{{ wireguard_interface }}.service"
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
daemon_reload: true
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Apply pending WireGuard handlers before verification
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Wait for every WireGuard peer handshake
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- wg
|
||||||
|
- show
|
||||||
|
- "{{ wireguard_interface }}"
|
||||||
|
- latest-handshakes
|
||||||
|
register: wireguard_latest_handshakes
|
||||||
|
changed_when: false
|
||||||
|
retries: "{{ wireguard_handshake_retries }}"
|
||||||
|
delay: "{{ wireguard_handshake_delay }}"
|
||||||
|
until:
|
||||||
|
- wireguard_latest_handshakes.stdout_lines | length == wireguard_peers | length
|
||||||
|
- wireguard_latest_handshakes.stdout_lines | select('search', '\t0$') | list | length == 0
|
||||||
|
when: not ansible_check_mode
|
||||||
25
ansible/roles/wireguard_overlay/templates/wg.conf.j2
Normal file
25
ansible/roles/wireguard_overlay/templates/wg.conf.j2
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Managed by Ansible. Do not edit manually.
|
||||||
|
[Interface]
|
||||||
|
Address = {{ wireguard_address }}
|
||||||
|
PrivateKey = {{ wireguard_private_key_material.content | b64decode | trim }}
|
||||||
|
MTU = {{ wireguard_mtu }}
|
||||||
|
SaveConfig = false
|
||||||
|
{% if wireguard_listen_port | int > 0 %}
|
||||||
|
ListenPort = {{ wireguard_listen_port }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for peer in wireguard_peers %}
|
||||||
|
[Peer]
|
||||||
|
# {{ peer.name }}
|
||||||
|
PublicKey = {{ hostvars[peer.host].wireguard_public_key }}
|
||||||
|
AllowedIPs = {{ peer.allowed_ips | join(', ') }}
|
||||||
|
{% if peer.endpoint is defined %}
|
||||||
|
Endpoint = {{ peer.endpoint }}
|
||||||
|
{% endif %}
|
||||||
|
{% if peer.persistent_keepalive | default(0) | int > 0 %}
|
||||||
|
PersistentKeepalive = {{ peer.persistent_keepalive }}
|
||||||
|
{% endif %}
|
||||||
|
{% if not loop.last %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
@@ -73,6 +73,13 @@
|
|||||||
- packages_rocky
|
- packages_rocky
|
||||||
- services_systemd
|
- services_systemd
|
||||||
|
|
||||||
|
- name: Configure WireGuard overlay
|
||||||
|
hosts: wireguard_overlay
|
||||||
|
become: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- wireguard_overlay
|
||||||
|
|
||||||
- name: Configure Aegis Fedora IoT profile
|
- name: Configure Aegis Fedora IoT profile
|
||||||
hosts: role_aegis
|
hosts: role_aegis
|
||||||
become: true
|
become: true
|
||||||
@@ -87,6 +94,13 @@
|
|||||||
roles:
|
roles:
|
||||||
- profile_atlas
|
- profile_atlas
|
||||||
|
|
||||||
|
- name: Configure Atlas phase-one backend services
|
||||||
|
hosts: role_backend_phase1
|
||||||
|
become: true
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- profile_backend_phase1
|
||||||
|
|
||||||
- name: Configure Rocky Linux server
|
- name: Configure Rocky Linux server
|
||||||
hosts: rocky_server
|
hosts: rocky_server
|
||||||
become: true
|
become: true
|
||||||
|
|||||||
@@ -38,22 +38,6 @@ services:
|
|||||||
# networks:
|
# networks:
|
||||||
# - web
|
# - web
|
||||||
|
|
||||||
navidromedb:
|
|
||||||
image: docker.io/library/postgres:13
|
|
||||||
container_name: navidromedb
|
|
||||||
restart: unless-stopped
|
|
||||||
mem_limit: 2048m
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: "navidrome_db"
|
|
||||||
POSTGRES_USER: "navidrome"
|
|
||||||
POSTGRES_PASSWORD: "{{ vault_postgres_root_password }}"
|
|
||||||
volumes:
|
|
||||||
- "/opt/postgres/data:/var/lib/postgresql/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:5432:5432"
|
|
||||||
networks:
|
|
||||||
- web
|
|
||||||
|
|
||||||
gitea:
|
gitea:
|
||||||
image: docker.gitea.com/gitea:1.25.2
|
image: docker.gitea.com/gitea:1.25.2
|
||||||
container_name: gitea
|
container_name: gitea
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
# Copy the persistent Docker data from the retired Ubuntu server to the Rocky
|
# Copy the persistent NPM and Gitea data from the retired Ubuntu server to the Rocky
|
||||||
# replacement. Run this script on the Ubuntu source as root. It is a dry run
|
# replacement. Run this script on the Ubuntu source as root. It is a dry run
|
||||||
# unless --execute and --quiesce-source are both supplied. Extended attributes
|
# unless --execute and --quiesce-source are both supplied. Extended attributes
|
||||||
# are deliberately not copied: Rocky must assign its own SELinux labels.
|
# are deliberately not copied: Rocky must assign its own SELinux labels.
|
||||||
@@ -14,11 +14,8 @@ EXECUTE=false
|
|||||||
QUIESCE_SOURCE=false
|
QUIESCE_SOURCE=false
|
||||||
|
|
||||||
DATA_PATHS='
|
DATA_PATHS='
|
||||||
/opt/navidrome/data
|
|
||||||
/opt/music
|
|
||||||
/opt/npm/data
|
/opt/npm/data
|
||||||
/opt/npm/letsencrypt
|
/opt/npm/letsencrypt
|
||||||
/opt/postgres/data
|
|
||||||
/opt/gitea/data
|
/opt/gitea/data
|
||||||
'
|
'
|
||||||
|
|
||||||
@@ -26,8 +23,8 @@ usage() {
|
|||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: sudo ./scripts/migrate_prometheus_data.sh --destination USER@HOST [options]
|
Usage: sudo ./scripts/migrate_prometheus_data.sh --destination USER@HOST [options]
|
||||||
|
|
||||||
Copies persistent Navidrome, Nginx Proxy Manager, PostgreSQL and Gitea data to
|
Copies persistent Nginx Proxy Manager and Gitea data to the Rocky server with
|
||||||
the Rocky server with rsync. The destination Docker containers must be stopped.
|
rsync. The destination Docker containers must be stopped.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--destination USER@HOST Rocky SSH destination (required).
|
--destination USER@HOST Rocky SSH destination (required).
|
||||||
@@ -97,7 +94,7 @@ if [ -n "$IDENTITY_FILE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$EXECUTE" = true ] && [ "$QUIESCE_SOURCE" != true ]; then
|
if [ "$EXECUTE" = true ] && [ "$QUIESCE_SOURCE" != true ]; then
|
||||||
fail '--execute requires --quiesce-source to keep PostgreSQL data consistent'
|
fail '--execute requires --quiesce-source to keep application data consistent'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
require_command rsync
|
require_command rsync
|
||||||
|
|||||||
@@ -1,61 +1,78 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
38633239386338323332633564613266393665666562353139646234343261303131336663316634
|
31386434333363613930316363393564373332303236306233643365386639346631336232373361
|
||||||
6535313265356434373464326664303937353132643636630a373966646565663564613463643732
|
3732633931376438313835373537666438383664306266380a633531316432343166323361303465
|
||||||
35646136346564636565616134303064313636643263343765396138653830646664633366656365
|
39306136386664356163346266303963373839373763343136346135633236333333326331313063
|
||||||
3137643164396265650a663338346161333832643732653539366462323039313563383735626661
|
6264306265373865310a373735393632373861333433326632303931633732636535326264346537
|
||||||
62626461656636366634353731613932653235323463336263363866336466356534636435313831
|
39633766326361666432396432623132616666363462373237623664363664373362346366383230
|
||||||
64373235383930663232613932623935346465643633666233386461346635353663323638633964
|
65326665303762356233356531306331333231376163353638363936626562623937323166623065
|
||||||
61366664323631313631663662643437386365616162333161636166666635636633333061323739
|
62633165383033303362336238343037353930396430336537363565333132666532323662653262
|
||||||
38386330326534303966613165656363633838313366666164656133386263646561306139383032
|
65373531313264353938666161373064393239653165666462336665323962336134343432316134
|
||||||
66646133643239396436653564323837623031393439303938323138326330636633613864623565
|
32373361613539356262313333663964623034313230356439626139383539326261663765373034
|
||||||
62616335633563333962653561633665643336303230623934356565363531323237643433306436
|
36633933366662656464306561393130373238313730336638663932633935313037363133636266
|
||||||
33363238306438333765323034636261333830653836656538636662303363363733356462353764
|
36663935303735623132663464376633306431396662306166393831313566323238363865303162
|
||||||
34636136666563333138383663643931363039303737313763373765356336656564633665666530
|
66303732376530653435613966373832333161333137366230613166303061333433336461386234
|
||||||
32346262636530646365636565656139623639346262373963346462626638316162616666373339
|
63393030616662373666643164663862653037383336323766646330653131623930353265336231
|
||||||
33353031313365356537646237356139613834343737343361636133343837613638643432346134
|
39646335353635616465663763306262353931316562616437313362616136623735646535616431
|
||||||
64313232646638633035383234303934666139616162356431306161353866316136663830343737
|
33363439623361343231363663363535663265366166346439623935323632366336363135373462
|
||||||
38646564393936383939373435313363653338653737346333643165626437386366346339303764
|
62646662316538323734333334393566333463653833326162663065646634363336373865623932
|
||||||
34623735613130643636623733353161393034303964656635366466396362393435376565313832
|
33646533636638386534366561663930613536663935663638313137646537626431393035653466
|
||||||
32623936646534656166383930666133663132663363623435393434663734363533653036663230
|
36623435613966323166363630383531656436373366343364346439343034333934623836336636
|
||||||
30663130626237303938613236306465613534346466663333663432316539653836323030643636
|
38653534343563393434646430306662343435653465363439326261373537666233353731306436
|
||||||
64646564643435373938363430303531333461613438633435356231656666373261636135396534
|
35323739336361343665633239366633393530346335396635316238323435663466316235376536
|
||||||
66343435663835646161376636633662353337386238316131623737353364383130653334643864
|
38303839313433643038353236616632626363653339346334303136306138336461383831626536
|
||||||
63353836303230303263343939373436646262346565313039323037383966636636656638366231
|
39303966393034333739363061363865326165303236656438316537396463383935393764346135
|
||||||
31343431373632363031346635663834393036623166346262393736346361323837303462346130
|
66323632323833353833643434316566326366633562666262386232653730303038613336663264
|
||||||
66643563306633323433616461343364326162336435363534326237366665316638353365376138
|
62636332383834393861373665393364653362636339346632303463386565633830336363393065
|
||||||
61633263643637323031613462303138303133623136323637633862623032623465616538643331
|
31383530613161366436323163393366386635316562633436383134623061353937326363396439
|
||||||
31343134353166383964666431353231636339636366626164346461386339356437386261336365
|
38636438396637616362373937666361303536376164636533653536306338356263313965623336
|
||||||
36373936356534623035663531353132633837333366663732393661613162316234373137333861
|
32633461623861643138613734313164633562613932386163643062353636376266633166373838
|
||||||
34656465343231623532343231646565316664373662626134346530653164393966333738656130
|
39613438653531316333663736366161313832613830353566356461393435656234383037353335
|
||||||
62663238373135643033396334386236313531646131313765323039306431323938386163316434
|
32343661376266353538643531313239306432646335383734623233373063316430383362643531
|
||||||
64616530343431303865336431666662343766303931653336376131313633306431346165623335
|
35393263343534363936373361316265653934383735663933663365363564663966646335353337
|
||||||
64383262626332336430373337313462613766613931613337393335393237616463646631623562
|
63366365383466393765336533363130643236313331633537346237353631623334633330376164
|
||||||
35343938323933663835313334306536343231663866616566653434366564313134393831343362
|
32616530623032663761643437336536323332343130393339313232623364656334613233323833
|
||||||
63396630366338626133383232666265383266396166306536633233306139666531613031643262
|
64346637343738386335343631333035636337633732333662663763326362636432646136353064
|
||||||
37356262363466303032336264333864646632373533396239393565653538383161653762313432
|
30393061326435323837633632333765623931663265326137373135303035623464333366653566
|
||||||
37313230666237626530316133393134303133613463363362323132653932333663313236623639
|
66613133396339376264313964353932396136613538383264363865633536373839663465333437
|
||||||
65326665626432386132323131353838346439393061343963616565306464636264626630396230
|
37613765626131343330653063663764346364316264363635653438396331363263623562333735
|
||||||
31373963326134383762373737626238353964383565336132623836333535313635316637653966
|
30646563303439663639376430336336383761363365353838613036333032306434623661636138
|
||||||
39386563396661663439343463353262656237616633646466323638346362376335306330373161
|
63616439636437663732386164346365643834376631303263376563323662373734653631613730
|
||||||
37343431316163626365386130383862643461363132636138646634663832383933303937643934
|
31373039656238303462643930303531613931373065316435303661343862623034653364653736
|
||||||
34326131353262643165336138643835333066663432353263333939633534383137393136633034
|
37613835616630316133333130633631653639336266313438633166316539373433333661613839
|
||||||
35386538616633336230353332306631353566343831376662306264386330623631653437393666
|
32356139366232313336343062323265366563633735383661393335386163653133313664613264
|
||||||
30646438666663383334333431336230373635663162626661373539336434646131346238623435
|
63303739323863333439316461366137656434666366306466356633306663623730653939663430
|
||||||
32303361366565353161646136386237366639343664616637303232376232316334366437636630
|
39313764663534316562326435316264633236373834323665356266346532323565323532333438
|
||||||
37633866376433633537373166613436303335663766303539336332366231353435646163386538
|
33306237623430613463393164383332356533303433343465343930636563363862373330613632
|
||||||
66323963303034393237323035366634303736653735623937323763336234376563663563333132
|
38643937613263313435626166313464316133373338373261666331653436373063393162636339
|
||||||
39646237306334326635353563326536633764306534313535383836633033373832316336383530
|
66356434663239646334383433626566336433653265336332323866633666646132663836313537
|
||||||
33316432356366363934666264306661373037663563333165633132643834353262376661636463
|
66663766306438396131623533613834626331653731323137303539303835306632663132616363
|
||||||
63326662663739616139393438393734353833376239363531376261303639376363646532666465
|
64303438353836613136623562626664326364656133383865383730373762666539383036396337
|
||||||
66356533393331393535393161653131343536353535373432323463346331646663353439383237
|
66393636343561333435313032353939316138306336316337353861383238326136373265663433
|
||||||
37386262633765326331383335613263653331303561343636626337333265313633373533363331
|
62366566343866626336646466386561353061343735306565383437333931613635393034363430
|
||||||
32373564646464626633323833663432663562363639373931623535306534646664346165356161
|
37633765663239643435623066313331353862393966306563393838386334393162306562363062
|
||||||
33306330363239386439646563353865646339323633646535316336363239303564373937636233
|
61633363386564373834383432323861323364623365306439353631323463326461383039356466
|
||||||
37396434353830386130316237386532333939363330306165633630336662333537663265363736
|
34396464653233626435656463326439303665633532656639376633353932666630626564616564
|
||||||
61636437623832383032643539626338313932303335333461363566363532663633353631353933
|
34326638383634353033326232646339393638663637313136653763336265616635326666336530
|
||||||
62376531303638303838663737373462323339633632626439353439396139656639373331656636
|
33663261376262626136346265656130653831636662306132393837306135643831353534626636
|
||||||
61303531383433343961666336363231353638656137666364323133326233623964363536303065
|
34383762386665666363313932336632326230646439663366663037323562633630373137333232
|
||||||
33623130653564363261653333343732653539386565326133386165313536613732366463303537
|
37393164656137303131313738396131613561306332356436303436636338623233343637363332
|
||||||
63616538383536636232636562653863323334633839656366373761626561656462316161323630
|
36613137316337666461333237373266326238303531396432383461616239316630346230303735
|
||||||
33396566333463326136626231653536336134653232306536396434636438366335633137623433
|
63636535383766353338643932366339333130663632386337643932636630316432313465393766
|
||||||
31636362366431396364
|
39613065623631316165313134386231616165366266323634643632626230303861376461323433
|
||||||
|
35346137326563646332303334313530383464373838373633363635373766333362386466323836
|
||||||
|
32613236646561343365656239346239353866663336393930356238653463336361333033643331
|
||||||
|
61323430663031383239353363616666336637666230663633616464616638303966343631386632
|
||||||
|
32333735643936353638666336303133356435396338653465323234316234626536666635333238
|
||||||
|
38313661303466333464666538363938656631396666643566343763396638336663376430646532
|
||||||
|
38666234653330646262633062356238343536336637356432313137313561363937363936383364
|
||||||
|
30643937383833663339643862363234643765386164316138636565643434373734383339363138
|
||||||
|
36323163633837326132393365333236633264386664373234313061373835346634663137383837
|
||||||
|
62666561353532303663346365343131316233633163323938623066656332383030393864363536
|
||||||
|
38383939383935613432613837333863313239653831333438383133343763633838353964353161
|
||||||
|
61323462343835613937653465633563306462613631323762656437626133336638396663646362
|
||||||
|
30323661383134653336366234663333336261353162373030626266656336356233316265636661
|
||||||
|
34303865313433633138363936373561636537353831373033303163646436303932626138356633
|
||||||
|
63656364353163313037613262396338636230646330666331616534313466306361363433656132
|
||||||
|
66633231626665303165346339373764666264313838313063323732653837383736633235363064
|
||||||
|
37353632336238623366313432376163653535656134633634313065356533343933666135396633
|
||||||
|
30613134646132613637656461303431613064393438363231383464663765316638
|
||||||
|
|||||||
@@ -9,14 +9,8 @@ vault_icloud_mail_password: "REPLACE_ME"
|
|||||||
vault_git_work_email: "REPLACE_ME"
|
vault_git_work_email: "REPLACE_ME"
|
||||||
vault_git_work_gpg: "REPLACE_ME"
|
vault_git_work_gpg: "REPLACE_ME"
|
||||||
vault_openai_api_key: "REPLACE_ME"
|
vault_openai_api_key: "REPLACE_ME"
|
||||||
vault_navidrome_db_password: "REPLACE_ME"
|
|
||||||
vault_postgres_root_password: "REPLACE_ME"
|
|
||||||
vault_ikaros_authorized_ssh_keys:
|
vault_ikaros_authorized_ssh_keys:
|
||||||
- "ssh-ed25519 REPLACE_ME"
|
- "ssh-ed25519 REPLACE_ME"
|
||||||
vault_atlas_authorized_ssh_keys:
|
|
||||||
- "ssh-ed25519 REPLACE_ME atlas-admin"
|
|
||||||
vault_atlas_admin_password_hash: "REPLACE_WITH_A_SHADOW_COMPATIBLE_HASH"
|
vault_atlas_admin_password_hash: "REPLACE_WITH_A_SHADOW_COMPATIBLE_HASH"
|
||||||
vault_atlas_samba_password: "REPLACE_ME"
|
vault_atlas_samba_password: "REPLACE_ME"
|
||||||
vault_atlas_immich_db_password: "REPLACE_ME"
|
vault_atlas_immich_db_password: "REPLACE_ME"
|
||||||
vault_prometheus_atlas_sftp_private_key: |
|
|
||||||
REPLACE_WITH_A_DEDICATED_ATLAS_SFTP_PRIVATE_KEY
|
|
||||||
|
|||||||
Reference in New Issue
Block a user