mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 11:02:47 +00:00
Compare commits
2 Commits
add75d74e9
...
bd13cb65cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd13cb65cc | ||
|
|
77afdda0a3 |
54
AGENTS.md
54
AGENTS.md
@@ -59,10 +59,8 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora
|
||||
`ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff`
|
||||
- Atlas network/share hardening:
|
||||
`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`
|
||||
- Prometheus/Aegis WireGuard gateway:
|
||||
`ansible-playbook ansible/site.yml --limit prometheus,aegis --tags wireguard --check --diff`
|
||||
- DuckDNS config only: `ansible-playbook ansible/site.yml --limit prometheus --tags duckdns --check --diff`
|
||||
|
||||
## Conventions
|
||||
@@ -151,36 +149,29 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i
|
||||
`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
|
||||
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/services/data`, `zpool/services/data/navidrome`, and `zpool/services/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.
|
||||
- `profile_backend_phase1` temporarily runs rootless Navidrome and Syncthing on Atlas until Uranus replaces
|
||||
them. It binds only to Atlas' LAN IP, never `wg0`; Navidrome and the Syncthing GUI admit only Aegis as
|
||||
the source-NAT gateway, while native Syncthing ports admit the configured LAN. It initializes fresh
|
||||
state only and never migrates or deletes source application data.
|
||||
- `wireguard_overlay` manages `wg0` between Prometheus (`10.0.0.1`) and Aegis (`10.0.0.2`). It persists private
|
||||
keys only on their respective hosts, exchanges only derived public keys through Ansible, and verifies a real peer
|
||||
handshake. Prometheus opens `51820/udp`; Aegis is the LAN gateway. Its persistent IPv4 forwarding, narrowly scoped
|
||||
WireGuard-to-LAN firewalld policy, and source masquerading permit Prometheus to reach LAN services without a static
|
||||
route on the router. Prometheus includes `192.168.178.0/24` in Aegis' peer `AllowedIPs`; add the Uranus VIP there
|
||||
when it is assigned. After a firewalld reload, restore Prometheus' rootful Podman networking with
|
||||
`podman network reload --all` so the existing proxy stack retains container DNS.
|
||||
|
||||
## Atlas NAS TODO
|
||||
Completed validation: the existing RAIDZ2 pool and datasets, SELinux, LAN firewall, SSH, Cockpit with
|
||||
the selected 45Drives plugins, encrypted SMB3 `Archive`, the Aegis-only NFSv4 `photobook` export, and
|
||||
the Prometheus--Atlas WireGuard path are operational. Aegis has validated NFSv4.2 read, write, delete,
|
||||
the former Prometheus--Atlas WireGuard path were operational. Aegis has validated NFSv4.2 read, write, delete,
|
||||
and `all_squash` mapping to UID/GID `1100` end-to-end.
|
||||
- Complete the Phase 1 Navidrome cutover: stop the Prometheus writer, copy and verify its complete
|
||||
`/opt/navidrome/data/` directory (including SQLite sidecars) under
|
||||
`/zpool/services/data/navidrome/`, then set `backend_phase1_start_services: true` and validate
|
||||
Navidrome on Atlas through WireGuard. Do not delete the source until a restore test succeeds.
|
||||
- Start and validate the rendered Syncthing Quadlet only after its device IDs, star topology, folders,
|
||||
folder modes, ignore rules, and GUI/API protection are declared. Validate its GUI and native transfer
|
||||
ports through WireGuard only.
|
||||
- Validate the Prometheus--Aegis WireGuard gateway after migration: peer handshake and counters, Aegis IPv4
|
||||
forwarding and masquerading, and an NPM request from Prometheus to an Atlas LAN address. Add the Uranus VIP to
|
||||
Prometheus' Aegis peer when the cluster control plane is assigned.
|
||||
- Validate temporary Atlas Navidrome and Syncthing through Aegis before creating their NPM Proxy Hosts.
|
||||
Keep NPM host configuration manual; plan their eventual Uranus migration with storage and routing declared
|
||||
separately from the NAS baseline.
|
||||
- Decide whether a common SMB/NFS namespace is required. `Archive` (SMB) and `photobook` (NFS) are
|
||||
intentionally distinct today; only if a shared namespace is selected, finalize its UID/GID, group,
|
||||
and POSIX ACL model and test the same files through both protocols.
|
||||
@@ -229,8 +220,9 @@ and `all_squash` mapping to UID/GID `1100` end-to-end.
|
||||
- `aegis` is a remote Fedora IoT Raspberry Pi 4 node. Bootstrap it once with
|
||||
`ansible/bootstrap/aegis.bu`; the remaining configuration is applied by `profile_aegis` over SSH.
|
||||
- Fedora IoT is immutable. Do not add it to mutable Fedora package or shared dotfile roles.
|
||||
- `profile_aegis` owns the `nfs-utils` rpm-ostree layer used as the Atlas NFS client and reports the
|
||||
required reboot without initiating it. It also owns rootful Podman Quadlets, persistent container
|
||||
- `profile_aegis` owns the `nfs-utils` and `wireguard-tools` rpm-ostree layers and reports the required reboot
|
||||
without initiating it. `wireguard_overlay` then configures Aegis as the WireGuard LAN gateway with persistent IPv4
|
||||
forwarding, a scoped inter-zone policy, and source masquerading. It also owns rootful Podman Quadlets, persistent container
|
||||
state under `/var/lib`, the Podman auto-update timer, LAN-restricted firewalld rules, and SSH hardening. Keep
|
||||
`aegis_lan_subnet`, `aegis_adguard_web_port`, and `aegis_network_connection_uuid` host-specific;
|
||||
SSH permits only the declared
|
||||
|
||||
102
README.md
102
README.md
@@ -111,21 +111,19 @@ the Compose stack, update DNS, or perform a cutover.
|
||||
The server profile installs platform-specific packages, Podman and podman-compose, declared systemd
|
||||
services, and firewalld. The manually activated `podman-compose-server` unit contains the existing
|
||||
Nginx Proxy Manager and Gitea services. The desired Compose file no longer includes Navidrome,
|
||||
Syncthing, or the obsolete Navidrome PostgreSQL database. Navidrome and Syncthing belong to Atlas;
|
||||
official Navidrome uses SQLite instead. Applying the profile does not stop or remove legacy
|
||||
containers and does not delete `/opt/postgres/data`.
|
||||
Syncthing, or the obsolete Navidrome PostgreSQL database; their temporary Atlas deployment is managed
|
||||
by `profile_backend_phase1`. Applying the profile does not stop or remove legacy containers and does
|
||||
not delete `/opt/postgres/data`.
|
||||
|
||||
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
|
||||
from Ikaros or Nymph with the `npm-tunnel` Bash alias. Nextcloud remains disabled and the profile
|
||||
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.
|
||||
NPM remains managed only by `profile_server`. Its WireGuard peer is Aegis (`10.0.0.2`), which forwards
|
||||
selected requests to LAN addresses and source-NATs them so no static route is required on the router.
|
||||
Use an Atlas LAN address for any current NAS-backed upstream; when Uranus receives its VIP, add that VIP
|
||||
to Prometheus' Aegis peer `AllowedIPs` and declare the corresponding proxy target separately.
|
||||
|
||||
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:
|
||||
|
||||
@@ -193,8 +191,10 @@ ansible/bootstrap/generate-aegis-ign.sh --write IMAGE DEVICE
|
||||
The controller manages it remotely as `pi@aegis`; unlike local desktop profiles, Aegis is
|
||||
intentionally an SSH inventory target. `profile_aegis` manages rootful Podman Quadlets for AdGuard
|
||||
Home and iCloudPD, persistent data under `/var/lib`, the Podman auto-update timer, LAN-restricted
|
||||
firewalld rules, SSH key-only access for `pi`, the `nfs-utils` rpm-ostree layer required by the
|
||||
Atlas NFS client, and `wake-ikaros`. A new layered package deployment requires a manual reboot; the
|
||||
firewalld rules, SSH key-only access for `pi`, the `nfs-utils` and `wireguard-tools` rpm-ostree layers,
|
||||
and `wake-ikaros`. `wireguard_overlay` makes Aegis the internal endpoint and LAN gateway for Prometheus:
|
||||
it enables persistent IPv4 forwarding, installs a scoped WireGuard-to-LAN firewalld policy, and source-NATs
|
||||
forwarded tunnel traffic so the router needs no static route. A new layered package deployment requires a manual reboot; the
|
||||
role reports this condition but never reboots Aegis automatically. Set the host-local
|
||||
`aegis_lan_subnet`, `aegis_adguard_web_port`, and `aegis_network_connection_uuid` values before
|
||||
applying it. The playbook permits
|
||||
@@ -225,7 +225,7 @@ ANSIBLE_LOCAL_TEMP=/tmp/ansible-local \
|
||||
ansible-playbook ansible/site.yml --limit aegis --tags dns --ask-become-pass
|
||||
```
|
||||
|
||||
Layer the Atlas NFS client package independently, then reboot Aegis manually when the role reports
|
||||
Layer the Aegis NFS and WireGuard client tools independently, then reboot Aegis manually when the role reports
|
||||
that the new deployment is ready:
|
||||
|
||||
```bash
|
||||
@@ -243,17 +243,9 @@ clients use NFSv4 and Windows/WSL clients use SMB; both are restricted to the co
|
||||
|
||||
For the first run, provide `vault_atlas_admin_password_hash`, `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
|
||||
include both WireGuard peers in the same idempotent playbook run:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/site.yml --limit prometheus,atlas \
|
||||
-e atlas_connection_username=<existing-admin> \
|
||||
-e atlas_create_pool=true
|
||||
```
|
||||
|
||||
The explicit pool gate is safe to repeat: the role creates the RAIDZ2 pool only when it is absent.
|
||||
WireGuard waits for a real peer handshake before the play continues.
|
||||
existing administrator. The explicit pool gate is safe to repeat: the role creates the RAIDZ2 pool only when
|
||||
it is absent. Atlas no longer participates in the WireGuard overlay; its old interface is retired manually only after
|
||||
Prometheus and Aegis have completed the replacement handshake.
|
||||
|
||||
`vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text
|
||||
Cockpit password. Subsequent runs use `atlas_admin_username`. Atlas declares storage, sharing, and its
|
||||
@@ -279,49 +271,33 @@ and NPM Quadlets share one Podman network. Immich runs as `1100:1100`; Server an
|
||||
and Photobook is mounted read-only at `/external/photobook`. NPM publishes ports `80` and `443`; its
|
||||
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/services/data/navidrome`, and Syncthing persists at
|
||||
`/zpool/services/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.
|
||||
Atlas temporarily hosts rootless Navidrome and Syncthing until Uranus replaces them. They bind only to
|
||||
Atlas' LAN address (`192.168.178.55`); WireGuard remains exclusively between Prometheus (`10.0.0.1`)
|
||||
and Aegis (`10.0.0.2`). Their state is initialized ex novo in `/zpool/services/data/navidrome` and
|
||||
`/zpool/services/data/syncthing`; no source application state is migrated. The music library at
|
||||
`/zpool/media/music` is populated separately.
|
||||
|
||||
`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/services/data/navidrome/`, preserving `navidrome.db` and any SQLite sidecar files. Then set
|
||||
this variable to true and rerun the role to enable and start Navidrome and Syncthing. The playbook
|
||||
never copies or deletes application data.
|
||||
The separate `wireguard_overlay` role manages `wg0` between Prometheus (`10.0.0.1`) and Aegis
|
||||
(`10.0.0.2`), generating private keys once on their respective hosts and exchanging only public keys
|
||||
through Ansible. Prometheus alone opens `51820/udp`. Aegis forwards only the declared overlay-to-LAN
|
||||
traffic and source-NATs it, so Atlas and future Uranus nodes require neither a VPN interface nor a router
|
||||
static route. Atlas permits Navidrome (`4533/tcp`) and the Syncthing GUI (`8384/tcp`) only from Aegis;
|
||||
Syncthing native ports are limited to the LAN. Configure NPM manually with
|
||||
`http://192.168.178.55:4533` and `http://192.168.178.55:8384` after the services are healthy.
|
||||
Prometheus' peer includes the LAN subnet in `AllowedIPs`; add the Uranus VIP there when it exists.
|
||||
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.
|
||||
|
||||
Validate and render the Atlas services with:
|
||||
Validate the gateway 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
|
||||
ansible-playbook ansible/site.yml --limit prometheus,aegis --tags wireguard --check --diff
|
||||
```
|
||||
|
||||
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.
|
||||
The first real WireGuard run must include both peers. If Fedora IoT has just layered `wireguard-tools`,
|
||||
reboot Aegis manually and rerun the command without `--check`; the role then waits for a real peer
|
||||
handshake.
|
||||
|
||||
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
|
||||
@@ -412,8 +388,8 @@ ansible-playbook ansible/site.yml --limit deadalus --tags ai_agents --check --di
|
||||
| `profile_workstation_dev_wsl` | WSL development setup. |
|
||||
| `profile_server` | Server setup. |
|
||||
| `profile_atlas` | Rocky Linux 9 NAS setup. |
|
||||
| `profile_backend_phase1` | Rootless Navidrome and Syncthing on Atlas. |
|
||||
| `wireguard_overlay` | Prometheus/Atlas WireGuard overlay. |
|
||||
| `profile_backend_phase1` | Temporary rootless Atlas Navidrome and Syncthing services. |
|
||||
| `wireguard_overlay` | Prometheus/Aegis WireGuard LAN gateway. |
|
||||
| `profile_aegis` | Fedora IoT always-on LAN node. |
|
||||
| `dotfiles_common` | Shared user dotfiles. |
|
||||
|
||||
@@ -425,8 +401,8 @@ platform_void -> packages_void + services_runit
|
||||
platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host
|
||||
platform_fedora -> packages_fedora + services_systemd
|
||||
platform_rocky -> packages_rocky + services_systemd
|
||||
wireguard_overlay -> wireguard_overlay (after platform_rocky)
|
||||
role_aegis -> profile_aegis
|
||||
wireguard_overlay -> wireguard_overlay (after Aegis profile and platform_rocky)
|
||||
atlas -> profile_atlas
|
||||
role_backend_phase1 -> profile_backend_phase1 (after atlas)
|
||||
rocky_server -> dotfiles_common + profile_server (after platform_rocky)
|
||||
@@ -531,7 +507,7 @@ ansible-playbook ansible/site.yml --list-tags
|
||||
| `sharing` | Atlas NFSv4 and SMB3 configuration. |
|
||||
| `storage` | Atlas child ZFS datasets. |
|
||||
| `tmux` | tmux configuration and plugins. |
|
||||
| `wireguard` | Prometheus/Atlas WireGuard overlay. |
|
||||
| `wireguard` | Prometheus/Aegis WireGuard LAN gateway. |
|
||||
| `wsl` | WSL bootstrap and configuration. |
|
||||
|
||||
## Bootstrapping a new machine
|
||||
|
||||
@@ -40,4 +40,3 @@ server_firewalld_services:
|
||||
server_firewalld_ports: []
|
||||
server_sshd_service_name: sshd
|
||||
server_compose_selinux_mount_option: Z
|
||||
server_syncthing_enabled: false
|
||||
|
||||
@@ -75,21 +75,6 @@ server_directories:
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
server_syncthing_enabled: true
|
||||
server_syncthing_directories:
|
||||
- path: /opt/syncthing/config
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /srv/syncthing
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /srv/syncthing/data
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0755"
|
||||
|
||||
server_sshd_settings:
|
||||
PermitRootLogin: "no"
|
||||
|
||||
|
||||
@@ -15,6 +15,26 @@ aegis_host_dns_servers:
|
||||
- 192.168.178.1
|
||||
aegis_host_dns_search_domains:
|
||||
- fritz.box
|
||||
|
||||
# Aegis is the internal WireGuard endpoint and NAT gateway for the LAN.
|
||||
wireguard_overlay_enabled: true
|
||||
wireguard_address: 10.0.0.2/24
|
||||
wireguard_enable_ipv4_forwarding: true
|
||||
wireguard_reload_rootful_podman_networks: true
|
||||
wireguard_forwarding_policies:
|
||||
- name: aegis-wireguard-to-lan
|
||||
ingress_zone: wireguard
|
||||
egress_zone: public
|
||||
source: 10.0.0.0/24
|
||||
destination: "{{ aegis_lan_subnet }}"
|
||||
masquerade: true
|
||||
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
|
||||
aegis_ssh_authorized_keys:
|
||||
- name: ikaros
|
||||
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrIxXjA3ffPwziKGR5gzc4gAoBehQPlnEMcXF4Wl0ZS ikaros"
|
||||
|
||||
@@ -52,19 +52,14 @@ atlas_manage_storage: true
|
||||
atlas_manage_sharing: true
|
||||
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
|
||||
|
||||
# WireGuard is retired on Atlas. These rootless services are a temporary home
|
||||
# until Uranus replaces them.
|
||||
backend_phase1_enabled: true
|
||||
backend_phase1_start_services: false
|
||||
backend_phase1_wireguard_address: 10.0.0.2
|
||||
backend_phase1_start_services: true
|
||||
backend_phase1_bind_address: "{{ ansible_host }}"
|
||||
backend_phase1_firewalld_zone: "{{ atlas_firewalld_zone }}"
|
||||
backend_phase1_npm_source_ip: "{{ atlas_aegis_ip }}"
|
||||
backend_phase1_syncthing_native_subnet: "{{ atlas_lan_subnet }}"
|
||||
|
||||
rocky_manage_openzfs_repo: true
|
||||
rocky_manage_syncthing_binary: false
|
||||
|
||||
@@ -21,10 +21,12 @@ wireguard_listen_port: 51820
|
||||
wireguard_enable_ipv4_forwarding: true
|
||||
wireguard_reload_rootful_podman_networks: true
|
||||
wireguard_peers:
|
||||
- name: atlas
|
||||
host: atlas
|
||||
- name: aegis
|
||||
host: aegis
|
||||
# TODO: Aggiungere VIP di Uranus in futuro
|
||||
allowed_ips:
|
||||
- 10.0.0.2/32
|
||||
- 192.168.178.0/24
|
||||
|
||||
host_packages:
|
||||
- cockpit
|
||||
|
||||
@@ -99,7 +99,7 @@ all:
|
||||
wireguard_overlay:
|
||||
hosts:
|
||||
prometheus:
|
||||
atlas:
|
||||
aegis:
|
||||
|
||||
role_backend_phase1:
|
||||
hosts:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
aegis_hostname: aegis
|
||||
aegis_layered_packages:
|
||||
- nfs-utils
|
||||
- wireguard-tools
|
||||
aegis_network_connection_name: ""
|
||||
aegis_network_connection_uuid: ""
|
||||
aegis_host_dns_servers: []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
- name: Manage Aegis layered packages
|
||||
tags: [aegis, packages, nfs]
|
||||
tags: [aegis, packages, nfs, wireguard]
|
||||
community.general.rpm_ostree_pkg:
|
||||
name: "{{ aegis_layered_packages }}"
|
||||
state: present
|
||||
@@ -8,7 +8,7 @@
|
||||
when: aegis_layered_packages | length > 0
|
||||
|
||||
- name: Report reboot required for Aegis layered packages
|
||||
tags: [aegis, packages, nfs]
|
||||
tags: [aegis, packages, nfs, wireguard]
|
||||
ansible.builtin.debug:
|
||||
msg: Reboot Aegis to activate the newly layered packages, then rerun the playbook.
|
||||
when: aegis_layered_packages_result.needs_reboot | default(false)
|
||||
|
||||
@@ -5,9 +5,10 @@ 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_bind_address: CHANGEME_ATLAS_BIND_ADDRESS
|
||||
backend_phase1_firewalld_zone: public
|
||||
backend_phase1_npm_source_ip: CHANGEME_AEGIS_IP
|
||||
backend_phase1_syncthing_native_subnet: CHANGEME_LAN_SUBNET
|
||||
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 }}"
|
||||
|
||||
@@ -7,17 +7,20 @@
|
||||
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_bind_address != 'CHANGEME_ATLAS_BIND_ADDRESS'
|
||||
- backend_phase1_bind_address is match('^[0-9]{1,3}(\.[0-9]{1,3}){3}$')
|
||||
- backend_phase1_firewalld_zone | length > 0
|
||||
- backend_phase1_npm_source_ip != 'CHANGEME_AEGIS_IP'
|
||||
- backend_phase1_syncthing_native_subnet != 'CHANGEME_LAN_SUBNET'
|
||||
- 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
|
||||
Disable the rootful media-stack gate and provide the Atlas LAN bind
|
||||
address, firewall sources, and absolute ZFS-backed paths before
|
||||
enabling phase one. This role does not manage Prometheus or migrate
|
||||
Navidrome application data.
|
||||
application data.
|
||||
|
||||
- name: Read the rootless service account
|
||||
ansible.builtin.getent:
|
||||
@@ -132,19 +135,35 @@
|
||||
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
|
||||
- name: Permit NPM access to phase-one web interfaces through Aegis
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ item }}"
|
||||
zone: "{{ backend_phase1_wireguard_firewalld_zone }}"
|
||||
rich_rule: >-
|
||||
rule family="ipv4" source address="{{ backend_phase1_npm_source_ip }}"
|
||||
port port="{{ item }}" protocol="tcp" accept
|
||||
zone: "{{ backend_phase1_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"
|
||||
- "{{ backend_phase1_navidrome_port }}"
|
||||
- "{{ backend_phase1_syncthing_gui_port }}"
|
||||
|
||||
- name: Permit native Syncthing traffic from the LAN
|
||||
ansible.posix.firewalld:
|
||||
rich_rule: >-
|
||||
rule family="ipv4" source address="{{ backend_phase1_syncthing_native_subnet }}"
|
||||
port port="{{ item.port }}" protocol="{{ item.protocol }}" accept
|
||||
zone: "{{ backend_phase1_firewalld_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop:
|
||||
- port: "{{ backend_phase1_syncthing_transfer_port }}"
|
||||
protocol: tcp
|
||||
- port: "{{ backend_phase1_syncthing_transfer_port }}"
|
||||
protocol: udp
|
||||
- port: "{{ backend_phase1_syncthing_discovery_port }}"
|
||||
protocol: udp
|
||||
|
||||
- name: Start rootless phase-one Quadlets
|
||||
become_user: "{{ backend_phase1_username }}"
|
||||
|
||||
@@ -8,7 +8,7 @@ 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
|
||||
PublishPort={{ backend_phase1_bind_address }}:{{ backend_phase1_navidrome_port }}:4533
|
||||
Environment=ND_LOGLEVEL=info
|
||||
Environment=ND_SCANSCHEDULE=1h
|
||||
Environment=ND_SESSIONTIMEOUT=24h
|
||||
|
||||
@@ -9,10 +9,10 @@ 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
|
||||
PublishPort={{ backend_phase1_bind_address }}:{{ backend_phase1_syncthing_gui_port }}:{{ backend_phase1_syncthing_gui_port }}
|
||||
PublishPort={{ backend_phase1_bind_address }}:{{ backend_phase1_syncthing_transfer_port }}:{{ backend_phase1_syncthing_transfer_port }}
|
||||
PublishPort={{ backend_phase1_bind_address }}:{{ backend_phase1_syncthing_transfer_port }}:{{ backend_phase1_syncthing_transfer_port }}/udp
|
||||
PublishPort={{ backend_phase1_bind_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 }}
|
||||
|
||||
@@ -20,15 +20,7 @@
|
||||
owner: "{{ item.owner }}"
|
||||
group: "{{ item.group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop: >-
|
||||
{{
|
||||
(server_directories | default([]))
|
||||
+ (
|
||||
server_syncthing_directories | default([])
|
||||
if server_syncthing_enabled | default(true) | bool
|
||||
else []
|
||||
)
|
||||
}}
|
||||
loop: "{{ server_directories | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ wireguard_mtu: 1420
|
||||
wireguard_firewalld_zone: wireguard
|
||||
wireguard_public_firewalld_zone: public
|
||||
wireguard_enable_ipv4_forwarding: false
|
||||
# Legacy zone-level masquerading; policy-level masquerading is required for inter-zone forwarding.
|
||||
wireguard_enable_masquerade: false
|
||||
# A list of narrowly scoped inter-zone forwarding rules, rendered as persistent
|
||||
# firewalld policies. Each item requires name, ingress_zone, egress_zone,
|
||||
# source, and destination.
|
||||
wireguard_forwarding_policies: []
|
||||
wireguard_reload_rootful_podman_networks: false
|
||||
wireguard_handshake_retries: 12
|
||||
wireguard_handshake_delay: 5
|
||||
|
||||
@@ -14,10 +14,44 @@
|
||||
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
|
||||
- name: Validate WireGuard forwarding policies
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.name is defined
|
||||
- item.ingress_zone is defined
|
||||
- item.egress_zone is defined
|
||||
- item.source is defined
|
||||
- item.destination is defined
|
||||
fail_msg: >-
|
||||
Every WireGuard forwarding policy requires name, ingress_zone,
|
||||
egress_zone, source, and destination.
|
||||
loop: "{{ wireguard_forwarding_policies }}"
|
||||
loop_control:
|
||||
label: "{{ item.name | default('unnamed policy') }}"
|
||||
|
||||
- name: Install WireGuard userspace tools on mutable hosts
|
||||
ansible.builtin.dnf:
|
||||
name: "{{ wireguard_packages }}"
|
||||
state: present
|
||||
when: "'platform_fedora_iot' not in group_names"
|
||||
|
||||
- name: Require WireGuard userspace tools in the booted deployment
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- wg
|
||||
- --version
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
register: wireguard_userspace_tools
|
||||
|
||||
- name: Require active WireGuard userspace tools
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- wireguard_userspace_tools.rc == 0
|
||||
fail_msg: >-
|
||||
WireGuard userspace tools are not present in the booted deployment.
|
||||
On Fedora IoT, reboot after rpm-ostree layers wireguard-tools, then
|
||||
rerun the WireGuard play.
|
||||
|
||||
- name: Create private WireGuard configuration directory
|
||||
ansible.builtin.file:
|
||||
@@ -124,12 +158,33 @@
|
||||
permanent: true
|
||||
register: wireguard_firewalld_zone_result
|
||||
|
||||
- name: Reload firewalld after creating the WireGuard zone
|
||||
- name: Create the firewalld policy directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/firewalld/policies
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: wireguard_forwarding_policies | length > 0
|
||||
|
||||
- name: Render WireGuard forwarding policies
|
||||
ansible.builtin.template:
|
||||
src: wireguard-forwarding-policy.xml.j2
|
||||
dest: "/etc/firewalld/policies/{{ item.name }}.xml"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ wireguard_forwarding_policies }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: wireguard_forwarding_policy_result
|
||||
|
||||
- name: Reload firewalld after WireGuard firewall changes
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld.service
|
||||
state: reloaded
|
||||
when:
|
||||
- wireguard_firewalld_zone_result is changed
|
||||
- wireguard_firewalld_zone_result is changed or (wireguard_forwarding_policy_result | default({})) is changed
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Restore rootful Podman networking after firewalld reload
|
||||
@@ -142,7 +197,7 @@
|
||||
register: wireguard_podman_network_reload
|
||||
changed_when: wireguard_podman_network_reload.stdout_lines | length > 0
|
||||
when:
|
||||
- wireguard_firewalld_zone_result is changed
|
||||
- wireguard_firewalld_zone_result is changed or (wireguard_forwarding_policy_result | default({})) is changed
|
||||
- wireguard_reload_rootful_podman_networks | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
@@ -154,6 +209,14 @@
|
||||
permanent: true
|
||||
immediate: true
|
||||
|
||||
- name: Manage legacy WireGuard zone masquerading
|
||||
ansible.posix.firewalld:
|
||||
zone: "{{ wireguard_firewalld_zone }}"
|
||||
masquerade: true
|
||||
state: "{{ 'enabled' if wireguard_enable_masquerade | bool else 'disabled' }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
|
||||
- name: Permit this host's public WireGuard listener
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ wireguard_listen_port }}/udp"
|
||||
|
||||
@@ -11,7 +11,8 @@ ListenPort = {{ wireguard_listen_port }}
|
||||
{% for peer in wireguard_peers %}
|
||||
[Peer]
|
||||
# {{ peer.name }}
|
||||
PublicKey = {{ hostvars[peer.host].wireguard_public_key }}
|
||||
{% if peer.comment is defined %}# {{ peer.comment }}
|
||||
{% endif %}PublicKey = {{ hostvars[peer.host].wireguard_public_key }}
|
||||
AllowedIPs = {{ peer.allowed_ips | join(', ') }}
|
||||
{% if peer.endpoint is defined %}
|
||||
Endpoint = {{ peer.endpoint }}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<policy target="CONTINUE">
|
||||
<short>WireGuard forwarding: {{ item.name }}</short>
|
||||
<description>Managed WireGuard-to-LAN forwarding policy.</description>
|
||||
{% if item.masquerade | default(false) %}
|
||||
<masquerade/>
|
||||
{% endif %}
|
||||
<ingress-zone name="{{ item.ingress_zone }}"/>
|
||||
<egress-zone name="{{ item.egress_zone }}"/>
|
||||
<rule family="ipv4">
|
||||
<source address="{{ item.source }}"/>
|
||||
<destination address="{{ item.destination }}"/>
|
||||
<accept/>
|
||||
</rule>
|
||||
</policy>
|
||||
@@ -73,13 +73,6 @@
|
||||
- packages_rocky
|
||||
- services_systemd
|
||||
|
||||
- name: Configure WireGuard overlay
|
||||
hosts: wireguard_overlay
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- wireguard_overlay
|
||||
|
||||
- name: Configure Aegis Fedora IoT profile
|
||||
hosts: role_aegis
|
||||
become: true
|
||||
@@ -87,6 +80,13 @@
|
||||
roles:
|
||||
- profile_aegis
|
||||
|
||||
- name: Configure WireGuard overlay
|
||||
hosts: wireguard_overlay
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- wireguard_overlay
|
||||
|
||||
- name: Configure Atlas NAS profile
|
||||
hosts: atlas
|
||||
become: true
|
||||
|
||||
@@ -56,24 +56,6 @@ services:
|
||||
- "3000:3000"
|
||||
- "127.0.0.1:222:22"
|
||||
|
||||
{% if server_syncthing_enabled | default(true) | bool %}
|
||||
syncthing:
|
||||
image: docker.io/syncthing/syncthing:2
|
||||
container_name: syncthing
|
||||
hostname: syncthing
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "8384"
|
||||
volumes:
|
||||
- "/opt/syncthing/config:/var/syncthing{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
- "/srv/syncthing/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
ports:
|
||||
- "22000:22000/tcp"
|
||||
- "22000:22000/udp"
|
||||
- "21027:21027/udp"
|
||||
networks:
|
||||
- web
|
||||
{% endif %}
|
||||
|
||||
networks:
|
||||
web:
|
||||
|
||||
Reference in New Issue
Block a user