mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 19:03:47 +00:00
Add Atlas media and storage services [Phase 1] (#9)
* Add Atlas media and storage services * Document Atlas backend phase one and WireGuard deployment * Enable Atlas NAS management and document bootstrap workflow * Harden Atlas network, SSH, firewall, and sharing * Rotate Ansible Vault secrets * Allow configurable Aegis SSH users and authorized keys * Manage Aegis SSH authorized key fragments * Manage SSH authorized key fragments for infrastructure hosts * Harden Rocky storage and sharing configuration * Verify WireGuard handshakes and restore Podman networking
This commit is contained in:
committed by
GitHub
parent
73bf2cd62a
commit
160d63c02d
@@ -1,29 +1,77 @@
|
||||
---
|
||||
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_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_ssh_key_directory: "{{ atlas_admin_home }}/.ssh/authorized_keys.d"
|
||||
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_group: immich
|
||||
atlas_immich_uid: 1100
|
||||
atlas_immich_gid: 1100
|
||||
atlas_immich_supplementary_groups:
|
||||
- video
|
||||
- render
|
||||
|
||||
atlas_lan_subnet: CHANGEME_LAN_SUBNET
|
||||
atlas_aegis_ip: CHANGEME_AEGIS_IP
|
||||
atlas_manage_firewall: false
|
||||
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_dataset_work: work
|
||||
atlas_zfs_dataset_syncthing: syncthing
|
||||
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_backup_prometheus: backup_prometheus
|
||||
atlas_zfs_dataset_icloud_photos: icloud_photos
|
||||
atlas_zfs_dataset_photobook: media/photobook
|
||||
atlas_zfs_dataset_backups: backups
|
||||
atlas_zfs_dataset_service_backups: backups/services
|
||||
atlas_zfs_service_backups_refreservation: 500G
|
||||
atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT
|
||||
|
||||
atlas_work_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_work }}"
|
||||
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_work_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_work }}"
|
||||
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_photobook_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_photobook }}"
|
||||
atlas_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_backups }}"
|
||||
atlas_service_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||
|
||||
atlas_45drives_repo_url: https://repo.45drives.com/repofiles/rocky/45drives-enterprise.repo
|
||||
atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo
|
||||
@@ -34,18 +82,61 @@ atlas_45drives_packages:
|
||||
- cockpit-zfs
|
||||
- cockpit-scheduler
|
||||
|
||||
atlas_nfs_exports: []
|
||||
atlas_samba_share_name: work
|
||||
atlas_nfs_exports:
|
||||
- path: "{{ atlas_photobook_mountpoint }}"
|
||||
client: "{{ atlas_aegis_ip }}"
|
||||
options:
|
||||
- rw
|
||||
- sync
|
||||
- no_subtree_check
|
||||
- all_squash
|
||||
- "anonuid={{ atlas_immich_uid }}"
|
||||
- "anongid={{ atlas_immich_gid }}"
|
||||
atlas_samba_share_name: Archive
|
||||
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_password: ""
|
||||
atlas_samba_password_marker: /var/lib/samba/private/.atlas-password.sha256
|
||||
atlas_samba_accounts: []
|
||||
atlas_samba_password_marker_dir: /var/lib/samba/private/ansible-passwords
|
||||
atlas_firewalld_rich_rules: []
|
||||
atlas_firewalld_restricted_services:
|
||||
- ssh
|
||||
- cockpit
|
||||
- nfs
|
||||
- samba
|
||||
- http
|
||||
- https
|
||||
atlas_selinux_booleans:
|
||||
- samba_export_all_rw
|
||||
- nfs_export_all_rw
|
||||
|
||||
atlas_manage_media_stack: false
|
||||
atlas_container_quadlet_dir: /etc/containers/systemd
|
||||
atlas_container_data_root: /var/lib/atlas-containers
|
||||
atlas_npm_data_dir: "{{ atlas_container_data_root }}/npm/data"
|
||||
atlas_npm_letsencrypt_dir: "{{ atlas_container_data_root }}/npm/letsencrypt"
|
||||
atlas_npm_admin_bind_address: 127.0.0.1
|
||||
atlas_timezone: Europe/Rome
|
||||
atlas_immich_upload_dir: "{{ atlas_container_data_root }}/immich/upload"
|
||||
atlas_immich_model_cache_dir: "{{ atlas_container_data_root }}/immich/model-cache"
|
||||
atlas_immich_machine_learning_config_dir: "{{ atlas_container_data_root }}/immich/machine-learning-config"
|
||||
atlas_immich_machine_learning_cache_dir: "{{ atlas_container_data_root }}/immich/machine-learning-cache"
|
||||
atlas_immich_redis_data_dir: "{{ atlas_container_data_root }}/immich/redis"
|
||||
atlas_immich_postgres_data_dir: "{{ atlas_container_data_root }}/immich/postgres"
|
||||
|
||||
atlas_npm_image: docker.io/jc21/nginx-proxy-manager:latest
|
||||
atlas_immich_version: release
|
||||
atlas_immich_server_image: "ghcr.io/immich-app/immich-server:{{ atlas_immich_version }}"
|
||||
atlas_immich_machine_learning_image: "ghcr.io/immich-app/immich-machine-learning:{{ atlas_immich_version }}-openvino"
|
||||
atlas_immich_redis_image: docker.io/valkey/valkey:9
|
||||
atlas_immich_postgres_image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
atlas_immich_db_username: postgres
|
||||
atlas_immich_db_name: immich
|
||||
atlas_immich_db_password: "{{ vault_atlas_immich_db_password | default('') }}"
|
||||
atlas_media_quadlet_services:
|
||||
- atlas-immich-redis.service
|
||||
- atlas-immich-postgres.service
|
||||
- atlas-immich-machine-learning.service
|
||||
- atlas-immich-server.service
|
||||
- atlas-npm.service
|
||||
|
||||
@@ -28,7 +28,14 @@
|
||||
name: smb
|
||||
state: restarted
|
||||
|
||||
- name: Restart Atlas Syncthing service
|
||||
- name: Restart Atlas media Quadlets
|
||||
ansible.builtin.systemd:
|
||||
name: atlas-syncthing
|
||||
name: "{{ item }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
loop: "{{ atlas_media_quadlet_services }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- atlas_manage_media_stack | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
- atlas_admin_username != 'CHANGEME_ATLAS_ADMIN'
|
||||
- (atlas_admin_ssh_keys | default([])) | length > 0
|
||||
- atlas_admin_password_hash not in ['', '!', '*']
|
||||
- atlas_ssh_allow_tcp_forwarding in ['no', 'yes', 'local', 'remote', 'all']
|
||||
- "'wheel' not in atlas_immich_supplementary_groups"
|
||||
fail_msg: >-
|
||||
Define atlas_admin_username, vault_atlas_authorized_ssh_keys and
|
||||
vault_atlas_admin_password_hash before applying the Atlas profile.
|
||||
Define atlas_admin_username, atlas_admin_ssh_keys and vault_atlas_admin_password_hash
|
||||
before applying the Atlas profile.
|
||||
no_log: true
|
||||
|
||||
- name: Create Atlas administrator group
|
||||
@@ -31,6 +33,41 @@
|
||||
state: present
|
||||
no_log: true
|
||||
|
||||
- name: Create Immich primary group
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.group:
|
||||
name: "{{ atlas_immich_group }}"
|
||||
gid: "{{ atlas_immich_gid }}"
|
||||
state: present
|
||||
|
||||
- name: Ensure Immich hardware-access groups exist
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.group:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ atlas_immich_supplementary_groups }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: Create unprivileged Immich account
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.user:
|
||||
name: "{{ atlas_immich_username }}"
|
||||
uid: "{{ atlas_immich_uid }}"
|
||||
group: "{{ atlas_immich_group }}"
|
||||
groups: "{{ atlas_immich_supplementary_groups }}"
|
||||
append: false
|
||||
home: /nonexistent
|
||||
create_home: false
|
||||
shell: /sbin/nologin
|
||||
system: true
|
||||
state: present
|
||||
|
||||
- name: Read Immich hardware-access group IDs
|
||||
tags: [atlas, accounts, containers, immich]
|
||||
ansible.builtin.getent:
|
||||
database: group
|
||||
|
||||
- name: Grant Atlas administrator passwordless sudo
|
||||
tags: [atlas, services]
|
||||
ansible.builtin.copy:
|
||||
@@ -41,13 +78,26 @@
|
||||
mode: "0440"
|
||||
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]
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ atlas_admin_username }}"
|
||||
key: "{{ atlas_admin_ssh_keys | join('\n') }}"
|
||||
state: present
|
||||
exclusive: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ atlas_admin_ssh_key_directory }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
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
|
||||
tags: [atlas, services]
|
||||
@@ -113,6 +163,18 @@
|
||||
- "'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"
|
||||
- "'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"
|
||||
fail_msg: The effective Atlas SSH configuration does not match the required hardening.
|
||||
when: not ansible_check_mode
|
||||
|
||||
140
ansible/roles/profile_atlas/tasks/containers.yml
Normal file
140
ansible/roles/profile_atlas/tasks/containers.yml
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
- name: Require completed Atlas media-stack configuration
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_manage_storage | bool
|
||||
- atlas_zfs_pool != 'CHANGEME_ZFS_POOL'
|
||||
- atlas_mount_root != '/CHANGEME_ATLAS_MOUNT_ROOT'
|
||||
- atlas_immich_db_password | length > 0
|
||||
- atlas_npm_admin_bind_address == '127.0.0.1'
|
||||
fail_msg: >-
|
||||
Enable and configure Atlas storage, provide vault_atlas_immich_db_password,
|
||||
and keep the NPM administration port bound to loopback before enabling the media stack.
|
||||
no_log: true
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Check Atlas Intel graphics device
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.stat:
|
||||
path: /dev/dri
|
||||
register: atlas_dri_device
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Require Atlas Intel graphics device
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_dri_device.stat.isdir | default(false)
|
||||
fail_msg: /dev/dri is required for Immich QuickSync and OpenVINO acceleration.
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas rootful Quadlet directory
|
||||
tags: [atlas, containers]
|
||||
ansible.builtin.file:
|
||||
path: "{{ atlas_container_quadlet_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas Immich configuration directory
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.file:
|
||||
path: /etc/immich
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ atlas_immich_group }}"
|
||||
mode: "0750"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas Immich persistent directories
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_immich_username }}"
|
||||
group: "{{ atlas_immich_group }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ atlas_immich_upload_dir }}"
|
||||
- "{{ atlas_immich_model_cache_dir }}"
|
||||
- "{{ atlas_immich_machine_learning_config_dir }}"
|
||||
- "{{ atlas_immich_machine_learning_cache_dir }}"
|
||||
- "{{ atlas_immich_redis_data_dir }}"
|
||||
- "{{ atlas_immich_postgres_data_dir }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Create Atlas NPM persistent directories
|
||||
tags: [atlas, containers, npm]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ atlas_npm_data_dir }}"
|
||||
- "{{ atlas_npm_letsencrypt_dir }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Allow confined containers to use Atlas graphics devices
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.posix.seboolean:
|
||||
name: container_use_devices
|
||||
state: true
|
||||
persistent: true
|
||||
when:
|
||||
- atlas_manage_media_stack | bool
|
||||
- (ansible_facts['selinux'] | default({})).get('status', 'disabled') == 'enabled'
|
||||
|
||||
- name: Render Vault-backed Immich environment
|
||||
tags: [atlas, containers, immich]
|
||||
ansible.builtin.template:
|
||||
src: immich.env.j2
|
||||
dest: /etc/immich/immich.env
|
||||
owner: root
|
||||
group: "{{ atlas_immich_group }}"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
diff: false
|
||||
notify: Restart Atlas media Quadlets
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Render Atlas media Quadlets
|
||||
tags: [atlas, containers]
|
||||
ansible.builtin.template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "{{ atlas_container_quadlet_dir }}/{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- atlas-media.network
|
||||
- atlas-immich-redis.container
|
||||
- atlas-immich-postgres.container
|
||||
- atlas-immich-machine-learning.container
|
||||
- atlas-immich-server.container
|
||||
- atlas-npm.container
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
notify: Restart Atlas media Quadlets
|
||||
when: atlas_manage_media_stack | bool
|
||||
|
||||
- name: Start Atlas media Quadlet services
|
||||
tags: [atlas, containers, services]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
state: started
|
||||
daemon_reload: true
|
||||
loop: "{{ atlas_media_quadlet_services }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when:
|
||||
- atlas_manage_media_stack | bool
|
||||
- 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,11 +5,17 @@
|
||||
- name: Import Atlas 45Drives tasks
|
||||
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
|
||||
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
|
||||
- name: Import Atlas media-container tasks
|
||||
ansible.builtin.import_tasks: containers.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
|
||||
64
ansible/roles/profile_atlas/tasks/samba_account.yml
Normal file
64
ansible/roles/profile_atlas/tasks/samba_account.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
- name: Require an existing Unix account for Atlas Samba
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ atlas_samba_account.username }}"
|
||||
|
||||
- name: Read Atlas Samba account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- pdbedit
|
||||
- --list
|
||||
- --user
|
||||
- "{{ atlas_samba_account.username }}"
|
||||
register: atlas_samba_account_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Check Atlas Samba password marker
|
||||
ansible.builtin.stat:
|
||||
path: "{{ atlas_samba_password_marker_dir }}/{{ atlas_samba_account.username }}.sha256"
|
||||
register: atlas_samba_password_marker_stat
|
||||
|
||||
- name: Read Atlas Samba password marker
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ atlas_samba_password_marker_dir }}/{{ atlas_samba_account.username }}.sha256"
|
||||
register: atlas_samba_password_marker_content
|
||||
no_log: true
|
||||
when: atlas_samba_password_marker_stat.stat.exists
|
||||
|
||||
- name: Determine whether Atlas Samba credentials must change
|
||||
ansible.builtin.set_fact:
|
||||
atlas_samba_password_digest: "{{ atlas_samba_account.password | hash('sha256') }}"
|
||||
atlas_samba_password_update_required: >-
|
||||
{{
|
||||
atlas_samba_account_check.rc != 0
|
||||
or not atlas_samba_password_marker_stat.stat.exists
|
||||
or (
|
||||
atlas_samba_password_marker_content.content | default('')
|
||||
| b64decode | trim
|
||||
) != (atlas_samba_account.password | hash('sha256'))
|
||||
}}
|
||||
no_log: true
|
||||
|
||||
- name: Set Atlas Samba account password
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- smbpasswd
|
||||
- -s
|
||||
- -a
|
||||
- "{{ atlas_samba_account.username }}"
|
||||
stdin: "{{ atlas_samba_account.password }}\n{{ atlas_samba_account.password }}"
|
||||
changed_when: true
|
||||
no_log: true
|
||||
when: atlas_samba_password_update_required | bool
|
||||
|
||||
- name: Record managed Atlas Samba password digest
|
||||
ansible.builtin.copy:
|
||||
content: "{{ atlas_samba_password_digest }}\n"
|
||||
dest: "{{ atlas_samba_password_marker_dir }}/{{ atlas_samba_account.username }}.sha256"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
when: atlas_samba_password_update_required | bool
|
||||
@@ -1,187 +1,12 @@
|
||||
---
|
||||
- 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_aegis_ip != 'CHANGEME_AEGIS_IP'
|
||||
- atlas_firewalld_zone | length > 0
|
||||
fail_msg: Replace the Atlas LAN subnet and firewall zone placeholders.
|
||||
- ansible_facts.default_ipv4.interface | default('') | length > 0
|
||||
fail_msg: Replace the Atlas LAN, Aegis and firewall-zone placeholders.
|
||||
when: atlas_manage_firewall | bool
|
||||
|
||||
- name: Apply Atlas firewalld rich rules
|
||||
@@ -197,6 +22,16 @@
|
||||
label: "{{ item }}"
|
||||
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
|
||||
tags: [atlas, sharing, services]
|
||||
ansible.posix.firewalld:
|
||||
@@ -209,3 +44,228 @@
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
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
|
||||
|
||||
@@ -5,12 +5,80 @@
|
||||
atlas_zfs_datasets:
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_work }}"
|
||||
mountpoint: "{{ atlas_work_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
mountpoint: "{{ atlas_work_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_archive }}"
|
||||
mountpoint: "{{ atlas_archive_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: zstd
|
||||
mountpoint: "{{ atlas_archive_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
|
||||
mountpoint: "{{ atlas_app_data_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: zstd
|
||||
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 }}"
|
||||
mountpoint: "{{ atlas_syncthing_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
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 }}"
|
||||
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_icloud_photos }}"
|
||||
mountpoint: "{{ atlas_icloud_photos_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_photobook }}"
|
||||
mountpoint: "{{ atlas_photobook_mountpoint }}"
|
||||
owner: "{{ atlas_immich_username }}"
|
||||
group: "{{ atlas_immich_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_photobook_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_backups }}"
|
||||
mountpoint: "{{ atlas_backups_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_backups_mountpoint }}"
|
||||
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||
mountpoint: "{{ atlas_service_backups_mountpoint }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
properties:
|
||||
compression: lz4
|
||||
mountpoint: "{{ atlas_service_backups_mountpoint }}"
|
||||
refreservation: "{{ atlas_zfs_service_backups_refreservation }}"
|
||||
|
||||
- name: Require completed Atlas storage placeholders
|
||||
tags: [atlas, storage]
|
||||
@@ -56,8 +124,7 @@
|
||||
community.general.zfs:
|
||||
name: "{{ item.name }}"
|
||||
state: present
|
||||
extra_zfs_properties:
|
||||
mountpoint: "{{ item.mountpoint }}"
|
||||
extra_zfs_properties: "{{ item.properties }}"
|
||||
loop: "{{ atlas_zfs_datasets }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
@@ -68,8 +135,8 @@
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.mountpoint }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
owner: "{{ item.owner }}"
|
||||
group: "{{ item.group }}"
|
||||
mode: "0770"
|
||||
loop: "{{ atlas_zfs_datasets }}"
|
||||
loop_control:
|
||||
|
||||
@@ -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
|
||||
PasswordAuthentication 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 }}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich machine learning
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-machine-learning
|
||||
Image={{ atlas_immich_machine_learning_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
{% for group_name in atlas_immich_supplementary_groups %}
|
||||
GroupAdd={{ ansible_facts['getent_group'][group_name][1] }}
|
||||
{% endfor %}
|
||||
AddDevice=/dev/dri
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-machine-learning
|
||||
Environment=TZ={{ atlas_timezone }}
|
||||
Volume={{ atlas_immich_model_cache_dir }}:/cache:Z
|
||||
Volume={{ atlas_immich_machine_learning_config_dir }}:/.config:Z
|
||||
Volume={{ atlas_immich_machine_learning_cache_dir }}:/.cache:Z
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,22 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich PostgreSQL database
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-postgres
|
||||
Image={{ atlas_immich_postgres_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-postgres
|
||||
EnvironmentFile=/etc/immich/immich.env
|
||||
Volume={{ atlas_immich_postgres_data_dir }}:/var/lib/postgresql/data:Z
|
||||
ShmSize=128m
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,20 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich Redis-compatible cache
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-redis
|
||||
Image={{ atlas_immich_redis_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-redis
|
||||
Volume={{ atlas_immich_redis_data_dir }}:/data:Z
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,29 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Immich server
|
||||
Requires=atlas-immich-redis.service atlas-immich-postgres.service atlas-immich-machine-learning.service
|
||||
After=atlas-immich-redis.service atlas-immich-postgres.service atlas-immich-machine-learning.service
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-immich-server
|
||||
Image={{ atlas_immich_server_image }}
|
||||
User={{ atlas_immich_uid }}:{{ atlas_immich_gid }}
|
||||
{% for group_name in atlas_immich_supplementary_groups %}
|
||||
GroupAdd={{ ansible_facts['getent_group'][group_name][1] }}
|
||||
{% endfor %}
|
||||
AddDevice=/dev/dri
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-immich-server
|
||||
EnvironmentFile=/etc/immich/immich.env
|
||||
Volume={{ atlas_immich_upload_dir }}:/data:Z
|
||||
Volume={{ atlas_photobook_mountpoint }}:/external/photobook:ro,Z
|
||||
Volume=/etc/localtime:/etc/localtime:ro
|
||||
NoNewPrivileges=true
|
||||
DropCapability=NET_RAW
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,3 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Network]
|
||||
NetworkName=atlas-media
|
||||
21
ansible/roles/profile_atlas/templates/atlas-npm.container.j2
Normal file
21
ansible/roles/profile_atlas/templates/atlas-npm.container.j2
Normal file
@@ -0,0 +1,21 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Atlas Nginx Proxy Manager
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-npm
|
||||
Image={{ atlas_npm_image }}
|
||||
Network=atlas-media.network
|
||||
NetworkAlias=atlas-npm
|
||||
PublishPort=80:80
|
||||
PublishPort=443:443
|
||||
PublishPort={{ atlas_npm_admin_bind_address }}:81:81
|
||||
Volume={{ atlas_npm_data_dir }}:/data:Z
|
||||
Volume={{ atlas_npm_letsencrypt_dir }}:/etc/letsencrypt:Z
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
{% for export in atlas_nfs_exports %}
|
||||
{{ export.path }} {{ export.clients }}
|
||||
{{ export.path }} {{ export.client }}({{ export.options | join(',') }})
|
||||
{% endfor %}
|
||||
|
||||
13
ansible/roles/profile_atlas/templates/immich.env.j2
Normal file
13
ansible/roles/profile_atlas/templates/immich.env.j2
Normal file
@@ -0,0 +1,13 @@
|
||||
# Managed by Ansible. Contains a Vault secret; do not edit or copy.
|
||||
TZ={{ atlas_timezone | to_json }}
|
||||
DB_HOSTNAME="atlas-immich-postgres"
|
||||
DB_USERNAME={{ atlas_immich_db_username | to_json }}
|
||||
DB_PASSWORD={{ atlas_immich_db_password | to_json }}
|
||||
DB_DATABASE_NAME={{ atlas_immich_db_name | to_json }}
|
||||
DB_VECTOR_EXTENSION="vectorchord"
|
||||
POSTGRES_USER={{ atlas_immich_db_username | to_json }}
|
||||
POSTGRES_PASSWORD={{ atlas_immich_db_password | to_json }}
|
||||
POSTGRES_DB={{ atlas_immich_db_name | to_json }}
|
||||
POSTGRES_INITDB_ARGS="--data-checksums"
|
||||
REDIS_HOSTNAME="atlas-immich-redis"
|
||||
IMMICH_MACHINE_LEARNING_URL="http://atlas-immich-machine-learning:3003"
|
||||
@@ -3,11 +3,16 @@
|
||||
workgroup = {{ atlas_samba_workgroup }}
|
||||
security = user
|
||||
map to guest = Never
|
||||
server min protocol = SMB2
|
||||
server min protocol = SMB3
|
||||
server signing = mandatory
|
||||
smb encrypt = {{ atlas_samba_encryption }}
|
||||
disable netbios = yes
|
||||
smb ports = 445
|
||||
hosts allow = {{ atlas_lan_subnet }}
|
||||
include = registry
|
||||
|
||||
[{{ atlas_samba_share_name }}]
|
||||
path = {{ atlas_work_mountpoint }}
|
||||
path = {{ atlas_archive_mountpoint }}
|
||||
browseable = yes
|
||||
read only = no
|
||||
valid users = {{ atlas_samba_valid_users | join(' ') }}
|
||||
|
||||
Reference in New Issue
Block a user