Add Atlas media and storage services

This commit is contained in:
Fabio Scotto di Santolo
2026-09-10 22:27:33 +02:00
parent 569e6ef24e
commit 64aebe8c34
34 changed files with 1127 additions and 209 deletions

View File

@@ -6,6 +6,7 @@
- atlas_admin_username != 'CHANGEME_ATLAS_ADMIN'
- (atlas_admin_ssh_keys | default([])) | length > 0
- atlas_admin_password_hash not in ['', '!', '*']
- "'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.
@@ -31,6 +32,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:

View 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

View File

@@ -11,5 +11,8 @@
- name: Import Atlas file sharing tasks
ansible.builtin.import_tasks: sharing.yml
- name: Import Atlas media-container tasks
ansible.builtin.import_tasks: containers.yml
- name: Import Atlas Syncthing tasks
ansible.builtin.import_tasks: syncthing.yml

View 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

View File

@@ -10,6 +10,19 @@
notify: Reload NFS exports
when: atlas_manage_storage | bool
- name: Enable Atlas file-sharing services
tags: [atlas, sharing, services]
ansible.builtin.systemd:
name: "{{ item }}"
enabled: true
state: started
loop:
- nfs-server.service
- smb.service
loop_control:
label: "{{ item }}"
when: atlas_manage_storage | bool
- name: Configure Atlas NFSv4-only service
tags: [atlas, sharing]
ansible.builtin.template:
@@ -82,106 +95,50 @@
notify: Restart Samba service
when: atlas_manage_storage | bool
- name: Require Atlas Samba password
- name: Require Vault-backed Atlas Samba accounts
tags: [atlas, sharing]
ansible.builtin.assert:
that:
- atlas_samba_password | length > 0
fail_msg: Define vault_atlas_samba_password before enabling Atlas storage.
- atlas_samba_accounts | length > 0
- >-
atlas_samba_accounts | map(attribute='username') | list
| difference(atlas_samba_valid_users) | length == 0
- >-
atlas_samba_valid_users
| difference(atlas_samba_accounts | map(attribute='username') | list) | length == 0
- atlas_samba_accounts | selectattr('password', 'equalto', '') | list | length == 0
fail_msg: Define every authorized Samba account and its Vault-backed password.
no_log: true
when: atlas_manage_storage | bool
- name: 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 }}"
path: "{{ atlas_samba_password_marker_dir }}"
state: directory
owner: root
group: root
mode: "0700"
when: atlas_manage_storage | bool
- name: Check Atlas Samba password marker
- name: Manage Vault-backed Atlas Samba credentials
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'))
}}
ansible.builtin.include_tasks: samba_account.yml
loop: "{{ atlas_samba_accounts }}"
loop_control:
loop_var: atlas_samba_account
label: "{{ atlas_samba_account.username }}"
no_log: true
when: atlas_manage_storage | bool
- name: 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.
fail_msg: Replace the Atlas LAN, Aegis and firewall-zone placeholders.
when: atlas_manage_firewall | bool
- name: Apply Atlas firewalld rich rules

View File

@@ -5,12 +5,52 @@
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_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_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_backup_prometheus }}"
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}"
properties:
mountpoint: "{{ atlas_backup_prometheus_mountpoint }}"
- name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_icloud_photos }}"
mountpoint: "{{ atlas_icloud_photos_mountpoint }}"
owner: "{{ atlas_immich_username }}"
group: "{{ atlas_immich_group }}"
properties:
compression: lz4
mountpoint: "{{ atlas_icloud_photos_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 +96,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 +107,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: