Initial commit
This commit is contained in:
commit
c482d75d5f
|
|
@ -0,0 +1,48 @@
|
|||
# Ansible variable files (sensitive / environment-specific)
|
||||
group_vars/
|
||||
host_vars/
|
||||
commands/
|
||||
|
||||
# Inventory files (often environment-specific and not meant for sharing)
|
||||
inventory/*
|
||||
inventory/group_vars/*
|
||||
inventories/*
|
||||
|
||||
# Keep sanitized examples
|
||||
!inventory/
|
||||
!inventory/hosts.example.yml
|
||||
!inventory/group_vars/
|
||||
!inventory/group_vars/*.example.yml
|
||||
|
||||
# Codex/local assistant config
|
||||
.codex/
|
||||
|
||||
# Ansible Vault / secrets
|
||||
*.vault
|
||||
*.secret
|
||||
*.secrets
|
||||
secrets.yml
|
||||
vault.yml
|
||||
**/vault.yml
|
||||
**/secrets.yml
|
||||
|
||||
# Local environment files
|
||||
.env
|
||||
.env.*
|
||||
*.local
|
||||
|
||||
# Optional: Ansible retry files
|
||||
*.retry
|
||||
|
||||
# Python/cache
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
# OS/editor noise
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
- name: Bootstrap SSH access
|
||||
hosts: all
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure .ssh directory exists
|
||||
file:
|
||||
path: "/home/{{ bootstrap_user }}/.ssh"
|
||||
state: directory
|
||||
owner: "{{ container_user }}"
|
||||
group: "{{ container_group }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: Install authorized key
|
||||
authorized_key:
|
||||
user: "{{ bootstrap_user }}"
|
||||
state: present
|
||||
key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
|
||||
|
||||
- name: Allow passwordless sudo
|
||||
copy:
|
||||
dest: /etc/sudoers.d/{{ bootstrap_user }}
|
||||
content: "{{ bootstrap_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
mode: "0440"
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
# Global settings shared by all inventory groups.
|
||||
|
||||
timezone: America/New_York
|
||||
container_user: ansible
|
||||
container_group: ansible
|
||||
container_uid: 1000
|
||||
container_runtime_dir: "/run/user/{{ container_uid }}"
|
||||
|
||||
bootstrap_user: "{{ container_user }}"
|
||||
deployment_directory: "/home/{{ container_user }}/sbots1"
|
||||
|
||||
# Stack directories
|
||||
stack_root: "/home/{{ container_user }}/lab"
|
||||
|
||||
adguard_dir: "{{ stack_root }}/adguard"
|
||||
prowlarr_dir: "{{ stack_root }}/prowlarr"
|
||||
qbittorrent_dir: "{{ stack_root }}/qbittorrent"
|
||||
radarr_dir: "{{ stack_root }}/radarr"
|
||||
sonarr_dir: "{{ stack_root }}/sonarr"
|
||||
bazarr_dir: "{{ stack_root }}/bazarr"
|
||||
caddy_dir: "{{ stack_root }}/caddy"
|
||||
nas01_dir: /mnt/nas01
|
||||
downloads_root: "{{ nas01_dir }}"
|
||||
|
||||
# Container paths
|
||||
container_config_dir: "/home/{{ container_user }}/.config/containers/systemd"
|
||||
|
||||
adguard_base_directories:
|
||||
- "{{ stack_root }}"
|
||||
- "{{ adguard_dir }}/work/data"
|
||||
- "{{ adguard_dir }}/conf"
|
||||
|
||||
servarr_base_directories:
|
||||
- "{{ stack_root }}"
|
||||
- "{{ prowlarr_dir }}/config"
|
||||
- "{{ qbittorrent_dir }}/config"
|
||||
- "{{ radarr_dir }}/config"
|
||||
- "{{ sonarr_dir }}/config"
|
||||
- "{{ bazarr_dir }}/config"
|
||||
|
||||
caddy_base_directories:
|
||||
- "{{ stack_root }}"
|
||||
- "{{ caddy_dir }}/data"
|
||||
- "{{ caddy_dir }}/config/caddy"
|
||||
- "{{ container_config_dir }}"
|
||||
|
||||
storage_tree:
|
||||
- data
|
||||
- data/media/movies
|
||||
- data/media/tv
|
||||
- data/media/music
|
||||
- data/torrents/movies
|
||||
- data/torrents/tv
|
||||
- data/torrents/music
|
||||
- data/torrents/incomplete
|
||||
- data/torrents/complete
|
||||
|
||||
arr_suite:
|
||||
- qbittorrent
|
||||
- prowlarr
|
||||
- radarr
|
||||
- sonarr
|
||||
- bazarr
|
||||
- flaresolverr
|
||||
|
||||
base_firewall_rules:
|
||||
- service: ssh
|
||||
|
||||
adguard_firewall_rules:
|
||||
- port: 53/tcp
|
||||
- port: 53/udp
|
||||
- port: 80/tcp
|
||||
- port: 443/tcp
|
||||
- port: 3000/tcp
|
||||
|
||||
nfs_exports:
|
||||
- path: "{{ nas01_dir }}"
|
||||
clients:
|
||||
- "192.168.1.11(rw,sync,no_subtree_check,fsid=0)"
|
||||
- "192.168.1.12(rw,sync,no_subtree_check,fsid=0)"
|
||||
- "192.168.1.14(rw,sync,no_subtree_check,fsid=0)"
|
||||
|
||||
adguard_admin_user: ansible
|
||||
adguard_admin_password_hash: "replace-with-adguard-bcrypt-hash"
|
||||
|
||||
nfs_server_name: 192.168.1.10
|
||||
nfs_export_location: /
|
||||
nfs_export_path: "{{ nas01_dir }}"
|
||||
nfs_export_type: nfs4
|
||||
nfs_export_options: rw,sync,no_subtree_check,fsid=0
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
# Gluetun SELinux access
|
||||
selinux_allow_gluetun: true
|
||||
|
||||
# Servarr stack
|
||||
servarr_stack:
|
||||
- src: qbittorrent.container.j2
|
||||
dest: qbittorrent.container
|
||||
- src: prowlarr.container.j2
|
||||
dest: prowlarr.container
|
||||
- src: radarr.container.j2
|
||||
dest: radarr.container
|
||||
- src: sonarr.container.j2
|
||||
dest: sonarr.container
|
||||
- src: bazarr.container.j2
|
||||
dest: bazarr.container
|
||||
- src: flaresolverr.container.j2
|
||||
dest: flaresolverr.container
|
||||
|
||||
# Gluetun setup
|
||||
vpn_provider: mullvad
|
||||
vpn_type: wireguard
|
||||
vpn_countries: "Netherlands,USA,Canada"
|
||||
vpn_private_key: "replace-with-wireguard-private-key"
|
||||
vpn_addresses: "10.0.0.2/32"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
# Caddy
|
||||
adguard_domain: "adguard.example.{{ caddy_node }}"
|
||||
adguard_upstream: adguard:80
|
||||
|
||||
qbittorrent_domain: "qbittorrent.example.{{ caddy_node }}"
|
||||
qbittorrent_upstream: host.containers.internal:8080
|
||||
|
||||
prowlarr_domain: "prowlarr.example.{{ caddy_node }}"
|
||||
prowlarr_upstream: host.containers.internal:9696
|
||||
|
||||
radarr_domain: "radarr.example.{{ caddy_node }}"
|
||||
radarr_upstream: host.containers.internal:7878
|
||||
|
||||
sonarr_domain: "sonarr.example.{{ caddy_node }}"
|
||||
sonarr_upstream: host.containers.internal:8989
|
||||
|
||||
bazarr_domain: "bazarr.example.{{ caddy_node }}"
|
||||
bazarr_upstream: host.containers.internal:6767
|
||||
|
||||
caddy_email: "admin@example.{{ caddy_node }}"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
# Media host variables go here when the media role needs host-specific settings.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
# MergerFS
|
||||
drive_a_path: /mnt/disk1
|
||||
drive_b_path: /mnt/disk2
|
||||
|
||||
mergerfs_path: /mnt/nas01
|
||||
mergerfs_opts: "defaults,allow_other,use_ino,category.create=mfs"
|
||||
|
||||
storage_backends:
|
||||
- "{{ drive_a_path }}"
|
||||
- "{{ drive_b_path }}"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
caddy_node: com
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
caddy_node: test
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
# Workstation host variables go here when workstation-specific settings are needed.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
all:
|
||||
children:
|
||||
|
||||
test:
|
||||
hosts:
|
||||
nas-test:
|
||||
ansible_host: 192.168.122.10
|
||||
ansible_user: ansible
|
||||
env: test
|
||||
media-test:
|
||||
ansible_host: 192.168.122.11
|
||||
ansible_user: ansible
|
||||
env: test
|
||||
controller-test:
|
||||
ansible_host: 192.168.122.12
|
||||
ansible_user: ansible
|
||||
env: test
|
||||
bots-test:
|
||||
ansible_host: 192.168.122.13
|
||||
ansible_user: ansible
|
||||
env: test
|
||||
workstation-test:
|
||||
ansible_host: 192.168.122.14
|
||||
ansible_user: ansible
|
||||
env: test
|
||||
|
||||
prod:
|
||||
hosts:
|
||||
nas01:
|
||||
ansible_host: 192.168.1.10
|
||||
ansible_user: ansible
|
||||
env: prod
|
||||
media1:
|
||||
ansible_host: 192.168.1.11
|
||||
ansible_user: ansible
|
||||
env: prod
|
||||
bots1:
|
||||
ansible_host: 192.168.1.12
|
||||
ansible_user: ansible
|
||||
env: prod
|
||||
controller1:
|
||||
ansible_host: 192.168.1.13
|
||||
ansible_user: ansible
|
||||
env: prod
|
||||
workstation1:
|
||||
ansible_host: 192.168.1.14
|
||||
ansible_user: ansible
|
||||
env: prod
|
||||
|
||||
nas:
|
||||
hosts:
|
||||
nas-test:
|
||||
nas01:
|
||||
|
||||
media:
|
||||
hosts:
|
||||
media-test:
|
||||
media1:
|
||||
|
||||
controller:
|
||||
hosts:
|
||||
controller-test:
|
||||
controller1:
|
||||
|
||||
bots:
|
||||
hosts:
|
||||
bots-test:
|
||||
bots1:
|
||||
|
||||
workstation:
|
||||
hosts:
|
||||
workstation-test:
|
||||
workstation1:
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
- name: Storage
|
||||
hosts: nas
|
||||
become: true
|
||||
roles:
|
||||
- base_os
|
||||
- firewall_base
|
||||
- container_runtime
|
||||
- storage_client
|
||||
- nfs_server
|
||||
|
||||
- name: Jellyfin
|
||||
hosts: media
|
||||
become: true
|
||||
roles:
|
||||
- base_os
|
||||
- firewall_base
|
||||
- container_runtime
|
||||
|
||||
- name: Bots
|
||||
hosts: bots
|
||||
become: true
|
||||
roles:
|
||||
- base_os
|
||||
- firewall_base
|
||||
- container_runtime
|
||||
- nfs_client
|
||||
- servarr
|
||||
|
||||
- name: DNS
|
||||
hosts: controller
|
||||
become: true
|
||||
roles:
|
||||
- base_os
|
||||
- firewall_base
|
||||
- container_runtime
|
||||
- adguard
|
||||
- caddy
|
||||
|
||||
- name: Workstation Setup
|
||||
hosts: workstation
|
||||
become: true
|
||||
roles:
|
||||
- base_os
|
||||
- firewall_base
|
||||
- container_runtime
|
||||
- selinux_containers
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
# adguard/meta/main.yml
|
||||
dependencies:
|
||||
- role: selinux_containers
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
#adguard/tasks/firewall.yml
|
||||
- name: Open required firewall rules
|
||||
become: true
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ item.port | default(omit) }}"
|
||||
service: "{{ item.service | default(omit) }}"
|
||||
permanent: true
|
||||
state: enabled
|
||||
immediate: true
|
||||
loop: "{{ adguard_firewall_rules }}"
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
#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
|
||||
ansible.builtin.set_fact:
|
||||
selinux_container_paths: "{{ 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'
|
||||
|
||||
- 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: 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
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
[Unit]
|
||||
Description=AdGuard Home DNS
|
||||
Wants=network-online.target
|
||||
After=network-online.target homelab-network.service
|
||||
Requires=homelab-network.service
|
||||
|
||||
[Container]
|
||||
Image=docker.io/adguard/adguardhome:latest
|
||||
ContainerName=adguard
|
||||
Network=homelab:alias=adguard
|
||||
|
||||
Volume={{ adguard_dir }}/work:/opt/adguardhome/work
|
||||
Volume={{ adguard_dir }}/conf:/opt/adguardhome/conf
|
||||
|
||||
PublishPort=53:53/tcp
|
||||
PublishPort=53:53/udp
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
#base_os/defaults/main.yml
|
||||
base_os_install_packages:
|
||||
- slirp4netns
|
||||
- fuse-overlayfs
|
||||
- policycoreutils-python-utils
|
||||
- nano
|
||||
- tree
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
#base_os/tasks/main.yml
|
||||
- import_tasks: time_sync.yml
|
||||
|
||||
- name: Install required base packages
|
||||
become: true
|
||||
dnf:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ base_os_install_packages }}"
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
#base_os/tasks/time_sync.yml
|
||||
- name: Chrony time sync (dev only)
|
||||
when: env == "test"
|
||||
block:
|
||||
|
||||
- name: Ensure chronyd is running
|
||||
become: true
|
||||
ansible.builtin.service:
|
||||
name: chronyd
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Wait for chrony to have reachable sources
|
||||
become: true
|
||||
command: chronyc activity
|
||||
register: chrony_activity
|
||||
retries: 20
|
||||
delay: 2
|
||||
until: "'sources online' in chrony_activity.stdout and '0 sources online' not in chrony_activity.stdout"
|
||||
|
||||
- name: Force time step correction
|
||||
become: true
|
||||
command: chronyc -a makestep
|
||||
|
||||
- name: Verify system time is reasonable
|
||||
command: date
|
||||
register: date_check
|
||||
failed_when: "'2026-04-13' in date_check.stdout"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
# caddy/meta/main.yml
|
||||
dependencies:
|
||||
- role: selinux_containers
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
#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
|
||||
ansible.builtin.set_fact:
|
||||
selinux_container_paths: "{{ caddy_base_directories }}"
|
||||
|
||||
- import_role:
|
||||
name: selinux_containers
|
||||
tasks_from: labels
|
||||
|
||||
- name: Ensure Caddyfile is deployed
|
||||
template:
|
||||
src: Caddyfile.j2
|
||||
dest: "{{ caddy_dir }}/Caddyfile"
|
||||
owner: "{{ container_user }}"
|
||||
group: "{{ container_group }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: Deploy quadlet
|
||||
template:
|
||||
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: Wait for quadlet generation
|
||||
pause:
|
||||
seconds: 1
|
||||
|
||||
- name: Start service
|
||||
become: true
|
||||
become_user: "{{ container_user }}"
|
||||
systemd:
|
||||
name: caddy.service
|
||||
scope: user
|
||||
state: started
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Adguard
|
||||
{{ adguard_domain }} {
|
||||
tls internal
|
||||
reverse_proxy {{ adguard_upstream }}
|
||||
}
|
||||
|
||||
# QBittorrent
|
||||
{{ qbittorrent_domain }} {
|
||||
tls internal
|
||||
reverse_proxy {{ qbittorrent_upstream }}
|
||||
}
|
||||
|
||||
# Prowlarr
|
||||
{{ prowlarr_domain }} {
|
||||
tls internal
|
||||
reverse_proxy {{ prowlarr_upstream }}
|
||||
}
|
||||
|
||||
# Radarr
|
||||
{{ radarr_domain }} {
|
||||
tls internal
|
||||
reverse_proxy {{ radarr_upstream }}
|
||||
}
|
||||
|
||||
# Sonarr
|
||||
{{ sonarr_domain }} {
|
||||
tls internal
|
||||
reverse_proxy {{ sonarr_upstream }}
|
||||
}
|
||||
|
||||
# Bazarr
|
||||
{{ bazarr_domain }} {
|
||||
tls internal
|
||||
reverse_proxy {{ bazarr_upstream }}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
[Unit]
|
||||
Description=Caddy Reverse Proxy
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
Requires=homelab-network.service
|
||||
After=homelab-network.service
|
||||
|
||||
[Container]
|
||||
Image=docker.io/caddy:latest
|
||||
ContainerName=caddy
|
||||
Network=homelab
|
||||
|
||||
Volume={{ caddy_dir }}/Caddyfile:/etc/caddy/Caddyfile
|
||||
Volume={{ caddy_dir }}/data:/data
|
||||
Volume={{ caddy_dir }}/config:/config
|
||||
|
||||
PublishPort=80:80/tcp
|
||||
PublishPort=443:443/tcp
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
#container_runtime/defaults/main.yml
|
||||
base_runtime_install_packages:
|
||||
- podman
|
||||
- podman-docker
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#container_runtime/tasks/main.yml
|
||||
- name: Install required base packages
|
||||
become: true
|
||||
dnf:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ base_runtime_install_packages }}"
|
||||
|
||||
- name: Enable lingering for rootless containers
|
||||
become: true
|
||||
command: "loginctl enable-linger {{ container_user }}"
|
||||
args:
|
||||
creates: "/var/lib/systemd/linger/{{ container_user }}"
|
||||
|
||||
- name: Allow rootless to bind to low ports
|
||||
become: true
|
||||
sysctl:
|
||||
name: net.ipv4.ip_unprivileged_port_start
|
||||
value: '53'
|
||||
state: present
|
||||
|
||||
- name: Create stack directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ container_user }}"
|
||||
group: "{{ container_group }}"
|
||||
mode: "0755"
|
||||
recurse: yes
|
||||
loop:
|
||||
- "{{ stack_root }}"
|
||||
- "{{ container_config_dir }}"
|
||||
|
||||
- name: Configure SELinux container policies
|
||||
ansible.builtin.import_tasks: ../selinux_containers/tasks/main.yml
|
||||
|
||||
- name: Deploy Podman Network Quadlet
|
||||
become: true
|
||||
template:
|
||||
src: homelab.network.j2
|
||||
dest: "{{ container_config_dir }}/homelab.network"
|
||||
mode: "0644"
|
||||
owner: "{{ container_user }}"
|
||||
group: "{{ container_group }}"
|
||||
|
||||
#- 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: Start homelab network
|
||||
become: true
|
||||
become_user: "{{ container_user }}"
|
||||
systemd:
|
||||
name: homelab-network.service
|
||||
scope: user
|
||||
state: started
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[Network]
|
||||
NetworkName=homelab
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
#firewall_base/tasks/main.yml
|
||||
- name: Install required base packages
|
||||
become: true
|
||||
dnf:
|
||||
name:
|
||||
- firewalld
|
||||
state: present
|
||||
|
||||
- name: Enable firewalld
|
||||
become: true
|
||||
systemd:
|
||||
name: firewalld
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Open required firewall rules
|
||||
become: true
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ item.port | default(omit) }}"
|
||||
service: "{{ item.service | default(omit) }}"
|
||||
permanent: true
|
||||
state: enabled
|
||||
immediate: true
|
||||
loop: "{{ base_firewall_rules }}"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
nfs_server: "{{ nfs_server_name }}"
|
||||
nfs_export: "{{ nfs_export_location }}"
|
||||
nfs_mount_point: "{{ nfs_export_path }}"
|
||||
nfs_fstype: "{{ nfs_export_type }}"
|
||||
nfs_options: "defaults,_netdev,nofail,x-systemd.automount,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.idle-timeout=60"
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
#nfs_client/tasks/main.yml
|
||||
- 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"
|
||||
when: env == "test"
|
||||
|
||||
- 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 }}"
|
||||
when: env == "test"
|
||||
|
||||
- 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
|
||||
when: env == "test"
|
||||
|
||||
- name: Apply SELinux context for dummy NAS storage in test environment
|
||||
become: true
|
||||
command: restorecon -Rv "{{ nfs_mount_point }}"
|
||||
changed_when: false
|
||||
when: env == "test"
|
||||
|
||||
- name: Install required NFS client packages
|
||||
become: true
|
||||
dnf:
|
||||
name: nfs-utils
|
||||
state: present
|
||||
when: env != "test"
|
||||
|
||||
- name: Check whether NFS mount point is already mounted
|
||||
become: true
|
||||
command: findmnt --mountpoint "{{ nfs_mount_point }}"
|
||||
register: nfs_mount_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: env != "test"
|
||||
|
||||
- name: Create NFS mount point
|
||||
become: true
|
||||
file:
|
||||
path: "{{ nfs_mount_point }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when:
|
||||
- env != "test"
|
||||
- nfs_mount_check.rc != 0
|
||||
|
||||
- name: Configure NFS mount
|
||||
become: true
|
||||
ansible.posix.mount:
|
||||
path: "{{ nfs_mount_point }}"
|
||||
src: "{{ nfs_server }}:{{ nfs_export }}"
|
||||
fstype: "{{ nfs_fstype }}"
|
||||
opts: "{{ nfs_options }}"
|
||||
state: mounted
|
||||
when: env != "test"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#nfw_server/defaults/main.yml
|
||||
nfs_packages:
|
||||
- nfs-utils
|
||||
- nfs-server
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
#nfs_server/tasks/main.yml
|
||||
- name: Install NFS utilities
|
||||
become: true
|
||||
dnf:
|
||||
name: nfs-utils
|
||||
state: present
|
||||
loop: "{{ nfs_packages }}"
|
||||
|
||||
- name: Build NFS exports entries
|
||||
become: true
|
||||
lineinfile:
|
||||
path: /etc/exports
|
||||
line: "{{ item.path }} {{ item.clients | join(' ') }}"
|
||||
create: true
|
||||
state: present
|
||||
loop: "{{ nfs_exports }}"
|
||||
|
||||
- name: Export NFS shares
|
||||
become: true
|
||||
command: exportfs -ra
|
||||
changed_when: false
|
||||
|
||||
- name: Enable and start NFS server
|
||||
become: true
|
||||
systemd:
|
||||
name: nfs-server
|
||||
enabled: true
|
||||
state: started
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# selinux_containers/tasks/labels.yml
|
||||
- name: Ensure SELinux context for container config paths
|
||||
become: true
|
||||
community.general.sefcontext:
|
||||
target: "{{ item }}(/.*)?"
|
||||
setype: container_file_t
|
||||
state: present
|
||||
loop: "{{ selinux_container_paths | default([]) }}"
|
||||
when:
|
||||
- selinux_container_paths is defined
|
||||
- selinux_container_paths | length > 0
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
#selinux_containers/tasks/main.yml
|
||||
# -----------------------------
|
||||
# Set SELinux
|
||||
# -----------------------------
|
||||
- name: Set SELinux context for stack
|
||||
become: true
|
||||
community.general.sefcontext:
|
||||
target: "{{ stack_root }}(/.*)?"
|
||||
setype: container_file_t
|
||||
state: present
|
||||
|
||||
# -----------------------------
|
||||
# APPLY SELinux
|
||||
# -----------------------------
|
||||
- name: Apply SELinux context (stack)
|
||||
become: true
|
||||
command: restorecon -Rv "{{ stack_root }}"
|
||||
changed_when: false
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
# 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"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# selinux_containers/tasks/vpn.yml
|
||||
# -----------------------------
|
||||
# SELinux BOOLEANS
|
||||
# -----------------------------
|
||||
- name: Allow containers to use devices (needed for Gluetun / /dev/net/tun)
|
||||
become: true
|
||||
ansible.posix.seboolean:
|
||||
name: container_use_devices
|
||||
state: true
|
||||
persistent: true
|
||||
when: selinux_allow_gluetun | default(false)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
#servarr/defaults/main.yml
|
||||
servarr_firewall_rules:
|
||||
- port: 8080/tcp
|
||||
- port: 6767/tcp
|
||||
- port: 7878/tcp
|
||||
- port: 8191/tcp
|
||||
- port: 8989/tcp
|
||||
- port: 9696/tcp
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
# servarr/meta/main.yml
|
||||
dependencies:
|
||||
- role: selinux_containers
|
||||
- role: vpn
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
#servarr/tasks/firewall.yml
|
||||
- name: Open required firewall rules
|
||||
become: true
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ item.port | default(omit) }}"
|
||||
service: "{{ item.service | default(omit) }}"
|
||||
permanent: true
|
||||
state: enabled
|
||||
immediate: true
|
||||
loop: "{{ servarr_firewall_rules }}"
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#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
|
||||
ansible.builtin.set_fact:
|
||||
selinux_container_paths: "{{ 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: Deploy Quadlet files
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ container_config_dir }}/{{ item.dest }}"
|
||||
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
|
||||
ansible.builtin.import_role:
|
||||
name: vpn_guard
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[Unit]
|
||||
Description=Bazarr Subtitle Manager
|
||||
Requires=gluetun.service
|
||||
After=gluetun.service
|
||||
|
||||
[Container]
|
||||
ContainerName=bazarr
|
||||
Image=lscr.io/linuxserver/bazarr:latest
|
||||
|
||||
Environment=PUID=0
|
||||
Environment=PGID=0
|
||||
Environment=TZ=America/New_York
|
||||
Network=container:gluetun
|
||||
Volume={{ bazarr_dir }}/config:/config
|
||||
Volume={{ downloads_root }}:/data
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=Flaresolverr Cloudflare Solver
|
||||
Requires=gluetun.service
|
||||
After=gluetun.service
|
||||
|
||||
[Container]
|
||||
ContainerName=flaresolverr
|
||||
Image=ghcr.io/flaresolverr/flaresolverr:latest
|
||||
|
||||
Environment=TZ=America/New_York
|
||||
Environment=LOG_LEVEL=info
|
||||
Network=container:gluetun
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=Prowlarr Indexer Manager
|
||||
Requires=gluetun.service
|
||||
After=gluetun.service
|
||||
|
||||
[Container]
|
||||
ContainerName=prowlarr
|
||||
Image=lscr.io/linuxserver/prowlarr:latest
|
||||
|
||||
Environment=PUID=0
|
||||
Environment=PGID=0
|
||||
Environment=TZ=America/New_York
|
||||
Network=container:gluetun
|
||||
Volume={{ prowlarr_dir }}/config:/config
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
[Unit]
|
||||
Description=qBittorrent Downloader
|
||||
Requires=gluetun.service
|
||||
After=gluetun.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Container]
|
||||
Image=lscr.io/linuxserver/qbittorrent:latest
|
||||
ContainerName=qbittorrent
|
||||
|
||||
Environment=PUID=0
|
||||
Environment=PGID=0
|
||||
Environment=TZ={{ timezone }}
|
||||
Environment=WEBUI_PORT=8080
|
||||
Environment=WEBUI_ADDRESS=0.0.0.0
|
||||
|
||||
Network=container:gluetun
|
||||
|
||||
Volume={{ qbittorrent_dir }}/config:/config
|
||||
Volume={{ downloads_root }}:/data
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
[Unit]
|
||||
Description=Radarr Movie Manager
|
||||
Requires=gluetun.service
|
||||
After=gluetun.service
|
||||
|
||||
[Container]
|
||||
ContainerName=radarr
|
||||
Image=lscr.io/linuxserver/radarr:latest
|
||||
|
||||
Environment=PUID=0
|
||||
Environment=PGID=0
|
||||
Environment=TZ=America/New_York
|
||||
|
||||
Network=container:gluetun
|
||||
|
||||
Volume={{ radarr_dir }}/config:/config
|
||||
Volume={{ downloads_root }}:/data
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
[Unit]
|
||||
Description=Sonarr TV Manager
|
||||
Requires=gluetun.service
|
||||
After=gluetun.service
|
||||
|
||||
[Container]
|
||||
ContainerName=sonarr
|
||||
Image=lscr.io/linuxserver/sonarr:latest
|
||||
|
||||
Environment=PUID=0
|
||||
Environment=PGID=0
|
||||
Environment=TZ=America/New_York
|
||||
|
||||
Network=container:gluetun
|
||||
|
||||
Volume={{ sonarr_dir }}/config:/config
|
||||
Volume={{ downloads_root }}:/data
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
#storage_client/defaults/main.yml
|
||||
base_storage_install_packages:
|
||||
- epel-release
|
||||
- fuse
|
||||
- fuse3
|
||||
- policycoreutils-python-utils
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
#storage_client/tasks/main.yml
|
||||
- name: Install required packages
|
||||
become: true
|
||||
dnf:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ base_storage_install_packages }}"
|
||||
|
||||
- name: Install mergerfs repo package
|
||||
become: true
|
||||
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
|
||||
|
||||
- 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: Create mergerfs mountpoint
|
||||
become: true
|
||||
file:
|
||||
path: /mnt/nas01
|
||||
state: directory
|
||||
owner: "{{ container_user }}"
|
||||
group: "{{ container_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Mount mergerfs pool
|
||||
become: true
|
||||
ansible.posix.mount:
|
||||
path: "{{ mergerfs_path }}"
|
||||
src: "{{ drive_a_path }}/data:{{ drive_b_path }}/data"
|
||||
fstype: fuse.mergerfs
|
||||
opts: defaults,allow_other,use_ino,category.create=mfs
|
||||
state: mounted
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
#vpn/tasks/main.yml
|
||||
- name: Install kernel extra modules for Gluetun firewall
|
||||
become: true
|
||||
dnf:
|
||||
name: kernel-modules-extra
|
||||
state: present
|
||||
register: kernel_modules_extra_install
|
||||
|
||||
- name: Check whether xt_conntrack is available for running kernel
|
||||
become: true
|
||||
command: modinfo xt_conntrack
|
||||
register: xt_conntrack_modinfo
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Reboot if kernel modules were installed but running kernel cannot find xt_conntrack
|
||||
become: true
|
||||
reboot:
|
||||
msg: "Rebooting to load kernel matching installed kernel-modules-extra for Gluetun firewall"
|
||||
reboot_timeout: 600
|
||||
when:
|
||||
- xt_conntrack_modinfo.rc != 0
|
||||
|
||||
- name: Load xt_conntrack for Gluetun firewall
|
||||
become: true
|
||||
community.general.modprobe:
|
||||
name: xt_conntrack
|
||||
state: present
|
||||
|
||||
- name: Persist xt_conntrack module
|
||||
become: true
|
||||
copy:
|
||||
dest: /etc/modules-load.d/gluetun.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
xt_conntrack
|
||||
|
||||
- name: Configure SELinux for Gluetun device access
|
||||
ansible.builtin.import_role:
|
||||
name: selinux_containers
|
||||
tasks_from: vpn
|
||||
|
||||
- name: Deploy Quadlet files
|
||||
template:
|
||||
src: "gluetun.container.j2"
|
||||
dest: "{{ container_config_dir }}/gluetun.container"
|
||||
|
||||
- name: Force systemd reload (blocking)
|
||||
become: true
|
||||
become_user: "{{ container_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "{{ container_runtime_dir }}"
|
||||
command: systemctl --user daemon-reload
|
||||
|
||||
- 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: Wait for Gluetun container to exist
|
||||
become: true
|
||||
become_user: "{{ container_user }}"
|
||||
command: podman container exists gluetun
|
||||
register: gluetun_exists
|
||||
retries: 20
|
||||
delay: 2
|
||||
until: gluetun_exists.rc == 0
|
||||
changed_when: false
|
||||
|
||||
- name: Wait for Gluetun to stabilize
|
||||
pause:
|
||||
seconds: 5
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
[Unit]
|
||||
Description=Gluetun VPN Client
|
||||
Requires=homelab-network.service
|
||||
After=network-online.target homelab-network.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=docker.io/qmcgaw/gluetun:latest
|
||||
ContainerName=gluetun
|
||||
UserNS=keep-id
|
||||
|
||||
AddCapability=NET_ADMIN
|
||||
AddCapability=NET_RAW
|
||||
AddDevice=/dev/net/tun:/dev/net/tun
|
||||
|
||||
# Port Mappings:
|
||||
# 6767 Bazarr
|
||||
# 7878 Radarr
|
||||
# 8080 qBittorrent
|
||||
# 8191 Flaresolverr
|
||||
# 8989 Sonarr
|
||||
# 9696 Prowlarr
|
||||
|
||||
PublishPort=6767:6767
|
||||
PublishPort=7878:7878
|
||||
PublishPort=8080:8080
|
||||
PublishPort=8191:8191
|
||||
PublishPort=8989:8989
|
||||
PublishPort=9696:9696
|
||||
|
||||
Environment=VPN_SERVICE_PROVIDER=mullvad
|
||||
Environment=VPN_TYPE=wireguard
|
||||
Environment=WIREGUARD_PRIVATE_KEY={{ vpn_private_key }}
|
||||
Environment=WIREGUARD_ADDRESSES={{ vpn_addresses }}
|
||||
|
||||
Environment=FIREWALL_ENABLED=on
|
||||
#Environment=FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=off
|
||||
Environment=DNS_ENABLED=false
|
||||
Environment=FIREWALL_INPUT_PORTS=6767,7878,8080,8191,8989,9696
|
||||
|
||||
Network=homelab:alias=gluetun
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
#vpn_guard/tasks/main.yml
|
||||
- name: Get host public IP
|
||||
command: curl -s https://ipinfo.io/ip
|
||||
register: host_ip
|
||||
changed_when: false
|
||||
|
||||
- name: Get VPN public IP (via Gluetun)
|
||||
become: true
|
||||
become_user: "{{ container_user }}"
|
||||
command: podman exec gluetun wget -qO- https://ipinfo.io/ip
|
||||
register: vpn_ip
|
||||
changed_when: false
|
||||
|
||||
- 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 }}"
|
||||
Loading…
Reference in New Issue