From 8ed439317a3185e6293da42a8ae8bf10dec635e4 Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Sun, 30 Aug 2026 14:27:07 +0200 Subject: [PATCH] Add Rocky 9 Atlas NAS profile --- AGENTS.md | 42 ++++ README.it.md | 40 +++- README.md | 35 ++- .../inventory/group_vars/platform_rocky.yml | 32 +++ ansible/inventory/host_vars/atlas.yml | 50 +++++ ansible/inventory/hosts.yml | 8 + ansible/roles/packages_rocky/tasks/main.yml | 151 +++++++++++++ ansible/roles/profile_atlas/defaults/main.yml | 51 +++++ ansible/roles/profile_atlas/handlers/main.yml | 34 +++ ansible/roles/profile_atlas/tasks/account.yml | 118 ++++++++++ ansible/roles/profile_atlas/tasks/cockpit.yml | 15 ++ ansible/roles/profile_atlas/tasks/main.yml | 15 ++ ansible/roles/profile_atlas/tasks/sharing.yml | 211 ++++++++++++++++++ ansible/roles/profile_atlas/tasks/storage.yml | 77 +++++++ .../roles/profile_atlas/tasks/syncthing.yml | 40 ++++ .../templates/00-atlas-hardening.conf.j2 | 6 + .../profile_atlas/templates/atlas-nfs.conf.j2 | 3 + .../templates/atlas-syncthing.service.j2 | 16 ++ .../profile_atlas/templates/atlas.exports.j2 | 4 + .../templates/nfs-mountd-v4only.conf.j2 | 3 + .../roles/profile_atlas/templates/smb.conf.j2 | 16 ++ ansible/site.yml | 18 +- 22 files changed, 981 insertions(+), 4 deletions(-) create mode 100644 ansible/inventory/group_vars/platform_rocky.yml create mode 100644 ansible/inventory/host_vars/atlas.yml create mode 100644 ansible/roles/packages_rocky/tasks/main.yml create mode 100644 ansible/roles/profile_atlas/defaults/main.yml create mode 100644 ansible/roles/profile_atlas/handlers/main.yml create mode 100644 ansible/roles/profile_atlas/tasks/account.yml create mode 100644 ansible/roles/profile_atlas/tasks/cockpit.yml create mode 100644 ansible/roles/profile_atlas/tasks/main.yml create mode 100644 ansible/roles/profile_atlas/tasks/sharing.yml create mode 100644 ansible/roles/profile_atlas/tasks/storage.yml create mode 100644 ansible/roles/profile_atlas/tasks/syncthing.yml create mode 100644 ansible/roles/profile_atlas/templates/00-atlas-hardening.conf.j2 create mode 100644 ansible/roles/profile_atlas/templates/atlas-nfs.conf.j2 create mode 100644 ansible/roles/profile_atlas/templates/atlas-syncthing.service.j2 create mode 100644 ansible/roles/profile_atlas/templates/atlas.exports.j2 create mode 100644 ansible/roles/profile_atlas/templates/nfs-mountd-v4only.conf.j2 create mode 100644 ansible/roles/profile_atlas/templates/smb.conf.j2 diff --git a/AGENTS.md b/AGENTS.md index c56009a..445c3c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS - Void desktop profile is also the base for other future/reference hosts via `platform_void + graphical_desktop` - Workstation: `deadalus` is Windows + Fedora WSL. - Ubuntu server: `prometheus` +- NAS: `atlas` (Rocky Linux 9, reached through SSH) - Hosts intentionally belong to multiple groups; trust `ansible/site.yml` over hostname assumptions. - Inventory axes are independent: `platform_*`, `role_*`, and `desktop_*`. Legacy `void` and `desktop` remain compatibility parents. @@ -40,6 +41,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, FreeBS - Fedora laptop work: `ansible-playbook ansible/site.yml --limit nymph --check --diff` - WSL workstation dev: `ansible-playbook ansible/site.yml --limit deadalus --check --diff` - Server: `ansible-playbook ansible/site.yml --limit prometheus --check --diff` + - Atlas NAS: `ansible-playbook ansible/site.yml --limit atlas --check --diff` - Focused checks: - Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit --tags emacs --check --diff -e emacs_enabled=true` - Mail bootstrap: `sh -n scripts/bootstrap_mail.sh` and `shellcheck scripts/bootstrap_mail.sh` @@ -83,6 +85,46 @@ The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-i - Fedora WSL belongs to `platform_fedora`, `workstation_dev_fedora`, and the shared WSL layer. It must not receive Flatpak or Snap runtimes. - Windows applications are installed manually and are not managed from the WSL profile. +## Atlas NAS Notes +- `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=`; + subsequent runs use the dedicated Atlas account. +- The pool is pre-existing: never add pool creation, disk partitioning, RAIDZ creation, rollback, + or destruction to the Atlas profile. +- `atlas_manage_storage` and `atlas_manage_firewall` remain false until their placeholders are + replaced; only then may the profile manage datasets, shares and LAN-restricted firewall rules. +- Atlas requires `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash` for Cockpit + and, when storage is enabled, `vault_atlas_samba_password`. Never print these values. +- Atlas uses NFSv4 for Linux and SMB for Windows/WSL, restricted to the configured LAN. Snapshot, + Rclone, Prometheus pull and USB backup automation are intentionally deferred. + +## Atlas NAS TODO +- Replace every Atlas `CHANGEME` value, provide the required Vault variables and validate the first + remote bootstrap on the real Rocky Linux 9 host. Enable `atlas_manage_storage` first and + `atlas_manage_firewall` only after confirming the pool, mountpoints, LAN subnet and firewalld zone. +- 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. +- Finalize dataset properties and the shared UID/GID, group and POSIX ACL model; test the same files + through both NFS and SMB before considering multiprotocol access complete. +- Add Ansible-managed ZFS snapshot retention and scrub timers. Use Cockpit Scheduler for visibility + or manual operations, not as the only source of configuration, and never automate snapshot rollback. +- Manage the Syncthing star topology, device IDs, folders, folder modes, ignore rules and protected GUI + or API access for the selected clients. +- Add Tailscale or WireGuard and corresponding LAN/VPN-only firewalld rules before enabling remote + services; 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, + pinned host key, atomic pull, verification, retention and an Atlas systemd service/timer. +- Add the encrypted Google Drive backup with Rclone Drive plus Crypt remotes, Vault-managed secrets, + snapshot-consistent sources, retries, logging, retention policy and a tested restore procedure. +- Add the UUID-bound offline USB backup with versioned rsync, locking, capacity checks, verification, + safe unmounting and a tested restore procedure; never trigger it for an arbitrary USB disk. +- Add monitoring and alerting for pool health, scrub/resilver, SMART data, temperatures, free space and + failed backup timers, plus a controlled Rocky kernel/OpenZFS update and reboot procedure. +- Document and test disaster recovery: rebuild Atlas with Ansible, import the existing pool, restore + from snapshot/USB/cloud, preserve Vault and Rclone recovery material offline, and define RPO/RTO. +- Optionally design iCloud photo ingestion as a separate workflow after the storage and backup layers + are validated; do not make it a dependency of the Atlas baseline. + ## Coding Agent Notes - Shared agent packages live in `ai_agents_npm_packages` in `ansible/inventory/group_vars/all.yml`. - Shared agent dotfiles live in `ai_agents_dotfiles`; rendered configs live in `ai_agents_templates`. diff --git a/README.it.md b/README.it.md index 36b3b8d..c64ba44 100644 --- a/README.it.md +++ b/README.it.md @@ -56,8 +56,8 @@ Il repository è diviso in due componenti principali: # Macchine gestite -Il repository modella attualmente host Fedora/GNOME, una workstation Fedora WSL e un server Ubuntu. -La composizione resta separata in assi indipendenti: +Il repository modella attualmente host Fedora/GNOME, una workstation Fedora WSL, un server Ubuntu e +un NAS Rocky Linux 9. La composizione resta separata in assi indipendenti: ```text common user environment @@ -75,6 +75,7 @@ Matrice target: | nymph | Fedora | Desktop laptop | GNOME | | deadalus | Fedora WSL | Workstation dev | — | | prometheus | Ubuntu | Server | — | +| atlas | Rocky 9 | NAS | — | Regola operativa: @@ -93,6 +94,7 @@ Nota sullo stato attuale del playbook principale: - `ansible/site.yml` applica oggi in automatico Fedora/GNOME su `ikaros` e `nymph` - `ansible/site.yml` applica il profilo Fedora WSL alla workstation `deadalus` - `ansible/site.yml` applica anche il profilo `ubuntu_server` con baseline apt, systemd, dotfiles server e firewall UFW +- `ansible/site.yml` applica il profilo NAS Rocky su `atlas` tramite SSH remoto ## Desktop @@ -200,6 +202,33 @@ ansible-playbook ansible/site.yml --limit prometheus -e server_username=myuser - --- +## NAS + +`atlas` e un NAS Rocky Linux 9 raggiunto tramite SSH. Il pool ZFS esiste gia: il profilo gestisce +solo i dataset figli e non deve mai creare, partizionare, distruggere, fare rollback o modificare il +pool. I client Linux usano NFSv4, quelli Windows/WSL SMB; entrambi restano limitati alla LAN +configurata. + +Per il primo avvio sostituire i placeholder Atlas e fornire +`vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash` e +`vault_atlas_samba_password`. Eseguire il bootstrap tramite l'amministratore esistente: + +```bash +ansible-playbook ansible/site.yml --limit atlas \ + -e atlas_connection_username= +``` + +`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 +`atlas_manage_storage` solo dopo aver verificato pool e mountpoint esistenti; abilitare +`atlas_manage_firewall` solo dopo aver verificato subnet LAN e zona firewalld attiva. + +Restano da implementare retention delle snapshot, topologia Syncthing, VPN, pull da Prometheus, +Rclone, backup USB, monitoraggio e test di disaster recovery. Il backlog operativo dettagliato e in +`AGENTS.md`. + +--- + # Composizione della configurazione Emacs è abilitato sui profili Fedora/GNOME e workstation; la configurazione canonica è distribuita da `dotfiles_common`, con Org in `~/Org/`, template versionati e export PDF/HTML/Markdown/DOCX/ODT. Per abilitarlo temporaneamente su un altro profilo: @@ -247,6 +276,7 @@ I principali ruoli attualmente presenti sono: | packages_freebsd | installazione pacchetti su FreeBSD via pkg | | packages_ubuntu | installazione pacchetti su Ubuntu | | packages_fedora | installazione pacchetti su Fedora | +| packages_rocky | installazione pacchetti su Rocky Linux 9 | | services_runit | gestione servizi runit | | services_systemd | gestione servizi systemd | | services_freebsd | gestione servizi FreeBSD dichiarati per host | @@ -259,6 +289,7 @@ I principali ruoli attualmente presenti sono: | profile_workstation_dev_common | configurazione dev workstation condivisa | | profile_workstation_dev_wsl | configurazione WSL condivisa per sviluppo | | profile_server | configurazione server | +| profile_atlas | configurazione NAS Rocky Linux 9 | | dotfiles_common | distribuzione dotfiles comuni | | dotfiles | distribuzione configurazioni utente | @@ -274,6 +305,8 @@ platform_void -> packages_void + services_runit platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host platform_freebsd -> packages_freebsd + services_freebsd platform_fedora -> packages_fedora + services_systemd +platform_rocky -> packages_rocky + services_systemd +atlas -> profile_atlas platform_fedora & role_personal_workstation -> profile_personal_workstation platform_fedora & desktop_gnome -> profile_desktop_gnome workstation_dev_fedora -> profile_workstation_dev_common @@ -288,6 +321,7 @@ Questo significa che, allo stato attuale: - il profilo Void resta selezionabile tramite `platform_void + graphical_desktop` per host futuri - `deadalus` riceve il profilo Fedora WSL tramite play dev dedicati - il server Ubuntu (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewall +- il NAS Rocky (`atlas`) usa un pool ZFS gia esistente, condivisioni NFSv4/SMB limitate alla LAN e Cockpit/45Drives - lo stack container server include `navidrome`, `postgres`, `gitea`, `nginx-proxy-manager` e `syncthing`, con GUI Syncthing raggiungibile tramite la rete Docker `web` # Dotfiles @@ -364,6 +398,7 @@ Allo stato attuale questo comando: - per `platform_fedora & desktop_gnome` applica il profilo GNOME a `ikaros` e `nymph` - per `workstation_dev_wsl` applica i tweak WSL dopo il layer Fedora a `deadalus`, escludendo Flatpak e Snap - per gli host `ubuntu_server` applica pacchetti Ubuntu, servizi systemd, profilo server, UFW, dotfiles e template dedicati +- per `platform_rocky` applica pacchetti Rocky e servizi systemd ad `atlas`, quindi il profilo NAS dedicato - non riavvia automaticamente il display manager - carica `secrets/vault.yml` solo se presente - carica `secrets/vault.local.yml` solo se presente, dopo `vault.yml`, cosi gli override locali hanno precedenza @@ -377,6 +412,7 @@ ansible-playbook ansible/site.yml --limit ikaros --check --diff ansible-playbook ansible/site.yml --limit nymph --check --diff ansible-playbook ansible/site.yml --limit deadalus --check --diff ansible-playbook ansible/site.yml --limit prometheus --check --diff +ansible-playbook ansible/site.yml --limit atlas --check --diff ansible-lint ansible/site.yml ansible-lint ansible/roles yamllint ansible/ diff --git a/README.md b/README.md index db12185..dece3b5 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ infra/ ## Managed machines -The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, and an Ubuntu server. Configuration is layered instead of being tied to host names: +The repo currently covers Fedora/GNOME desktops, one Fedora WSL workstation, an Ubuntu server, and +a Rocky Linux 9 NAS. Configuration is layered instead of being tied to host names: ```text common user environment @@ -52,6 +53,7 @@ common user environment | `nymph` | Fedora | Desktop laptop | GNOME | | `deadalus` | Fedora WSL | Development workstation | — | | `prometheus` | Ubuntu | Server | — | +| `atlas` | Rocky 9 | NAS | — | ```text ikaros must be boring @@ -110,6 +112,31 @@ ansible-playbook ansible/site.yml --limit prometheus \ -e server_user_home=/srv/myuser ``` +## NAS + +`atlas` is a Rocky Linux 9 NAS reached through SSH. Its pool already exists: the profile only +manages child datasets and must never create, partition, destroy, roll back, or otherwise alter the +pool itself. Linux clients use NFSv4 and Windows/WSL clients use SMB; both are restricted to the +configured LAN. + +For the first run, replace the Atlas placeholders and provide +`vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash`, and +`vault_atlas_samba_password`. Bootstrap the host through its existing administrator: + +```bash +ansible-playbook ansible/site.yml --limit atlas \ + -e atlas_connection_username= +``` + +`vault_atlas_admin_password_hash` must be an `/etc/shadow`-compatible hash, not a clear-text +Cockpit password. Subsequent runs use `atlas_admin_username`. Enable +`atlas_manage_storage` only after checking the existing pool and mountpoints; enable +`atlas_manage_firewall` only after checking the LAN subnet and active firewalld zone. + +Snapshot retention, Syncthing topology, VPN access, Prometheus pulls, Rclone, USB backup, +monitoring, and disaster-recovery tests remain follow-up work. The detailed operational backlog is +kept in `AGENTS.md`. + ## How layering works A host can intentionally belong to more than one inventory group. The final configuration is the combination of the host and its groups, not a one-host/one-play mapping. @@ -146,6 +173,7 @@ ansible-playbook ansible/site.yml --limit --tags emacs -e emacs_enabled=t | `packages_freebsd` | Installs packages on FreeBSD with pkg. | | `packages_ubuntu` | Installs packages on Ubuntu. | | `packages_fedora` | Installs packages on Fedora. | +| `packages_rocky` | Installs packages on Rocky Linux 9. | | `services_runit` | Manages runit services. | | `services_systemd` | Manages systemd services. | | `services_freebsd` | Manages declared FreeBSD rc services. | @@ -158,6 +186,7 @@ ansible-playbook ansible/site.yml --limit --tags emacs -e emacs_enabled=t | `profile_workstation_dev_common` | Shared workstation development setup. | | `profile_workstation_dev_wsl` | WSL development setup. | | `profile_server` | Server setup. | +| `profile_atlas` | Rocky Linux 9 NAS setup. | | `dotfiles_common` | Shared user dotfiles. | ## What `site.yml` runs @@ -168,6 +197,8 @@ platform_void -> packages_void + services_runit platform_void & graphical_desktop -> profile_desktop_common + profile_desktop_sway + profile_desktop_niri + profile_desktop_host platform_freebsd -> packages_freebsd + services_freebsd platform_fedora -> packages_fedora + services_systemd +platform_rocky -> packages_rocky + services_systemd +atlas -> profile_atlas platform_fedora & role_personal_workstation -> profile_personal_workstation platform_fedora & desktop_gnome -> profile_desktop_gnome workstation_dev_fedora -> profile_workstation_dev_common @@ -180,6 +211,7 @@ So, in practice: - `platform_fedora` configures `ikaros`, `nymph`, and `deadalus`. - `deadalus` gets the Fedora development layer followed by the WSL layer. - `ubuntu_server` configures `prometheus`. +- `atlas` receives the Rocky platform layer and the NAS profile through SSH. - Empty `platform_void` and `platform_freebsd` groups do nothing until they get a host. - The playbook never restarts the display manager during a run. - `secrets/vault.yml` and then `secrets/vault.local.yml` are loaded only when present. @@ -218,6 +250,7 @@ ansible-playbook ansible/site.yml --limit ikaros --check --diff ansible-playbook ansible/site.yml --limit nymph --check --diff ansible-playbook ansible/site.yml --limit deadalus --check --diff ansible-playbook ansible/site.yml --limit prometheus --check --diff +ansible-playbook ansible/site.yml --limit atlas --check --diff ansible-lint ansible/site.yml ansible-lint ansible/roles yamllint ansible/ diff --git a/ansible/inventory/group_vars/platform_rocky.yml b/ansible/inventory/group_vars/platform_rocky.yml new file mode 100644 index 0000000..3ea84da --- /dev/null +++ b/ansible/inventory/group_vars/platform_rocky.yml @@ -0,0 +1,32 @@ +--- +platform_profile: rocky +platform_family: redhat +platform_package_manager: dnf +platform_service_manager: systemd + +rocky_openzfs_release_rpm: https://zfsonlinux.org/epel/zfs-release-3-0.el9.noarch.rpm +rocky_syncthing_version: 2.1.3 +rocky_syncthing_archive_checksum: sha256:f929eb8e5b72a85543eeeefb2c38f34a68e0c530e70758a2905b78840c76602c +rocky_syncthing_archive_url: >- + {{ + 'https://github.com/syncthing/syncthing/releases/download/v' + ~ rocky_syncthing_version + ~ '/syncthing-linux-amd64-v' + ~ rocky_syncthing_version + ~ '.tar.gz' + }} + +enabled_services: + - firewalld + +rocky_packages_base: + - cockpit + - dnf-plugins-core + - firewalld + - nfs-utils + - openssh-server + - policycoreutils-python-utils + - rsync + - samba + - sudo + - zfs diff --git a/ansible/inventory/host_vars/atlas.yml b/ansible/inventory/host_vars/atlas.yml new file mode 100644 index 0000000..a168937 --- /dev/null +++ b/ansible/inventory/host_vars/atlas.yml @@ -0,0 +1,50 @@ +--- +# Replace every CHANGEME value before enabling Atlas storage management. +hostname: atlas +ansible_host: CHANGEME_ATLAS_HOST +ansible_user: "{{ atlas_connection_username }}" +ansible_port: 22 +ansible_ssh_private_key_file: CHANGEME_ATLAS_SSH_PRIVATE_KEY_PATH + +atlas_admin_username: CHANGEME_ATLAS_ADMIN +atlas_connection_username: "{{ atlas_admin_username }}" +atlas_admin_group: "{{ atlas_admin_username }}" +atlas_admin_home: "/home/{{ atlas_admin_username }}" +atlas_admin_uid: 1000 +atlas_admin_gid: 1000 +atlas_admin_ssh_keys: "{{ vault_atlas_authorized_ssh_keys | default([]) }}" +atlas_admin_password_hash: "{{ vault_atlas_admin_password_hash | default('!') }}" + +atlas_lan_subnet: CHANGEME_LAN_SUBNET +atlas_manage_firewall: false +atlas_firewalld_zone: public +atlas_zfs_pool: CHANGEME_ZFS_POOL +atlas_zfs_dataset_work: work +atlas_zfs_dataset_syncthing: syncthing +atlas_zfs_dataset_backup_prometheus: backup_prometheus +atlas_zfs_dataset_icloud_photos: icloud_photos +atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT +atlas_manage_storage: false + +atlas_nfs_exports: + - path: "{{ atlas_work_mountpoint }}" + clients: "{{ atlas_lan_subnet }}(rw,sync,no_subtree_check,root_squash)" + +atlas_samba_share_name: work +atlas_samba_workgroup: WORKGROUP +atlas_samba_valid_users: + - "{{ atlas_admin_username }}" +atlas_samba_password: "{{ vault_atlas_samba_password | default('') }}" + +atlas_firewalld_rich_rules: + - 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="ssh" accept' + - 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="cockpit" accept' + - 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" port port="2049" protocol="tcp" accept' + - 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="samba" accept' + +host_enabled_services: + - sshd + - cockpit.socket + - nfs-server + - smb + - zfs.target diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index e2beee9..e933605 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -16,6 +16,10 @@ all: platform_freebsd: hosts: {} + platform_rocky: + hosts: + atlas: + void: children: platform_void: @@ -24,6 +28,10 @@ all: children: platform_fedora: + rocky: + children: + platform_rocky: + graphical_desktop: hosts: ikaros: diff --git a/ansible/roles/packages_rocky/tasks/main.yml b/ansible/roles/packages_rocky/tasks/main.yml new file mode 100644 index 0000000..b6a456c --- /dev/null +++ b/ansible/roles/packages_rocky/tasks/main.yml @@ -0,0 +1,151 @@ +--- +- name: Require Rocky Linux 9 + tags: [packages] + ansible.builtin.assert: + that: + - ansible_facts['distribution'] == 'Rocky' + - ansible_facts['distribution_major_version'] == '9' + fail_msg: This platform role supports Rocky Linux 9 only. + +- name: Install DNF configuration plugin + tags: [packages] + ansible.builtin.dnf: + name: dnf-plugins-core + state: present + +- name: Enable Rocky CodeReady Builder repository + tags: [packages] + community.general.dnf_config_manager: + name: + - crb + state: enabled + when: rocky_enable_crb | default(true) | bool + +- name: Install EPEL repository package + tags: [packages] + ansible.builtin.dnf: + name: epel-release + state: present + +- name: Install official OpenZFS repository package + tags: [packages, storage] + ansible.builtin.dnf: + name: "{{ rocky_openzfs_release_rpm }}" + state: present + +- name: Disable OpenZFS DKMS repository + tags: [packages, storage] + community.general.dnf_config_manager: + name: + - zfs + state: disabled + +- name: Enable OpenZFS kmod repository + tags: [packages, storage] + community.general.dnf_config_manager: + name: + - zfs-kmod + state: enabled + +- name: Refresh Rocky package metadata + tags: [packages] + ansible.builtin.dnf: + update_cache: true + +- name: Install packages on Rocky Linux + tags: [packages] + ansible.builtin.dnf: + name: >- + {{ + ( + (common_packages | default([])) + + (rocky_packages_base | default([])) + + (profile_packages | default([])) + + (host_packages | default([])) + ) | unique + }} + state: present + +- name: Require supported architecture for Syncthing + tags: [packages, syncthing] + ansible.builtin.assert: + that: + - ansible_facts['architecture'] == 'x86_64' + fail_msg: The pinned Atlas Syncthing binary currently supports x86_64 only. + +- name: Read installed Syncthing version + tags: [packages, syncthing] + ansible.builtin.command: + argv: + - /usr/local/bin/syncthing + - --version + register: rocky_syncthing_version_check + changed_when: false + failed_when: false + check_mode: false + +- name: Determine whether Syncthing must be installed + tags: [packages, syncthing] + ansible.builtin.set_fact: + rocky_syncthing_install_required: >- + {{ + ('syncthing v' ~ rocky_syncthing_version ~ ' ') + not in (rocky_syncthing_version_check.stdout | default('')) + }} + +- name: Create temporary Syncthing extraction directory + tags: [packages, syncthing] + ansible.builtin.file: + path: "/tmp/syncthing-{{ rocky_syncthing_version }}" + state: directory + owner: root + group: root + mode: "0755" + when: rocky_syncthing_install_required | bool + +- name: Download pinned Syncthing release + tags: [packages, syncthing] + ansible.builtin.get_url: + url: "{{ rocky_syncthing_archive_url }}" + dest: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz" + checksum: "{{ rocky_syncthing_archive_checksum }}" + owner: root + group: root + mode: "0644" + when: rocky_syncthing_install_required | bool + +- name: Extract pinned Syncthing release + tags: [packages, syncthing] + ansible.builtin.unarchive: + src: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz" + dest: "/tmp/syncthing-{{ rocky_syncthing_version }}" + remote_src: true + when: rocky_syncthing_install_required | bool + +- name: Install pinned Syncthing binary + tags: [packages, syncthing] + ansible.builtin.copy: + src: >- + {{ + '/tmp/syncthing-' ~ rocky_syncthing_version + ~ '/syncthing-linux-amd64-v' ~ rocky_syncthing_version + ~ '/syncthing' + }} + dest: /usr/local/bin/syncthing + remote_src: true + owner: root + group: root + mode: "0755" + when: rocky_syncthing_install_required | bool + +- name: Remove Syncthing release archive + tags: [packages, syncthing] + ansible.builtin.file: + path: "/tmp/syncthing-{{ rocky_syncthing_version }}.tar.gz" + state: absent + +- name: Remove Syncthing extraction directory + tags: [packages, syncthing] + ansible.builtin.file: + path: "/tmp/syncthing-{{ rocky_syncthing_version }}" + state: absent diff --git a/ansible/roles/profile_atlas/defaults/main.yml b/ansible/roles/profile_atlas/defaults/main.yml new file mode 100644 index 0000000..558889e --- /dev/null +++ b/ansible/roles/profile_atlas/defaults/main.yml @@ -0,0 +1,51 @@ +--- +atlas_manage_storage: false +atlas_admin_username: CHANGEME_ATLAS_ADMIN +atlas_admin_group: "{{ atlas_admin_username }}" +atlas_admin_home: "/home/{{ atlas_admin_username }}" +atlas_admin_uid: 1000 +atlas_admin_gid: 1000 +atlas_admin_ssh_keys: [] +atlas_admin_password_hash: "!" + +atlas_lan_subnet: CHANGEME_LAN_SUBNET +atlas_manage_firewall: false +atlas_firewalld_zone: public +atlas_zfs_pool: CHANGEME_ZFS_POOL +atlas_zfs_dataset_work: work +atlas_zfs_dataset_syncthing: syncthing +atlas_zfs_dataset_backup_prometheus: backup_prometheus +atlas_zfs_dataset_icloud_photos: icloud_photos +atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT + +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_icloud_photos_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_icloud_photos }}" +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_file: /etc/yum.repos.d/45drives-enterprise.repo +atlas_45drives_packages: + - cockpit-file-sharing + - cockpit-navigator + - cockpit-identities + - cockpit-zfs + - cockpit-scheduler + +atlas_nfs_exports: [] +atlas_samba_share_name: work +atlas_samba_workgroup: WORKGROUP +atlas_samba_valid_users: [] +atlas_samba_password: "" +atlas_samba_password_marker: /var/lib/samba/private/.atlas-password.sha256 +atlas_firewalld_rich_rules: [] +atlas_firewalld_restricted_services: + - ssh + - cockpit + - nfs + - samba +atlas_selinux_booleans: + - samba_export_all_rw + - nfs_export_all_rw diff --git a/ansible/roles/profile_atlas/handlers/main.yml b/ansible/roles/profile_atlas/handlers/main.yml new file mode 100644 index 0000000..089f865 --- /dev/null +++ b/ansible/roles/profile_atlas/handlers/main.yml @@ -0,0 +1,34 @@ +--- +- name: Reload SSH service + ansible.builtin.systemd: + name: sshd + state: reloaded + +- name: Reload NFS exports + ansible.builtin.command: + argv: + - exportfs + - -ra + changed_when: true + when: not ansible_check_mode + +- name: Restart NFS server + ansible.builtin.systemd: + name: nfs-server + state: restarted + +- name: Restart NFS mount daemon + ansible.builtin.systemd: + name: nfs-mountd + state: restarted + daemon_reload: true + +- name: Restart Samba service + ansible.builtin.systemd: + name: smb + state: restarted + +- name: Restart Atlas Syncthing service + ansible.builtin.systemd: + name: atlas-syncthing + state: restarted diff --git a/ansible/roles/profile_atlas/tasks/account.yml b/ansible/roles/profile_atlas/tasks/account.yml new file mode 100644 index 0000000..242ab08 --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/account.yml @@ -0,0 +1,118 @@ +--- +- name: Reject incomplete Atlas account configuration + tags: [atlas, services] + ansible.builtin.assert: + that: + - atlas_admin_username != 'CHANGEME_ATLAS_ADMIN' + - (atlas_admin_ssh_keys | default([])) | length > 0 + - atlas_admin_password_hash not in ['', '!', '*'] + fail_msg: >- + Define atlas_admin_username, vault_atlas_authorized_ssh_keys and + vault_atlas_admin_password_hash before applying the Atlas profile. + no_log: true + +- name: Create Atlas administrator group + tags: [atlas, services] + ansible.builtin.group: + name: "{{ atlas_admin_group }}" + gid: "{{ atlas_admin_gid }}" + state: present + +- name: Create Atlas administrator account + tags: [atlas, services] + ansible.builtin.user: + name: "{{ atlas_admin_username }}" + uid: "{{ atlas_admin_uid }}" + group: "{{ atlas_admin_group }}" + home: "{{ atlas_admin_home }}" + shell: /bin/bash + password: "{{ atlas_admin_password_hash }}" + create_home: true + state: present + no_log: true + +- name: Grant Atlas administrator passwordless sudo + tags: [atlas, services] + ansible.builtin.copy: + content: "{{ atlas_admin_username }} ALL=(ALL) NOPASSWD: ALL\n" + dest: "/etc/sudoers.d/{{ atlas_admin_username }}" + owner: root + group: root + mode: "0440" + validate: "visudo -cf %s" + +- name: Manage Atlas administrator authorized SSH keys exclusively + tags: [atlas, services] + ansible.posix.authorized_key: + user: "{{ atlas_admin_username }}" + key: "{{ atlas_admin_ssh_keys | join('\n') }}" + state: present + exclusive: true + +- name: Check whether the Atlas SSH host key exists + tags: [atlas, services] + ansible.builtin.stat: + path: /etc/ssh/ssh_host_ed25519_key + register: atlas_ssh_host_ed25519_key + +- name: Generate missing Atlas SSH host keys + tags: [atlas, services] + ansible.builtin.command: + argv: + - ssh-keygen + - -A + changed_when: true + when: not atlas_ssh_host_ed25519_key.stat.exists + +- name: Ensure Atlas SSH configuration drop-in directory exists + tags: [atlas, services] + ansible.builtin.file: + path: /etc/ssh/sshd_config.d + state: directory + owner: root + group: root + mode: "0755" + +- name: Ensure Atlas SSH drop-ins are loaded before other settings + tags: [atlas, services] + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf\s*$' + line: Include /etc/ssh/sshd_config.d/*.conf + insertbefore: BOF + state: present + validate: "sshd -t -f %s" + +- name: Render Atlas SSH hardening drop-in + tags: [atlas, services] + ansible.builtin.template: + src: 00-atlas-hardening.conf.j2 + dest: /etc/ssh/sshd_config.d/00-atlas-hardening.conf + owner: root + group: root + mode: "0600" + notify: Reload SSH service + +- name: Read effective Atlas SSH daemon configuration + tags: [atlas, services] + ansible.builtin.command: + argv: + - sshd + - -T + - -C + - "user={{ atlas_admin_username }},host=atlas,addr=127.0.0.1" + register: atlas_sshd_effective_configuration + changed_when: false + when: not ansible_check_mode + +- name: Verify effective Atlas SSH hardening + tags: [atlas, services] + ansible.builtin.assert: + that: + - "'permitrootlogin no' 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" + - "'kbdinteractiveauthentication no' 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. + when: not ansible_check_mode diff --git a/ansible/roles/profile_atlas/tasks/cockpit.yml b/ansible/roles/profile_atlas/tasks/cockpit.yml new file mode 100644 index 0000000..1b6f486 --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/cockpit.yml @@ -0,0 +1,15 @@ +--- +- name: Configure the official 45Drives repository + tags: [atlas, packages] + ansible.builtin.get_url: + url: "{{ atlas_45drives_repo_url }}" + dest: "{{ atlas_45drives_repo_file }}" + owner: root + group: root + mode: "0644" + +- name: Install 45Drives Cockpit plugins + tags: [atlas, packages] + ansible.builtin.dnf: + name: "{{ atlas_45drives_packages }}" + state: present diff --git a/ansible/roles/profile_atlas/tasks/main.yml b/ansible/roles/profile_atlas/tasks/main.yml new file mode 100644 index 0000000..0ed92c4 --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Import Atlas account tasks + ansible.builtin.import_tasks: account.yml + +- name: Import Atlas 45Drives tasks + ansible.builtin.import_tasks: cockpit.yml + +- name: Import Atlas storage tasks + ansible.builtin.import_tasks: storage.yml + +- name: Import Atlas file sharing tasks + ansible.builtin.import_tasks: sharing.yml + +- name: Import Atlas Syncthing tasks + ansible.builtin.import_tasks: syncthing.yml diff --git a/ansible/roles/profile_atlas/tasks/sharing.yml b/ansible/roles/profile_atlas/tasks/sharing.yml new file mode 100644 index 0000000..913fa29 --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/sharing.yml @@ -0,0 +1,211 @@ +--- +- 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: 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 Atlas Samba password + tags: [atlas, sharing] + ansible.builtin.assert: + that: + - atlas_samba_password | length > 0 + fail_msg: Define vault_atlas_samba_password before enabling Atlas storage. + no_log: true + when: atlas_manage_storage | bool + +- name: Read Atlas Samba account + tags: [atlas, sharing] + ansible.builtin.command: + argv: + - pdbedit + - --list + - --user + - "{{ atlas_admin_username }}" + register: atlas_samba_account + changed_when: false + failed_when: false + when: atlas_manage_storage | bool + +- name: Ensure Atlas Samba private state directory exists + tags: [atlas, sharing] + ansible.builtin.file: + path: "{{ atlas_samba_password_marker | dirname }}" + state: directory + owner: root + group: root + mode: "0700" + when: atlas_manage_storage | bool + +- name: Check Atlas Samba password marker + tags: [atlas, sharing] + ansible.builtin.stat: + path: "{{ atlas_samba_password_marker }}" + register: atlas_samba_password_marker_stat + when: atlas_manage_storage | bool + +- name: Read Atlas Samba password marker + tags: [atlas, sharing] + ansible.builtin.slurp: + src: "{{ atlas_samba_password_marker }}" + register: atlas_samba_password_marker_content + no_log: true + when: + - atlas_manage_storage | bool + - atlas_samba_password_marker_stat.stat.exists + +- name: Determine whether Atlas Samba credentials must change + tags: [atlas, sharing] + ansible.builtin.set_fact: + atlas_samba_password_digest: "{{ atlas_samba_password | hash('sha256') }}" + atlas_samba_password_update_required: >- + {{ + atlas_samba_account.rc != 0 + or not atlas_samba_password_marker_stat.stat.exists + or ( + atlas_samba_password_marker_content.content | default('') + | b64decode | trim + ) != (atlas_samba_password | hash('sha256')) + }} + no_log: true + when: atlas_manage_storage | bool + +- name: Set Atlas Samba account password + tags: [atlas, sharing] + ansible.builtin.command: + argv: + - smbpasswd + - -s + - -a + - "{{ atlas_admin_username }}" + stdin: "{{ atlas_samba_password }}\n{{ atlas_samba_password }}" + changed_when: true + no_log: true + when: + - atlas_manage_storage | bool + - atlas_samba_password_update_required | bool + +- name: Record managed Atlas Samba password digest + tags: [atlas, sharing] + ansible.builtin.copy: + content: "{{ atlas_samba_password_digest }}\n" + dest: "{{ atlas_samba_password_marker }}" + owner: root + group: root + mode: "0600" + no_log: true + when: + - atlas_manage_storage | bool + - atlas_samba_password_update_required | bool + +- name: Require completed Atlas firewall placeholders + tags: [atlas, sharing, services] + ansible.builtin.assert: + that: + - atlas_lan_subnet != 'CHANGEME_LAN_SUBNET' + - atlas_firewalld_zone | length > 0 + fail_msg: Replace the Atlas LAN subnet and firewall zone placeholders. + when: atlas_manage_firewall | bool + +- name: Apply Atlas firewalld rich rules + tags: [atlas, sharing, services] + ansible.posix.firewalld: + rich_rule: "{{ item }}" + permanent: true + immediate: true + state: enabled + zone: "{{ atlas_firewalld_zone }}" + loop: "{{ atlas_firewalld_rich_rules }}" + loop_control: + label: "{{ item }}" + when: atlas_manage_firewall | bool + +- name: Remove unrestricted Atlas services from firewalld zone + tags: [atlas, sharing, services] + ansible.posix.firewalld: + service: "{{ item }}" + permanent: true + immediate: true + state: disabled + zone: "{{ atlas_firewalld_zone }}" + loop: "{{ atlas_firewalld_restricted_services }}" + loop_control: + label: "{{ item }}" + when: atlas_manage_firewall | bool diff --git a/ansible/roles/profile_atlas/tasks/storage.yml b/ansible/roles/profile_atlas/tasks/storage.yml new file mode 100644 index 0000000..fb783dc --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/storage.yml @@ -0,0 +1,77 @@ +--- +- name: Define Atlas ZFS datasets + tags: [atlas, storage] + ansible.builtin.set_fact: + atlas_zfs_datasets: + - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_work }}" + mountpoint: "{{ atlas_work_mountpoint }}" + - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_syncthing }}" + mountpoint: "{{ atlas_syncthing_mountpoint }}" + - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backup_prometheus }}" + mountpoint: "{{ atlas_backup_prometheus_mountpoint }}" + - name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_icloud_photos }}" + mountpoint: "{{ atlas_icloud_photos_mountpoint }}" + +- name: Require completed Atlas storage placeholders + tags: [atlas, storage] + ansible.builtin.assert: + that: + - atlas_zfs_pool != 'CHANGEME_ZFS_POOL' + - atlas_mount_root != '/CHANGEME_ATLAS_MOUNT_ROOT' + - atlas_mount_root.startswith('/') + - (atlas_zfs_datasets | map(attribute='name') | unique | list | length) == (atlas_zfs_datasets | length) + - (atlas_zfs_datasets | map(attribute='mountpoint') | unique | list | length) == (atlas_zfs_datasets | length) + fail_msg: >- + Replace the Atlas ZFS pool and mount-root placeholders, then set + atlas_manage_storage to true before managing datasets. + when: atlas_manage_storage | bool + +- name: Check whether the configured Atlas ZFS pool exists + tags: [atlas, storage] + ansible.builtin.command: + argv: + - zpool + - list + - -H + - -o + - name + - "{{ atlas_zfs_pool }}" + register: atlas_zfs_pool_check + changed_when: false + failed_when: false + when: atlas_manage_storage | bool + +- name: Require the configured Atlas ZFS pool + tags: [atlas, storage] + ansible.builtin.assert: + that: + - atlas_zfs_pool_check.rc == 0 + fail_msg: >- + The configured Atlas ZFS pool does not exist. The Atlas profile only + manages child datasets and never creates pools. + when: atlas_manage_storage | bool + +- name: Manage Atlas ZFS datasets declaratively + tags: [atlas, storage] + community.general.zfs: + name: "{{ item.name }}" + state: present + extra_zfs_properties: + mountpoint: "{{ item.mountpoint }}" + loop: "{{ atlas_zfs_datasets }}" + loop_control: + label: "{{ item.name }}" + when: atlas_manage_storage | bool + +- name: Set Atlas dataset ownership + tags: [atlas, storage] + ansible.builtin.file: + path: "{{ item.mountpoint }}" + state: directory + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "0770" + loop: "{{ atlas_zfs_datasets }}" + loop_control: + label: "{{ item.mountpoint }}" + when: atlas_manage_storage | bool diff --git a/ansible/roles/profile_atlas/tasks/syncthing.yml b/ansible/roles/profile_atlas/tasks/syncthing.yml new file mode 100644 index 0000000..f2f4c4e --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/syncthing.yml @@ -0,0 +1,40 @@ +--- +- 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 diff --git a/ansible/roles/profile_atlas/templates/00-atlas-hardening.conf.j2 b/ansible/roles/profile_atlas/templates/00-atlas-hardening.conf.j2 new file mode 100644 index 0000000..90c8386 --- /dev/null +++ b/ansible/roles/profile_atlas/templates/00-atlas-hardening.conf.j2 @@ -0,0 +1,6 @@ +# Managed by Ansible. Do not edit manually. +PermitRootLogin no +PubkeyAuthentication yes +PasswordAuthentication no +KbdInteractiveAuthentication no +AllowUsers {{ atlas_admin_username }} diff --git a/ansible/roles/profile_atlas/templates/atlas-nfs.conf.j2 b/ansible/roles/profile_atlas/templates/atlas-nfs.conf.j2 new file mode 100644 index 0000000..7f988b3 --- /dev/null +++ b/ansible/roles/profile_atlas/templates/atlas-nfs.conf.j2 @@ -0,0 +1,3 @@ +# Managed by Ansible. Do not edit manually. +[nfsd] +vers3 = n diff --git a/ansible/roles/profile_atlas/templates/atlas-syncthing.service.j2 b/ansible/roles/profile_atlas/templates/atlas-syncthing.service.j2 new file mode 100644 index 0000000..5506d26 --- /dev/null +++ b/ansible/roles/profile_atlas/templates/atlas-syncthing.service.j2 @@ -0,0 +1,16 @@ +[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 diff --git a/ansible/roles/profile_atlas/templates/atlas.exports.j2 b/ansible/roles/profile_atlas/templates/atlas.exports.j2 new file mode 100644 index 0000000..b99caef --- /dev/null +++ b/ansible/roles/profile_atlas/templates/atlas.exports.j2 @@ -0,0 +1,4 @@ +# Managed by Ansible. Do not edit manually. +{% for export in atlas_nfs_exports %} +{{ export.path }} {{ export.clients }} +{% endfor %} diff --git a/ansible/roles/profile_atlas/templates/nfs-mountd-v4only.conf.j2 b/ansible/roles/profile_atlas/templates/nfs-mountd-v4only.conf.j2 new file mode 100644 index 0000000..f1489ec --- /dev/null +++ b/ansible/roles/profile_atlas/templates/nfs-mountd-v4only.conf.j2 @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=/usr/sbin/rpc.mountd --no-tcp --no-udp diff --git a/ansible/roles/profile_atlas/templates/smb.conf.j2 b/ansible/roles/profile_atlas/templates/smb.conf.j2 new file mode 100644 index 0000000..2f9e23a --- /dev/null +++ b/ansible/roles/profile_atlas/templates/smb.conf.j2 @@ -0,0 +1,16 @@ +# Managed by Ansible. Do not edit manually. +[global] + workgroup = {{ atlas_samba_workgroup }} + security = user + map to guest = Never + server min protocol = SMB2 + include = registry + +[{{ atlas_samba_share_name }}] + path = {{ atlas_work_mountpoint }} + browseable = yes + read only = no + valid users = {{ atlas_samba_valid_users | join(' ') }} + force group = {{ atlas_admin_group }} + create mask = 0660 + directory mask = 0770 diff --git a/ansible/site.yml b/ansible/site.yml index 1cadc8f..4a1bd66 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -20,7 +20,8 @@ errors='ignore', wantlist=True) | length > 0 roles: - - dotfiles_common + - role: dotfiles_common + when: "'platform_rocky' not in group_names" - name: Configure Void platform hosts: platform_void @@ -56,6 +57,21 @@ - packages_fedora - services_systemd +- name: Configure Rocky Linux platform + hosts: platform_rocky + become: true + + roles: + - packages_rocky + - services_systemd + +- name: Configure Atlas NAS profile + hosts: atlas + become: true + + roles: + - profile_atlas + - name: Configure personal workstation role on Fedora hosts: platform_fedora:&role_personal_workstation become: true