mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 19:03:47 +00:00
Add Atlas media and storage services
This commit is contained in:
@@ -6,6 +6,29 @@ effective_username: "{{ server_username }}"
|
||||
effective_user_group: "{{ server_user_group }}"
|
||||
effective_user_home: "{{ server_user_home }}"
|
||||
server_container_stack_dir: /opt/docker/server
|
||||
server_atlas_music_enabled: false
|
||||
server_atlas_sftp_remote_name: atlas
|
||||
server_atlas_sftp_host: CHANGEME_ATLAS_WIREGUARD_IP
|
||||
server_atlas_sftp_port: 22
|
||||
server_atlas_sftp_username: admin
|
||||
server_atlas_sftp_remote_path: /pool/media/music
|
||||
server_atlas_sftp_private_key: "{{ vault_prometheus_atlas_sftp_private_key | default('') }}"
|
||||
server_atlas_sftp_private_key_file: /etc/rclone/atlas_ed25519
|
||||
server_atlas_sftp_known_host: ""
|
||||
server_atlas_sftp_known_hosts_file: /etc/rclone/known_hosts
|
||||
server_atlas_wireguard_unit: ""
|
||||
server_rclone_config_dir: /etc/rclone
|
||||
server_rclone_config_file: /etc/rclone/rclone.conf
|
||||
server_rclone_music_service: rclone-music.service
|
||||
server_rclone_music_mountpoint: /mnt/music_atlas
|
||||
server_rclone_music_cache_dir: /var/cache/rclone-music
|
||||
server_navidrome_data_dir: /opt/navidrome/data
|
||||
server_navidrome_quadlet_dir: "{{ server_user_home }}/.config/containers/systemd"
|
||||
server_navidrome_env_dir: "{{ server_user_home }}/.config/navidrome"
|
||||
server_navidrome_env_file: "{{ server_navidrome_env_dir }}/navidrome.env"
|
||||
server_navidrome_image: docker.io/deluan/navidrome:latest
|
||||
server_navidrome_port: 4533
|
||||
server_navidrome_db_password: "{{ vault_navidrome_db_password | default('') }}"
|
||||
ai_agents: {}
|
||||
vim_plugins_enabled: false
|
||||
|
||||
@@ -62,11 +85,7 @@ server_directories:
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
- path: /opt/navidrome/data
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0755"
|
||||
- path: /opt/music
|
||||
- path: "{{ server_navidrome_data_dir }}"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0755"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
# Replace every CHANGEME value before enabling Atlas storage management.
|
||||
# Keep Atlas management gates disabled until the NAS and required Vault inputs are ready.
|
||||
hostname: atlas
|
||||
ansible_host: CHANGEME_ATLAS_HOST
|
||||
ansible_host: 192.168.178.55
|
||||
ansible_user: "{{ atlas_connection_username }}"
|
||||
ansible_port: 22
|
||||
ansible_ssh_private_key_file: CHANGEME_ATLAS_SSH_PRIVATE_KEY_PATH
|
||||
ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519
|
||||
|
||||
atlas_admin_username: admin
|
||||
atlas_connection_username: "{{ atlas_admin_username }}"
|
||||
@@ -15,46 +15,67 @@ 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_lan_subnet: 192.168.178.0/24
|
||||
atlas_aegis_ip: 192.168.178.54
|
||||
atlas_manage_firewall: false
|
||||
atlas_firewalld_zone: public
|
||||
atlas_zfs_pool: CHANGEME_ZFS_POOL
|
||||
atlas_zfs_pool: zpool
|
||||
atlas_zfs_dataset_work: work
|
||||
atlas_zfs_dataset_archive: archive
|
||||
atlas_zfs_dataset_music: media/music
|
||||
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_zfs_dataset_icloud_photos: media/icloud_photos
|
||||
atlas_zfs_dataset_service_backups: backups/services
|
||||
atlas_mount_root: /zpool
|
||||
atlas_manage_storage: false
|
||||
atlas_manage_media_stack: false
|
||||
|
||||
rocky_manage_openzfs_repo: true
|
||||
rocky_manage_syncthing_binary: true
|
||||
rocky_manage_podman: true
|
||||
rocky_podman_packages:
|
||||
- podman
|
||||
|
||||
host_packages:
|
||||
- cockpit
|
||||
- nfs-utils
|
||||
- policycoreutils-python-utils
|
||||
- samba
|
||||
- samba-client
|
||||
- samba-common-tools
|
||||
- zfs
|
||||
|
||||
atlas_nfs_exports:
|
||||
- path: "{{ atlas_work_mountpoint }}"
|
||||
clients: "{{ atlas_lan_subnet }}(rw,sync,no_subtree_check,root_squash)"
|
||||
- path: "{{ atlas_icloud_photos_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: work
|
||||
atlas_samba_share_name: Archive
|
||||
atlas_samba_workgroup: WORKGROUP
|
||||
atlas_samba_valid_users:
|
||||
- "{{ atlas_admin_username }}"
|
||||
atlas_samba_password: "{{ vault_atlas_samba_password | default('') }}"
|
||||
# Append any pre-existing laptop Unix account here and keep its Samba password in Vault.
|
||||
atlas_samba_accounts:
|
||||
- username: "{{ atlas_admin_username }}"
|
||||
password: "{{ vault_atlas_samba_password | default('') }}"
|
||||
atlas_samba_valid_users: "{{ atlas_samba_accounts | map(attribute='username') | list }}"
|
||||
|
||||
atlas_immich_db_password: "{{ vault_atlas_immich_db_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_aegis_ip }}" service name="nfs" accept'
|
||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="samba" accept'
|
||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="http" accept'
|
||||
- 'rule family="ipv4" source address="{{ atlas_lan_subnet }}" service name="https" accept'
|
||||
|
||||
host_enabled_services:
|
||||
- sshd
|
||||
- cockpit.socket
|
||||
- nfs-server
|
||||
- smb
|
||||
- zfs.target
|
||||
|
||||
@@ -7,11 +7,19 @@ ansible_ssh_private_key_file: /home/fscotto/.ssh/id_ed25519
|
||||
|
||||
server_username: rocky
|
||||
server_duckdns_domain: fscotto
|
||||
server_atlas_music_enabled: false
|
||||
server_atlas_sftp_host: CHANGEME_ATLAS_WIREGUARD_IP
|
||||
server_atlas_sftp_username: admin
|
||||
server_atlas_sftp_known_host: CHANGEME_ATLAS_SSH_HOST_KEY
|
||||
server_atlas_wireguard_unit: wg-quick@wg0.service
|
||||
server_atlas_sftp_private_key: "{{ vault_prometheus_atlas_sftp_private_key | default('') }}"
|
||||
|
||||
host_packages:
|
||||
- cockpit
|
||||
- cockpit-navigator
|
||||
- cockpit-podman
|
||||
- fuse3
|
||||
- rclone
|
||||
|
||||
host_enabled_services:
|
||||
- cockpit.socket
|
||||
|
||||
@@ -8,20 +8,36 @@ atlas_admin_gid: 1000
|
||||
atlas_admin_ssh_keys: []
|
||||
atlas_admin_password_hash: "!"
|
||||
|
||||
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_zfs_pool: CHANGEME_ZFS_POOL
|
||||
atlas_zfs_dataset_work: work
|
||||
atlas_zfs_dataset_archive: archive
|
||||
atlas_zfs_dataset_music: media/music
|
||||
atlas_zfs_dataset_syncthing: syncthing
|
||||
atlas_zfs_dataset_backup_prometheus: backup_prometheus
|
||||
atlas_zfs_dataset_icloud_photos: icloud_photos
|
||||
atlas_zfs_dataset_icloud_photos: media/icloud_photos
|
||||
atlas_zfs_dataset_service_backups: backups/services
|
||||
atlas_zfs_service_backups_refreservation: 500G
|
||||
atlas_mount_root: /CHANGEME_ATLAS_MOUNT_ROOT
|
||||
|
||||
atlas_archive_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_archive }}"
|
||||
atlas_music_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_music }}"
|
||||
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_service_backups_mountpoint: "{{ atlas_mount_root }}/{{ atlas_zfs_dataset_service_backups }}"
|
||||
atlas_syncthing_config_dir: "{{ atlas_admin_home }}/.local/state/syncthing"
|
||||
atlas_syncthing_default_dir: "{{ atlas_syncthing_mountpoint }}/Sync"
|
||||
|
||||
@@ -34,18 +50,59 @@ atlas_45drives_packages:
|
||||
- cockpit-zfs
|
||||
- cockpit-scheduler
|
||||
|
||||
atlas_nfs_exports: []
|
||||
atlas_samba_share_name: work
|
||||
atlas_nfs_exports:
|
||||
- path: "{{ atlas_icloud_photos_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
|
||||
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
|
||||
|
||||
@@ -32,3 +32,15 @@
|
||||
ansible.builtin.systemd:
|
||||
name: atlas-syncthing
|
||||
state: restarted
|
||||
|
||||
- name: Restart Atlas media Quadlets
|
||||
ansible.builtin.systemd:
|
||||
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,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:
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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_icloud_photos_mountpoint }}:/external/icloud_photos: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,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,12 @@
|
||||
workgroup = {{ atlas_samba_workgroup }}
|
||||
security = user
|
||||
map to guest = Never
|
||||
server min protocol = SMB2
|
||||
server min protocol = SMB3
|
||||
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(' ') }}
|
||||
|
||||
@@ -9,3 +9,29 @@
|
||||
tags: [services]
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart rclone music mount
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ server_rclone_music_service }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
when:
|
||||
- server_atlas_music_enabled | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Restart rootless Navidrome
|
||||
tags: [services, podman, rclone, navidrome]
|
||||
become_user: "{{ server_username }}"
|
||||
ansible.builtin.systemd:
|
||||
name: navidrome.service
|
||||
scope: user
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: >-
|
||||
unix:path=/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}/bus
|
||||
when:
|
||||
- server_atlas_music_enabled | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
fail_msg: >-
|
||||
server_firewall_backend must be firewalld for the Rocky server profile.
|
||||
|
||||
- name: Require server container secret variables
|
||||
tags: [dotfiles, dotfiles:server, services]
|
||||
- name: Require server database secret variables
|
||||
tags: [dotfiles, dotfiles:server, services, navidrome]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (vault_navidrome_db_password | default('')) | length > 0
|
||||
- server_navidrome_db_password | length > 0
|
||||
- (vault_postgres_root_password | default('')) | length > 0
|
||||
fail_msg: >-
|
||||
Server container secrets are missing. Define vault_navidrome_db_password and
|
||||
vault_postgres_root_password in secrets/vault.yml or another vars source.
|
||||
Define vault_navidrome_db_password and vault_postgres_root_password in Vault
|
||||
before rendering the Navidrome database configuration.
|
||||
no_log: true
|
||||
|
||||
- name: Configure DuckDNS updater
|
||||
tags: [dotfiles, dotfiles:server, duckdns]
|
||||
@@ -71,6 +72,12 @@
|
||||
tags: [services, podman]
|
||||
ansible.builtin.include_tasks: podman-compose.yml
|
||||
|
||||
- name: Configure Atlas music mount
|
||||
ansible.builtin.import_tasks: rclone-music.yml
|
||||
|
||||
- name: Configure rootless Navidrome
|
||||
ansible.builtin.import_tasks: navidrome.yml
|
||||
|
||||
- name: Disable SSH root login on server
|
||||
tags: [services]
|
||||
ansible.builtin.lineinfile:
|
||||
|
||||
114
ansible/roles/profile_server/tasks/navidrome.yml
Normal file
114
ansible/roles/profile_server/tasks/navidrome.yml
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
- name: Read server account details for rootless Navidrome
|
||||
tags: [services, podman, rclone, navidrome]
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ server_username }}"
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Check for a running legacy rootful Navidrome container
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- podman
|
||||
- container
|
||||
- inspect
|
||||
- --format
|
||||
- "{{ '{{.State.Running}}' }}"
|
||||
- navidrome
|
||||
register: server_legacy_navidrome_container
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Refuse concurrent legacy and rootless Navidrome
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
server_legacy_navidrome_container.rc != 0
|
||||
or server_legacy_navidrome_container.stdout | trim != 'true'
|
||||
fail_msg: >-
|
||||
Stop the legacy rootful Navidrome container before enabling the rootless Quadlet.
|
||||
The playbook does not remove the old container or its data automatically.
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Enable lingering for rootless Navidrome
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- loginctl
|
||||
- enable-linger
|
||||
- "{{ server_username }}"
|
||||
creates: "/var/lib/systemd/linger/{{ server_username }}"
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Start server user systemd manager
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.systemd:
|
||||
name: "user@{{ ansible_facts['getent_passwd'][server_username][1] }}.service"
|
||||
state: started
|
||||
when:
|
||||
- server_atlas_music_enabled | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Create rootless Navidrome Quadlet directory
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.file:
|
||||
path: "{{ server_navidrome_quadlet_dir }}"
|
||||
state: directory
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0700"
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Create private rootless Navidrome configuration directory
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.file:
|
||||
path: "{{ server_navidrome_env_dir }}"
|
||||
state: directory
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0700"
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Render Vault-backed rootless Navidrome environment
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.template:
|
||||
src: navidrome.env.j2
|
||||
dest: "{{ server_navidrome_env_file }}"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
diff: false
|
||||
notify: Restart rootless Navidrome
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Render rootless Navidrome Quadlet
|
||||
tags: [services, podman, navidrome]
|
||||
ansible.builtin.template:
|
||||
src: navidrome.container.j2
|
||||
dest: "{{ server_navidrome_quadlet_dir }}/navidrome.container"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0644"
|
||||
notify: Restart rootless Navidrome
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Start rootless Navidrome Quadlet
|
||||
tags: [services, podman, navidrome]
|
||||
become_user: "{{ server_username }}"
|
||||
ansible.builtin.systemd:
|
||||
name: navidrome.service
|
||||
scope: user
|
||||
state: started
|
||||
daemon_reload: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: >-
|
||||
unix:path=/run/user/{{ ansible_facts['getent_passwd'][server_username][1] }}/bus
|
||||
when:
|
||||
- server_atlas_music_enabled | bool
|
||||
- not ansible_check_mode
|
||||
105
ansible/roles/profile_server/tasks/rclone-music.yml
Normal file
105
ansible/roles/profile_server/tasks/rclone-music.yml
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
- name: Require completed Atlas SFTP mount configuration
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- server_atlas_sftp_host != 'CHANGEME_ATLAS_WIREGUARD_IP'
|
||||
- server_atlas_sftp_known_host != 'CHANGEME_ATLAS_SSH_HOST_KEY'
|
||||
- server_atlas_sftp_known_host | length > 0
|
||||
- server_atlas_sftp_private_key | length > 0
|
||||
- server_atlas_sftp_remote_path == '/pool/media/music'
|
||||
fail_msg: >-
|
||||
Define the Atlas WireGuard address, pinned SSH host key and Vault-backed
|
||||
SFTP private key before enabling the music mount.
|
||||
no_log: true
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Create private rclone configuration directory
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.file:
|
||||
path: "{{ server_rclone_config_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0750"
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Install Vault-backed Atlas SFTP private key
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.copy:
|
||||
content: "{{ server_atlas_sftp_private_key | trim }}\n"
|
||||
dest: "{{ server_atlas_sftp_private_key_file }}"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
diff: false
|
||||
notify:
|
||||
- Restart rclone music mount
|
||||
- Restart rootless Navidrome
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Install pinned Atlas SSH host key
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.copy:
|
||||
content: "{{ server_atlas_sftp_known_host | trim }}\n"
|
||||
dest: "{{ server_atlas_sftp_known_hosts_file }}"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0640"
|
||||
notify:
|
||||
- Restart rclone music mount
|
||||
- Restart rootless Navidrome
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Render Atlas SFTP rclone configuration
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.template:
|
||||
src: rclone.conf.j2
|
||||
dest: "{{ server_rclone_config_file }}"
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0600"
|
||||
notify:
|
||||
- Restart rclone music mount
|
||||
- Restart rootless Navidrome
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Create rclone music directories
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ server_username }}"
|
||||
group: "{{ server_user_group }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ server_rclone_music_mountpoint }}"
|
||||
- "{{ server_rclone_music_cache_dir }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Render rclone music system service
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.template:
|
||||
src: rclone-music.service.j2
|
||||
dest: "/etc/systemd/system/{{ server_rclone_music_service }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify:
|
||||
- Restart rclone music mount
|
||||
- Restart rootless Navidrome
|
||||
when: server_atlas_music_enabled | bool
|
||||
|
||||
- name: Enable and start rclone music mount
|
||||
tags: [services, rclone, navidrome]
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ server_rclone_music_service }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
when:
|
||||
- server_atlas_music_enabled | bool
|
||||
- not ansible_check_mode
|
||||
@@ -0,0 +1,21 @@
|
||||
# Managed by Ansible. Do not edit manually.
|
||||
[Unit]
|
||||
Description=Rootless Navidrome music server
|
||||
|
||||
[Container]
|
||||
ContainerName=navidrome
|
||||
Image={{ server_navidrome_image }}
|
||||
Network=host
|
||||
EnvironmentFile={{ server_navidrome_env_file }}
|
||||
Volume={{ server_navidrome_data_dir }}:/data
|
||||
Volume={{ server_rclone_music_mountpoint }}:/music:ro
|
||||
SecurityLabelDisable=true
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/usr/bin/mountpoint -q {{ server_rclone_music_mountpoint }}
|
||||
Restart=always
|
||||
RestartSec=10s
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
7
ansible/roles/profile_server/templates/navidrome.env.j2
Normal file
7
ansible/roles/profile_server/templates/navidrome.env.j2
Normal file
@@ -0,0 +1,7 @@
|
||||
# Managed by Ansible. Contains a Vault secret; do not edit or copy.
|
||||
ND_DATABASE_URL={{ ('postgres://navidrome:' ~ server_navidrome_db_password ~ '@127.0.0.1:5432/navidrome_db?sslmode=disable') | to_json }}
|
||||
ND_PORT={{ server_navidrome_port }}
|
||||
ND_SCANSCHEDULE="1h"
|
||||
ND_SESSIONTIMEOUT="24h"
|
||||
ND_ENABLETRANSCODING="true"
|
||||
ND_LOGLEVEL="info"
|
||||
@@ -0,0 +1,29 @@
|
||||
[Unit]
|
||||
Description=Read-only Atlas music mount via rclone SFTP
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
{% if server_atlas_wireguard_unit | length > 0 %}
|
||||
Wants={{ server_atlas_wireguard_unit }}
|
||||
After={{ server_atlas_wireguard_unit }}
|
||||
{% endif %}
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
User={{ server_username }}
|
||||
Group={{ server_user_group }}
|
||||
ExecStart=/usr/bin/rclone mount \
|
||||
{{ server_atlas_sftp_remote_name }}:{{ server_atlas_sftp_remote_path }} \
|
||||
{{ server_rclone_music_mountpoint }} \
|
||||
--config {{ server_rclone_config_file }} \
|
||||
--cache-dir {{ server_rclone_music_cache_dir }} \
|
||||
--read-only \
|
||||
--vfs-cache-mode full \
|
||||
--vfs-cache-max-size 15G \
|
||||
--vfs-read-chunk-size 5M \
|
||||
--dir-cache-time 24h
|
||||
ExecStop=-/usr/bin/fusermount3 -uz {{ server_rclone_music_mountpoint }}
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
9
ansible/roles/profile_server/templates/rclone.conf.j2
Normal file
9
ansible/roles/profile_server/templates/rclone.conf.j2
Normal file
@@ -0,0 +1,9 @@
|
||||
# Managed by Ansible. Authentication uses the separately deployed Vault-backed key.
|
||||
[{{ server_atlas_sftp_remote_name }}]
|
||||
type = sftp
|
||||
host = {{ server_atlas_sftp_host }}
|
||||
user = {{ server_atlas_sftp_username }}
|
||||
port = {{ server_atlas_sftp_port }}
|
||||
key_file = {{ server_atlas_sftp_private_key_file }}
|
||||
known_hosts_file = {{ server_atlas_sftp_known_hosts_file }}
|
||||
shell_type = unix
|
||||
@@ -3,25 +3,6 @@
|
||||
name: server
|
||||
|
||||
services:
|
||||
navidrome:
|
||||
image: docker.io/deluan/navidrome:latest
|
||||
container_name: navidrome
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "4533"
|
||||
environment:
|
||||
ND_DATABASE_URL: "postgres://navidrome:{{ vault_navidrome_db_password }}@navidromedb:5432/navidrome_db?sslmode=disable"
|
||||
ND_SESSIONTIMEOUT: 24h
|
||||
ND_ENABLETRANSCODING: "true"
|
||||
|
||||
volumes:
|
||||
- "/opt/navidrome/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
- "/opt/music:/music:ro{{ ',' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
user: "1000:1000"
|
||||
networks:
|
||||
- web
|
||||
depends_on:
|
||||
- navidromedb
|
||||
nginx-proxy-manager:
|
||||
image: docker.io/jc21/nginx-proxy-manager:latest
|
||||
container_name: nginx-proxy-manager
|
||||
@@ -30,6 +11,8 @@ services:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "127.0.0.1:81:81"
|
||||
extra_hosts:
|
||||
- "host.containers.internal:host-gateway"
|
||||
volumes:
|
||||
- "/opt/npm/data:/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
- "/opt/npm/letsencrypt:/etc/letsencrypt{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
@@ -64,11 +47,13 @@ services:
|
||||
POSTGRES_DB: "navidrome_db"
|
||||
POSTGRES_USER: "navidrome"
|
||||
POSTGRES_PASSWORD: "{{ vault_postgres_root_password }}"
|
||||
|
||||
volumes:
|
||||
- "/opt/postgres/data:/var/lib/postgresql/data{{ ':' ~ selinux_volume_option if selinux_volume_option else '' }}"
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432"
|
||||
networks:
|
||||
- web
|
||||
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:1.25.2
|
||||
container_name: gitea
|
||||
|
||||
Reference in New Issue
Block a user