Logical bug fixes and add Jellyfin

This commit is contained in:
drew 2026-05-28 17:13:36 -04:00
parent bd16ea01bc
commit 6363aa713e
39 changed files with 437 additions and 718 deletions

View File

@ -8,6 +8,9 @@
tags: firewall_base
- role: cli_productivity
tags: cli_productivity
- role: tailscale
tags: tailscale
when: tailscale_enabled | bool
- name: Shared storage clients
hosts: nfs_clients
@ -26,7 +29,7 @@
tags: nfs_server
- name: Containers stack
hosts: services
hosts: nas:services
become: true
roles:
- role: container_runtime
@ -34,13 +37,55 @@
- role: selinux_containers
tags: selinux_containers
- name: VPN
hosts: vpn_hosts
become: true
roles:
- role: vpn
tags: vpn
- name: Postgres stack
hosts: postgres_hosts
become: true
roles:
- role: postgres
tags: postgres
- name: Matrix stack
hosts: matrix_hosts
become: true
roles:
- role: matrix_synapse
tags: matrix
when: matrix_enabled | bool
- name: Coturn stack
hosts: coturn_hosts
become: true
roles:
- role: coturn
tags: coturn
- name: Media services
hosts: media
become: true
roles:
# Jellyfin role will go here later.
# - role: jellyfin
# tags: jellyfin
- role: jellyfin
tags: jellyfin
- name: Notes stack
hosts: notes_hosts
become: true
roles:
- role: trilium
tags: trilium
- name: Servarr stack
hosts: servarr_hosts
become: true
roles:
- role: servarr
tags: servarr
- name: DNS and reverse proxy
hosts: services
@ -51,27 +96,6 @@
- role: caddy
tags: caddy
- name: Servarr stack
hosts: servarr_hosts
become: true
roles:
- role: servarr
tags: servarr
- name: Matrix stack
hosts: matrix_hosts
become: true
roles:
- role: matrix_synapse
tags: matrix
- name: Notes stack
hosts: notes_hosts
become: true
roles:
- role: trilium
tags: trilium
- name: ML workloads
hosts: ml_hosts
become: true

View File

@ -1,4 +0,0 @@
---
# adguard/meta/main.yml
dependencies:
- role: selinux_containers

View File

@ -2,58 +2,25 @@
#adguard/tasks/main.yml
- import_tasks: firewall.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
recurse: yes
loop: "{{ adguard_base_directories }}"
- name: Directory SELinux requirement
- name: Create adguard container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ adguard_base_directories }}"
dir_list: "{{ adguard_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Ensure container app config directories are owned by container UID
become: true
file:
path: "{{ adguard_dir }}/conf"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
recurse: true
- name: Deploy AdGuard configuration template
template:
src: AdGuardHome.yaml.j2
dest: "{{ stack_root }}/adguard/conf/AdGuardHome.yaml"
mode: '0600'
force: "{{ adguard_overwrite_config | default(false) | bool }}"
- name: Setup directory for adguard config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy AdGuard Quadlet
template:
src: adguard.container.j2
dest: "{{ container_config_dir }}/adguard.container"
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "adguard"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start and enable AdGuard service
become: true
become_user: "{{ container_user }}"
systemd:
name: adguard.service
scope: user
state: started
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -1,194 +0,0 @@
http:
pprof:
port: 6060
enabled: false
doh:
routes:
- GET /dns-query
- POST /dns-query
- GET /dns-query/{ClientID}
- POST /dns-query/{ClientID}
insecure_enabled: false
address: 0.0.0.0:80
session_ttl: 720h
users:
- name: "{{ adguard_admin_user }}"
password: "{{ adguard_admin_password_hash }}"
auth_attempts: 5
block_auth_min: 15
http_proxy: ""
language: ""
theme: auto
dns:
bind_hosts:
- 0.0.0.0
port: 53
anonymize_client_ip: false
ratelimit: 20
ratelimit_subnet_len_ipv4: 24
ratelimit_subnet_len_ipv6: 56
ratelimit_whitelist: []
refuse_any: true
upstream_dns:
- https://dns10.quad9.net/dns-query
upstream_dns_file: ""
bootstrap_dns:
- 9.9.9.10
- 149.112.112.10
- 2620:fe::10
- 2620:fe::fe:10
fallback_dns: []
upstream_mode: load_balance
fastest_timeout: 1s
allowed_clients: []
disallowed_clients: []
blocked_hosts:
- version.bind
- id.server
- hostname.bind
trusted_proxies:
- 127.0.0.0/8
- ::1/128
cache_enabled: true
cache_size: 4194304
cache_ttl_min: 0
cache_ttl_max: 0
cache_optimistic: false
cache_optimistic_answer_ttl: 30s
cache_optimistic_max_age: 12h
bogus_nxdomain: []
aaaa_disabled: false
enable_dnssec: false
edns_client_subnet:
custom_ip: ""
enabled: false
use_custom: false
max_goroutines: 300
handle_ddr: true
ipset: []
ipset_file: ""
bootstrap_prefer_ipv6: false
upstream_timeout: 10s
private_networks: []
use_private_ptr_resolvers: true
local_ptr_upstreams: []
use_dns64: false
dns64_prefixes: []
serve_http3: false
use_http3_upstreams: false
serve_plain_dns: true
hostsfile_enabled: true
pending_requests:
enabled: true
tls:
enabled: false
server_name: ""
force_https: false
port_https: 443
port_dns_over_tls: 853
port_dns_over_quic: 853
port_dnscrypt: 0
dnscrypt_config_file: ""
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
strict_sni_check: false
querylog:
dir_path: ""
ignored: []
interval: 2160h
size_memory: 1000
enabled: true
ignored_enabled: false
file_enabled: true
statistics:
dir_path: ""
ignored: []
interval: 24h
enabled: true
ignored_enabled: false
filters:
- enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
name: AdGuard DNS filter
id: 1
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt
name: AdAway Default Blocklist
id: 2
whitelist_filters: []
user_rules: []
dhcp:
enabled: false
interface_name: ""
local_domain_name: lan
dhcpv4:
gateway_ip: ""
subnet_mask: ""
range_start: ""
range_end: ""
lease_duration: 86400
icmp_timeout_msec: 1000
options: []
dhcpv6:
range_start: ""
lease_duration: 86400
ra_slaac_only: false
ra_allow_slaac: false
filtering:
blocking_ipv4: ""
blocking_ipv6: ""
blocked_services:
schedule:
time_zone: UTC
ids: []
protection_disabled_until: null
safe_search:
enabled: false
bing: true
duckduckgo: true
ecosia: true
google: true
pixabay: true
yandex: true
youtube: true
blocking_mode: default
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
rewrites: []
safe_fs_patterns:
- /opt/adguardhome/work/userfilters/*
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
filters_update_interval: 24
blocked_response_ttl: 10
filtering_enabled: true
rewrites_enabled: true
parental_enabled: false
safebrowsing_enabled: false
protection_enabled: true
clients:
runtime_sources:
whois: true
arp: true
rdns: true
dhcp: true
hosts: true
persistent: []
log:
enabled: true
file: ""
max_backups: 0
max_size: 100
max_age: 3
compress: false
local_time: false
verbose: false
os:
group: ""
user: ""
rlimit_nofile: 0
schema_version: 34

View File

@ -7,15 +7,15 @@ Requires=homelab-network.service
[Container]
Image=docker.io/adguard/adguardhome:latest
ContainerName=adguard
Network=homelab.network
Network=homelab
NetworkAlias=adguard
Volume={{ adguard_dir }}/work:/opt/adguardhome/work
Volume={{ adguard_dir }}/conf:/opt/adguardhome/conf
PublishPort=53:53/tcp
PublishPort=53:53/udp
PublishPort=3000:3000/tcp
PublishPort={{ adguard_bind_ip }}:{{ adguard_dns_port | default(53) }}:53/tcp
PublishPort={{ adguard_bind_ip }}:{{ adguard_dns_port | default(53) }}:53/udp
PublishPort={{ adguard_bind_ip }}:{{ adguard_web_port | default(3000) }}:3000/tcp
[Service]
Restart=always

View File

@ -1,4 +0,0 @@
---
# caddy/meta/main.yml
dependencies:
- role: selinux_containers

View File

@ -1,22 +1,13 @@
---
#caddy/tasks/main.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
recurse: yes
loop: "{{ caddy_base_directories }}"
- name: Base SELinux requirement
- name: Create caddy container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ caddy_base_directories }}"
dir_list: "{{ caddy_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Setup directory for caddy config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Ensure Caddyfile is deployed
template:
@ -31,19 +22,11 @@
src: caddy.container.j2
dest: "{{ container_config_dir }}/caddy.container"
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "caddy"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start service
become: true
become_user: "{{ container_user }}"
systemd:
name: caddy.service
scope: user
state: started
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -4,6 +4,12 @@
reverse_proxy {{ adguard_upstream }}
}
# Jellyfin
{{ jellyfin_domain }} {
tls internal
reverse_proxy {{ jellyfin_upstream }}
}
# QBittorrent
{{ qbittorrent_domain }} {
tls internal

View File

@ -8,7 +8,7 @@ After=homelab-network.service
[Container]
Image=docker.io/caddy:latest
ContainerName=caddy
Network=homelab.network
Network=homelab
Volume={{ caddy_dir }}/Caddyfile:/etc/caddy/Caddyfile
Volume={{ caddy_dir }}/data:/data

View File

@ -4,7 +4,7 @@ cli_user: "{{ container_user }}"
cli_group: "{{ cli_user }}"
cli_install_epel: true
cli_use_epel: "{{ cli_install_epel | bool and ansible_os_family == 'RedHat' and ansible_distribution != 'Fedora' }}"
cli_use_epel: "{{ cli_install_epel | bool and ansible_facts['os_family'] == 'RedHat' and ansible_facts['distribution'] != 'Fedora' }}"
cli_set_fish_as_default_shell: true

View File

@ -0,0 +1,14 @@
---
# container_runtime/tasks/config-setup.yml
- import_tasks: directory.yml
- name: Directory SELinux requirement
ansible.builtin.set_fact:
selinux_container_paths: "{{ missing_dirs }}"
when: missing_dirs | length > 0
- name: Apply SELinux container labels
ansible.builtin.import_role:
name: selinux_containers
tasks_from: labels
when: missing_dirs | length > 0

View File

@ -0,0 +1,26 @@
- name: Check directories exist
ansible.builtin.stat:
path: "{{ item.path if item is mapping else item }}"
loop: "{{ dir_list }}"
register: dir_stats
- name: Build list of missing directories
ansible.builtin.set_fact:
missing_dirs: >-
{{
dir_stats.results
| rejectattr('stat.exists', 'equalto', true)
| map(attribute='item')
| list
}}
- name: Ensure missing directories exist
become: true
ansible.builtin.file:
path: "{{ item.path if item is mapping else item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "{{ item.mode | default('0755') if item is mapping else '0755' }}"
loop: "{{ missing_dirs }}"
when: missing_dirs | length > 0

View File

@ -20,6 +20,24 @@
value: '53'
state: present
- name: Check configuration directories exist
ansible.builtin.stat:
path: "{{ item }}"
loop:
- "{{ stack_root }}"
- "{{ container_config_dir }}"
register: config_dirs_stats
- name: Build list of missing configuration directories
ansible.builtin.set_fact:
missing_config_dirs: >-
{{
config_dirs_stats.results
| rejectattr('stat.exists')
| map(attribute='item')
| list
}}
- name: Create stack directories
file:
path: "{{ item }}"
@ -28,9 +46,9 @@
group: "{{ container_group }}"
mode: "0755"
recurse: yes
loop:
- "{{ stack_root }}"
- "{{ container_config_dir }}"
loop: "{{ missing_config_dirs }}"
when: missing_config_dirs | length > 0
- name: Configure SELinux container policies
ansible.builtin.import_tasks: ../selinux_containers/tasks/main.yml
@ -44,17 +62,11 @@
owner: "{{ container_user }}"
group: "{{ container_group }}"
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "homelab-network"
- name: Start homelab network
become: true
become_user: "{{ container_user }}"
systemd:
name: homelab-network.service
scope: user
state: started
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -0,0 +1,31 @@
---
# container_runtime/tasks/systemd.yml
- name: Build container service list
ansible.builtin.set_fact:
container_service_names: "{{ service_names | default([service_name]) }}"
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
when: not ansible_check_mode
- name: Wait for quadlet generation
pause:
seconds: 1
when: not ansible_check_mode
- name: Start and enable container services
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: "{{ item }}.service"
scope: user
state: started
enabled: true
loop: "{{ container_service_names }}"
when: not ansible_check_mode

View File

@ -1,4 +0,0 @@
---
# coturn/meta/main.yml
dependencies:
- role: selinux_containers

View File

@ -2,32 +2,14 @@
# coturn/tasks/main.yml
- import_tasks: firewall.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
recurse: yes
loop: "{{ coturn_base_directories }}"
- name: Directory SELinux requirement
- name: Create coturn container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ coturn_base_directories }}"
dir_list: "{{ coturn_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Ensure container app config directories are owned by container UID
become: true
file:
path: "{{ coturn_dir }}/conf"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
recurse: true
- name: Setup directory for coturn config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy Turnserver configuration template
template:
@ -36,6 +18,8 @@
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: '0600'
force: "{{ coturn_overwrite_config | default(false) | bool }}"
when: not ansible_check_mode
- name: Deploy Coturn Quadlet
template:
@ -45,24 +29,11 @@
group: "{{ container_group }}"
mode: '0644'
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "coturn"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start and enable Coturn service
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: coturn.service
scope: user
state: started
enabled: true
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -7,7 +7,7 @@ Requires=homelab-network.service
ContainerName=coturn
Image=docker.io/coturn/coturn:latest
Volume={{ coturn_config_dir }}/turnserver.conf:/etc/coturn/turnserver.conf:Z
Network=homelab.network
Network=homelab
PublishPort=3478:3478/tcp
PublishPort=3478:3478/udp
PublishPort=49152-49172:49152-49172/udp

View File

@ -0,0 +1,12 @@
---
# jellyfin/tasks/firewall.yml
- name: Open required Jellyfin firewall rules
become: true
ansible.posix.firewalld:
port: "{{ item.port | default(omit) }}"
service: "{{ item.service | default(omit) }}"
permanent: true
state: enabled
immediate: true
loop: "{{ jellyfin_firewall_rules }}"

View File

@ -0,0 +1,30 @@
---
# jellyfin/tasks/main.yml
- import_tasks: firewall.yml
- name: Create jellyfin container directory
ansible.builtin.set_fact:
dir_list: "{{ jellyfin_base_directories }}"
- name: Setup directory for jellyfin config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy Jellyfin Quadlet
template:
src: jellyfin.container.j2
dest: "{{ container_config_dir }}/jellyfin.container"
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0644"
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "jellyfin"
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -0,0 +1,21 @@
[Unit]
Description=Jellyfin Media Server
Wants=network-online.target
After=network-online.target
[Container]
ContainerName=jellyfin
Image=docker.io/jellyfin/jellyfin:10.11.7
Network=host
Volume={{ jellyfin_config_dir }}:/config:Z
Volume={{ jellyfin_cache_dir }}:/cache:Z
Volume={{ jellyfin_media_dir }}:/data:ro
Environment=TZ={{ timezone }}
[Service]
Restart=always
[Install]
WantedBy=default.target

View File

@ -1,6 +0,0 @@
---
# matrix_synapse/meta/main.yml
dependencies:
- role: tailscale
- role: postgres
- role: coturn

View File

@ -1,21 +1,13 @@
---
# matrix_synapse/tasks/main.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
loop: "{{ synapse_base_directories }}"
- name: Directory SELinux requirement
- name: Create synapse container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ synapse_base_directories }}"
dir_list: "{{ synapse_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Create directory for synapse config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy homesever configuration template
template:
@ -25,6 +17,7 @@
group: "{{ container_group }}"
mode: '0600'
force: "{{ matrix_overwrite_config | default(false) | bool }}"
when: not ansible_check_mode
- name: Ensure Synapse signing key is deployed
copy:
@ -34,6 +27,7 @@
group: "{{ container_group }}"
mode: '0600'
force: "{{ matrix_overwrite_signing_key | default(false) | bool }}"
when: not ansible_check_mode
- name: Deploy Synapse Quadlet
template:
@ -43,24 +37,11 @@
group: "{{ container_group }}"
mode: '0644'
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "synapse"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start and enable Synapse service
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: synapse.service
scope: user
state: started
enabled: true
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -7,7 +7,7 @@ Requires=postgres.service
ContainerName=synapse
Image=docker.io/matrixdotorg/synapse:latest
Volume={{ synapse_config_dir }}:/data:Z
Network=homelab.network
Network=homelab
# Expose the client-server API port
PublishPort={{ tailscale_ip }}:8008:8008
UserNS=keep-id

View File

@ -1,56 +1,19 @@
---
# nfs_client/tasks/main.yml
- name: Configure dummy NAS storage for test environment
when: env == "test"
block:
- name: Create dummy NAS root for test environment
become: true
file:
path: "{{ nfs_mount_point }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
- name: Create dummy NAS storage tree for test environment
become: true
file:
path: "{{ nfs_mount_point }}/{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0775"
loop: "{{ storage_tree }}"
- name: Set SELinux context for dummy NAS storage in test environment
become: true
community.general.sefcontext:
target: "{{ nfs_mount_point }}(/.*)?"
setype: container_file_t
state: present
- name: Apply SELinux context for dummy NAS storage in test environment
become: true
command: restorecon -Rv "{{ nfs_mount_point }}"
changed_when: false
- name: Configure NFS client for non-test environments
when: env != "test"
block:
- name: Install required NFS client packages
- name: Install required NFS client packages
become: true
dnf:
name: nfs-utils
state: present
- name: Check whether NFS mount point is already mounted
- name: Check whether NFS mount point is already mounted
become: true
ansible.builtin.command: findmnt --mountpoint "{{ nfs_mount_point }}"
register: nfs_mount_check
changed_when: false
failed_when: false
- name: Ensure local NFS mount point exists before mounting
- name: Ensure local NFS mount point exists before mounting
become: true
file:
path: "{{ nfs_mount_point }}"
@ -60,7 +23,7 @@
mode: "0755"
when: nfs_mount_check.rc != 0
- name: Ensure NFS mount is present in fstab and mounted
- name: Ensure NFS mount is present in fstab and mounted
become: true
ansible.posix.mount:
path: "{{ nfs_mount_point }}"

View File

@ -1,4 +0,0 @@
---
# postgres/meta/main.yml
dependencies:
- role: selinux_containers

View File

@ -1,30 +1,13 @@
---
# postgres/tasks/main.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
loop: "{{ postgres_base_directories }}"
- name: Directory SELinux requirement
- name: Create postgres container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ postgres_base_directories }}"
dir_list: "{{ postgres_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Ensure container app config directories are owned by container UID
become: true
file:
path: "{{ postgres_config_dir }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
recurse: true
- name: Setup directory for postgres config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy Postgres Quadlet
template:
@ -34,27 +17,14 @@
group: "{{ container_group }}"
mode: "0600"
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "postgres"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start and enable Postgres service
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: postgres.service
scope: user
state: started
enabled: true
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd
- name: Wait for Postgres to be ready (Handling the double-start)
become: true
@ -68,3 +38,4 @@
retries: 20
delay: 5
changed_when: false
when: not ansible_check_mode

View File

@ -11,7 +11,7 @@ Environment=POSTGRES_PASSWORD={{ vault_matrix_postgres_password }}
Environment=POSTGRES_DB=synapse
Environment=POSTGRES_INITDB_ARGS="--lc-collate=C --lc-ctype=C --encoding=UTF8"
Volume={{ postgres_data_dir }}:/var/lib/postgresql/data
Network=homelab.network
Network=homelab
IP=10.89.0.54
[Install]

View File

@ -3,7 +3,7 @@
- name: Ensure SELinux context for container config paths
become: true
community.general.sefcontext:
target: "{{ item }}(/.*)?"
target: "{{ (item.path if item is mapping else item) }}(/.*)?"
setype: container_file_t
state: present
loop: "{{ selinux_container_paths | default([]) }}"

View File

@ -1,17 +0,0 @@
---
# selinux_containers/tasks/storage.yml
- name: Set SELinux context for storage mounts
become: true
community.general.sefcontext:
target: "{{ item }}(/.*)?"
setype: container_file_t
state: present
loop: "{{ storage_backends }}"
when: "'nas' in group_names"
- name: Apply SELinux context (storage)
become: true
command: restorecon -R -F -v "{{ item }}"
loop: "{{ storage_backends }}"
changed_when: false
when: "'nas' in group_names"

View File

@ -7,3 +7,5 @@ servarr_firewall_rules:
- port: 8191/tcp
- port: 8989/tcp
- port: 9696/tcp
vpn_guard_enabled: false

View File

@ -1,5 +0,0 @@
---
# servarr/meta/main.yml
dependencies:
- role: selinux_containers
- role: vpn

View File

@ -1,31 +1,14 @@
#servarr/tasks/main.yml
- import_tasks: firewall.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
recurse: yes
loop: "{{ servarr_base_directories }}"
- name: Directory SELinux requirement
- name: Create servarr container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ servarr_base_directories }}"
dir_list: "{{ servarr_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Ensure systemd directory exists for rootless user
file:
path: "{{ container_config_dir }}"
state: directory
mode: '0755'
owner: "{{ container_user }}"
group: "{{ container_group }}"
- name: Setup directory for servarr config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy Quadlet files
template:
@ -34,33 +17,25 @@
loop: "{{ servarr_stack }}"
register: quadlets_deployed
- name: Ensure app config directories are writable
become: true
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
recurse: true
loop:
- "{{ radarr_dir }}/config"
- "{{ sonarr_dir }}/config"
- "{{ qbittorrent_dir }}/config"
- "{{ prowlarr_dir }}/config"
- "{{ bazarr_dir }}/config"
#- name: Force systemd reload (blocking)
# become: true
# become_user: "{{ container_user }}"
# command: systemctl --user daemon-reload
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Validate VPN and start arr stack
- name: Validate VPN
ansible.builtin.import_role:
name: vpn_guard
when:
- vpn_guard_enabled | bool
- not ansible_check_mode
- name: Skip VPN guard validation in check mode
debug:
msg: "Skipping VPN guard validation during Ansible check mode."
when:
- vpn_guard_enabled | bool
- ansible_check_mode
- name: Set fact for systemd
ansible.builtin.set_fact:
service_names: "{{ arr_suite }}"
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -1,43 +1,53 @@
---
#storage_client/tasks/main.yml
- name: Install required packages
become: true
dnf:
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop: "{{ base_storage_install_packages }}"
when: env == "prod"
- name: Install mergerfs repo package
become: true
dnf:
ansible.builtin.dnf:
name: https://github.com/trapexit/mergerfs/releases/download/2.41.1/mergerfs-2.41.1-1.el10.x86_64.rpm
disable_gpg_check: true
state: present
when: env == "prod"
- name: Ensure source data directories exist
become: true
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
loop: "{{ storage_backends }}"
- name: Set storage mount directory list
ansible.builtin.set_fact:
dir_list: "{{ storage_drives + mergerfs }}"
when: env == "prod"
- name: Create mergerfs mountpoint
- name: Ensure storage mount directories exist
ansible.builtin.import_role:
name: container_runtime
tasks_from: directory
when: env == "prod"
- name: Mount filesystem by UUID
become: true
file:
path: /mnt/nas01
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
ansible.posix.mount:
path: "{{ item.path }}"
src: "UUID={{ item.uuid }}"
fstype: "{{ item.fstype }}"
opts: "{{ item.opts }}"
dump: "{{ item.dump }}"
passno: "{{ item.passno }}"
state: mounted
loop: "{{ storage_drives }}"
when: env == "prod"
- name: Mount mergerfs pool
become: true
ansible.posix.mount:
path: "{{ mergerfs_path }}"
src: "{{ drive_a_path }}/data:{{ drive_b_path }}/data"
path: "{{ item.path }}"
src: "{{ item.src_path }}"
fstype: fuse.mergerfs
opts: defaults,allow_other,use_ino,category.create=mfs
opts: "{{ item.opts }}"
dump: "{{ item.dump | default('0') }}"
passno: "{{ item.passno | default('0') }}"
state: mounted
loop: "{{ mergerfs }}"
when: env == "prod"

View File

@ -20,6 +20,7 @@
name: tailscaled
state: started
enabled: true
when: not ansible_check_mode
- name: Bring Tailscale up (without hijacking DNS)
become: true
@ -34,3 +35,5 @@
failed_when:
- ts_up.rc != 0
- "'already authenticated' not in ts_up.stderr"
when: not ansible_check_mode
no_log: true

View File

@ -2,23 +2,14 @@
# trilium/tasks/main.yml
- import_tasks: firewall.yml
- name: Create stack and config directories
file:
path: "{{ item }}"
state: directory
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0755"
recurse: yes
loop: "{{ trilium_base_directories }}"
- name: Directory SELinux requirement
- name: Create trilium container directory
ansible.builtin.set_fact:
selinux_container_paths: "{{ trilium_base_directories }}"
dir_list: "{{ trilium_base_directories }}"
- import_role:
name: selinux_containers
tasks_from: labels
- name: Setup directory for trilium config
ansible.builtin.import_role:
name: container_runtime
tasks_from: config-setup
- name: Deploy Trilium Quadlet
template:
@ -28,24 +19,11 @@
group: "{{ container_group }}"
mode: "0644"
- name: Force systemd reload
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "trilium"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start and enable Trilium service
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: trilium.service
scope: user
state: started
enabled: true
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd

View File

@ -7,9 +7,8 @@ Requires=homelab-network.service
ContainerName=trilium
Image=docker.io/triliumnext/notes:latest
Volume={{ trilium_data_dir }}:/home/node/trilium-data:Z
Network=homelab.network
Network=homelab
NetworkAlias=trilium
IP={{ trilium_ip }}
Environment=TZ={{ timezone }}
[Service]

View File

@ -9,10 +9,11 @@
- name: Check whether xt_conntrack is available for running kernel
become: true
command: modinfo xt_conntrack
ansible.builtin.command: modinfo xt_conntrack
register: xt_conntrack_modinfo
changed_when: false
failed_when: false
check_mode: false
- name: Reboot if kernel modules were installed but running kernel cannot find xt_conntrack
become: true
@ -20,6 +21,8 @@
msg: "Rebooting to load kernel matching installed kernel-modules-extra for Gluetun firewall"
reboot_timeout: 600
when:
- not ansible_check_mode
- kernel_modules_extra_install.changed
- xt_conntrack_modinfo.rc != 0
- name: Load xt_conntrack for Gluetun firewall
@ -42,30 +45,21 @@
tasks_from: vpn
- name: Deploy Quadlet files
template:
ansible.builtin.template:
src: "gluetun.container.j2"
dest: "{{ container_config_dir }}/gluetun.container"
owner: "{{ container_user }}"
group: "{{ container_group }}"
mode: "0644"
- name: Force systemd reload (blocking)
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
command: systemctl --user daemon-reload
- name: Set fact for systemd
ansible.builtin.set_fact:
service_name: "gluetun"
- name: Wait for quadlet generation
pause:
seconds: 1
- name: Start vpn
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: gluetun.service
scope: user
state: started
- name: Execute systemd tasks
ansible.builtin.import_role:
name: container_runtime
tasks_from: systemd
- name: Wait for Gluetun container to exist
become: true
@ -76,7 +70,9 @@
delay: 2
until: gluetun_exists.rc == 0
changed_when: false
when: not ansible_check_mode
- name: Wait for Gluetun to stabilize
pause:
seconds: 5
- name: Skip Gluetun runtime validation in check mode
debug:
msg: "Skipping Gluetun container existence check during Ansible check mode."
when: ansible_check_mode

View File

@ -7,7 +7,6 @@ Wants=network-online.target
[Container]
Image=docker.io/qmcgaw/gluetun:latest
ContainerName=gluetun
UserNS=keep-id
AddCapability=NET_ADMIN
AddCapability=NET_RAW
@ -38,7 +37,8 @@ Environment=FIREWALL_ENABLED=on
Environment=DNS_ENABLED=false
Environment=FIREWALL_INPUT_PORTS=6767,7878,8080,8191,8989,9696
Network=homelab:alias=gluetun
Network=homelab
NetworkAlias=gluetun
[Install]
WantedBy=default.target

View File

@ -4,6 +4,7 @@
command: curl -s https://ipinfo.io/ip
register: host_ip
changed_when: false
when: not ansible_check_mode
- name: Get VPN public IP (via Gluetun)
become: true
@ -11,30 +12,11 @@
command: podman exec gluetun wget -qO- https://ipinfo.io/ip
register: vpn_ip
changed_when: false
when: not ansible_check_mode
- name: Fail if VPN is not active (kill switch check)
fail:
msg: "VPN is NOT active (host={{ host_ip.stdout }} vpn={{ vpn_ip.stdout }}). Aborting arr stack start."
when: host_ip.stdout == vpn_ip.stdout
#- name: Start arr stack only after VPN validation
# become: true
# become_user: "{{ container_user }}"
# systemd:
# name: "{{ item }}.service"
# enabled: yes
# state: started
# scope: user
# loop: "{{ arr_suite }}"
- name: Start arr stack only after VPN validation
become: true
become_user: "{{ container_user }}"
environment:
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
systemd:
name: "{{ item }}.service"
enabled: yes
state: started
scope: user
loop: "{{ arr_suite }}"
when:
- not ansible_check_mode
- host_ip.stdout == vpn_ip.stdout