--- - hosts: internal-apps become: true vars_files: - ./vars.yml tasks: - name: Update Packages ansible.builtin.dnf: update_cache: yes - name: Install Packages ansible.builtin.dnf: name: - vim - podman - fail2ban - python3-libdnf5 state: present - name: Create directories file: path: "{{ item }}" state: directory mode: '0755' loop: "{{ directories }}" - name: Create Jellyfin directories file: path: "{{ item }}" state: directory mode: '0777' loop: "{{ directories_jellyfin }}" - name: Create internal podman network containers.podman.podman_network: name: npm_internal ip_range: 10.10.0.128/25 subnet: 10.10.0.0/24 gateway: 10.10.0.1 state: present - name: Ensure WireGuard kernel module is loaded ansible.builtin.modprobe: name: wireguard state: present - name: Persist WireGuard kernel module across reboots ansible.builtin.copy: dest: /etc/modules-load.d/wg-easy.conf content: | wireguard nft_masq mode: '0644' - name: Enable IP forwarding on host ansible.posix.sysctl: name: net.ipv4.ip_forward value: '1' sysctl_set: true state: present reload: true - name: Enable source valid mark on host ansible.posix.sysctl: name: net.ipv4.conf.all.src_valid_mark value: '1' sysctl_set: true state: present reload: true - name: Raise vm.max_map_count for Wazuh indexer ansible.posix.sysctl: name: vm.max_map_count value: '262144' sysctl_set: true state: present reload: true - name: pull images containers.podman.podman_image: name: "{{ item }}" loop: "{{ images }}" - name: Deploy Nginx Proxy Manager containers.podman.podman_container: image: "docker.io/jc21/nginx-proxy-manager:latest" name: npm restart_policy: always recreate: true state: started ports: - "80:80" - "81:81" - "443:443" volumes: - "/opt/apps/npm:/data:z" - "/opt/apps/letsencrypt:/etc/letsencrypt:z" network: - npm_internal ip: '10.10.0.19' - name: Deploy Gitea containers.podman.podman_container: image: docker.gitea.com/gitea:nightly name: gitea env: USER_UID: 1000 USER_GID: 1000 restart_policy: always recreate: true state: started volumes: - /opt/apps/gitea:/data:z - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro network: - npm_internal ip: '10.10.0.25' ports: # - "3000:3000" - "222:22" - name: Deploy Jellyfin containers.podman.podman_container: image: lscr.io/linuxserver/jellyfin:latest name: jellyfin env: USER_UID: 1000 USER_GID: 1000 JELLYFIN_PublishedServerUrl: http://jellyfin.boxswapper.io restart_policy: always recreate: true state: started volumes: - /opt/apps/jellyfin/config:/config:z - /opt/apps/jellyfin/tvseries:/data/tvshows:z - /opt/apps/jellyfin/movies:/data/movies:z - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro network: - npm_internal ip: '10.10.0.30' # ports: # - 8096:8096 # - 8920:8920 #optional # - 7359:7359/udp #optional # - 1900:1900/udp #optional # WireGuard Easy VPN (v15) — UI at 10.10.0.36:51821 on npm_internal, fronted by NPM # (vpn.boxswapper.io -> http://10.10.0.36:51821). Login: admin / wireguard_ui_password from vars.yml. # v15 init is ONE-TIME: the INIT_* envs below are consumed only when /etc/wireguard/wg-easy.db is ABSENT. # Force a re-init (wipes clients + server keys): podman rm -f wg-easy && rm -rf /opt/apps/wireguard, then re-run. # NAT from VPN clients to LAN/apps (10.10.1.0/24 -> 10.10.0.0/24) uses NFTABLES hooks — # wg-easy's iptables-legacy hooks cannot init the nat table inside a podman netns # ("Table does not exist"), which makes wg-quick up fail and every client API call # 500 with "Unable to access interface". scripts/patch-wg-hooks.py rewrites the hooks # to nft rules and the tasks below restart wg-easy to apply them. See the official # wg-easy "Podman + nftables" tutorial. # EXTERNAL ACCESS: router MUST forward UDP 51820 -> 192.168.0.98. Test from LTE, # not your LAN — NAT hairpin breaks public-IP handshakes from inside the network. - name: Deploy WireGuard Easy (wg-easy) containers.podman.podman_container: name: wg-easy image: "{{ wireguard_image }}" restart_policy: always recreate: true state: started env: INIT_ENABLED: "true" INIT_USERNAME: "{{ wireguard_ui_username }}" INIT_PASSWORD: "{{ wireguard_ui_password }}" INIT_HOST: "{{ wireguard_host }}" INIT_PORT: "{{ wireguard_port }}" INIT_DNS: "{{ wireguard_default_dns }}" INIT_IPV4_CIDR: "{{ wireguard_cidr }}" INIT_IPV6_CIDR: "{{ wireguard_ipv6_cidr }}" INIT_ALLOWED_IPS: "{{ wireguard_allowed_ips }}" PORT: "{{ wireguard_ui_port }}" INSECURE: "true" DISABLE_VERSION_CHECK: "true" ports: - "{{ wireguard_port }}:{{ wireguard_port }}/udp" - "{{ wireguard_ui_port }}:{{ wireguard_ui_port }}/tcp" volumes: - "/opt/apps/wireguard:/etc/wireguard:z" - "/lib/modules:/lib/modules:ro" cap_add: - NET_ADMIN - SYS_MODULE - NET_RAW sysctl: net.ipv4.ip_forward: "1" net.ipv4.conf.all.src_valid_mark: "1" net.ipv6.conf.all.disable_ipv6: "0" net.ipv6.conf.all.forwarding: "1" net.ipv6.conf.default.forwarding: "1" network: - npm_internal ip: '{{ wireguard_ip }}' - name: Copy wg-easy hook repair script ansible.builtin.copy: src: ./scripts/patch-wg-hooks.py dest: /opt/apps/wireguard/patch-wg-hooks.py owner: root group: root mode: '0755' - name: Replace iptables hooks with nftables (iptables-legacy broken in podman) ansible.builtin.command: python3 /opt/apps/wireguard/patch-wg-hooks.py register: wg_hooks_patch changed_when: "'patched hooks' in wg_hooks_patch.stdout" - name: Restart wg-easy to apply nft hooks ansible.builtin.command: podman restart wg-easy when: "'patched hooks' in wg_hooks_patch.stdout" - name: Wait for WireGuard Easy web UI ansible.builtin.wait_for: host: '{{ wireguard_ip }}' port: '{{ wireguard_ui_port }}' delay: 5 timeout: 120 # Wazuh SIEM (single-node: indexer + manager + dashboard) on npm_internal # Dashboard is NOT exposed on host ports — NPM proxies wazuh.boxswapper.io to https://10.10.0.40:5601 # DNS record must be grey-cloud (Cloudflare 523 lesson from vpn.boxswapper.io), cert via NPM # Config is copied from ./wazuh-docker/config before first deploy - name: Copy Wazuh config files to host ansible.builtin.copy: src: ./wazuh-docker/config/ dest: /opt/apps/wazuh/config/ owner: root group: root mode: preserve force: true - name: Generate Wazuh certificates ansible.builtin.command: podman run --rm -e CERT_TOOL_VERSION=4.14 -v /opt/apps/wazuh/config/certs.yml:/config/certs.yml:z -v /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/:/certificates:z docker.io/wazuh/wazuh-certs-generator:0.0.3 args: creates: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem - name: Copy root CA as manager root CA ansible.builtin.copy: src: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem dest: /opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca-manager.pem remote_src: true force: false - name: Deploy Wazuh indexer containers.podman.podman_container: name: wazuh.indexer image: "docker.io/wazuh/wazuh-indexer:{{ wazuh_version }}" hostname: wazuh.indexer restart_policy: always recreate: true state: started env: OPENSEARCH_JAVA_OPTS: "-Xms1g -Xmx1g" ulimit: - "memlock=-1:-1" - "nofile=65536:65536" volumes: - "wazuh-indexer-data:/var/lib/wazuh-indexer" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml:z" - "/opt/apps/wazuh/config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml:z" network: - npm_internal ip: '{{ wazuh_indexer_ip }}' - name: Wait for Wazuh indexer to be ready ansible.builtin.wait_for: host: '{{ wazuh_indexer_ip }}' port: 9200 delay: 10 timeout: 300 - name: Deploy Wazuh manager containers.podman.podman_container: name: wazuh.manager image: "docker.io/wazuh/wazuh-manager:{{ wazuh_version }}" hostname: wazuh.manager restart_policy: always recreate: true state: started env: INDEXER_URL: "https://wazuh.indexer:9200" INDEXER_USERNAME: "admin" INDEXER_PASSWORD: "SecretPassword" FILEBEAT_SSL_VERIFICATION_MODE: "full" SSL_CERTIFICATE_AUTHORITIES: "/etc/ssl/root-ca.pem" SSL_CERTIFICATE: "/etc/ssl/filebeat.pem" SSL_KEY: "/etc/ssl/filebeat.key" API_USERNAME: "wazuh-wui" API_PASSWORD: "MyS3cr37P450r.*-" ulimit: - "memlock=-1:-1" - "nofile=655360:655360" ports: - "1514:1514" - "1515:1515" - "514:514/udp" - "55000:55000" volumes: - "wazuh_api_configuration:/var/ossec/api/configuration" - "wazuh_etc:/var/ossec/etc" - "wazuh_logs:/var/ossec/logs" - "wazuh_queue:/var/ossec/queue" - "wazuh_var_multigroups:/var/ossec/var/multigroups" - "wazuh_active_response:/var/ossec/active-response/bin" - "wazuh_wodles:/var/ossec/wodles" - "filebeat_etc:/etc/filebeat" - "filebeat_var:/var/lib/filebeat" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key:z" - "/opt/apps/wazuh/config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf:z" network: - npm_internal ip: '{{ wazuh_manager_ip }}' - name: Deploy Wazuh dashboard containers.podman.podman_container: name: wazuh.dashboard image: "docker.io/wazuh/wazuh-dashboard:{{ wazuh_version }}" hostname: wazuh.dashboard restart_policy: always recreate: true state: started env: INDEXER_USERNAME: "admin" INDEXER_PASSWORD: "SecretPassword" WAZUH_API_URL: "https://wazuh.manager" DASHBOARD_USERNAME: "kibanaserver" DASHBOARD_PASSWORD: "kibanaserver" API_USERNAME: "wazuh-wui" API_PASSWORD: "MyS3cr37P450r.*-" volumes: - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem:z" - "/opt/apps/wazuh/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:z" - "/opt/apps/wazuh/config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:z" - "/opt/apps/wazuh/config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml:z" - "wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config" - "wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom" network: - npm_internal ip: '{{ wazuh_dashboard_ip }}' #----N8N deployment, no GPU avail for deployment ------# # - name: Deploy n8n # containers.podman.podman_container: # image: docker.n8n.io/n8nio/n8n # name: n8n # env: # N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: true # N8N_RUNNERS_ENABLED: true # restart_policy: always # recreate: true # state: started # volumes: # - /opt/apps/n8n/data:/home/node/.n8n:z # - /opt/apps/n8n/files:/files:z # - /usr/share/zoneinfo/America/Denver:/etc/timezone:ro # - /etc/localtime:/etc/localtime:ro # network: # - npm_internal # ip: '10.10.0.35' # # ports: # # - 5678:5678