--- # Ansible Playbook for *arr Stack Deployment # Based on successful VPS deployment at YOUR_VPS_IP_ADDRESS # # Usage: ansible-playbook -i inventory/production.yml ansible-deployment.yml # # This playbook deploys a complete media automation stack including: # - Prowlarr (indexer management) # - Sonarr (TV shows) # - Radarr (movies) # - Lidarr (music) # - Whisparr (adult content) # - Bazarr (subtitles) # - Jellyseerr (request management) # - SABnzbd (usenet downloader) # - Deluge (torrent downloader) # - Plex (media server) # - Tautulli (Plex analytics) # - Gluetun (VPN container) - name: Deploy Complete *arr Media Stack hosts: all become: yes vars: # Stack configuration stack_name: "arr-stack" base_path: "/home/docker" compose_file: "{{ base_path }}/compose/docker-compose.yml" # Network configuration tailscale_ip: "YOUR_TAILSCALE_IP" # Your current Tailscale IP # Service ports (current working configuration) services: prowlarr: 9696 sonarr: 8989 radarr: 7878 lidarr: 8686 whisparr: 6969 bazarr: 6767 jellyseerr: 5055 sabnzbd: 8080 deluge: 8081 plex: 32400 tautulli: 8181 # VPN configuration (parameterized) vpn_provider: "{{ vault_vpn_provider | default('nordvpn') }}" vpn_username: "{{ vault_vpn_username }}" vpn_password: "{{ vault_vpn_password }}" # API Keys (to be generated or provided) api_keys: prowlarr: "{{ vault_prowlarr_api_key | default('') }}" sonarr: "{{ vault_sonarr_api_key | default('') }}" radarr: "{{ vault_radarr_api_key | default('') }}" lidarr: "{{ vault_lidarr_api_key | default('') }}" whisparr: "{{ vault_whisparr_api_key | default('') }}" bazarr: "{{ vault_bazarr_api_key | default('') }}" jellyseerr: "{{ vault_jellyseerr_api_key | default('') }}" sabnzbd: "{{ vault_sabnzbd_api_key | default('') }}" tasks: - name: System Requirements Check block: - name: Check OS version ansible.builtin.setup: filter: ansible_distribution* - name: Verify minimum requirements ansible.builtin.assert: that: - ansible_memtotal_mb >= 4096 - ansible_architecture == "x86_64" fail_msg: "System does not meet minimum requirements (4GB RAM, x86_64)" success_msg: "System requirements verified" - name: Display system info ansible.builtin.debug: msg: | System: {{ ansible_distribution }} {{ ansible_distribution_version }} Memory: {{ ansible_memtotal_mb }}MB Architecture: {{ ansible_architecture }} Disk Space: {{ ansible_mounts[0].size_total // 1024 // 1024 // 1024 }}GB - name: Docker Environment Setup block: - name: Install Docker dependencies ansible.builtin.apt: name: - apt-transport-https - ca-certificates - curl - gnupg - lsb-release state: present update_cache: yes - name: Add Docker GPG key ansible.builtin.apt_key: url: https://download.docker.com/linux/ubuntu/gpg state: present - name: Add Docker repository ansible.builtin.apt_repository: repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" state: present - name: Install Docker ansible.builtin.apt: name: - docker-ce - docker-ce-cli - containerd.io - docker-compose-plugin state: present update_cache: yes - name: Start and enable Docker ansible.builtin.systemd: name: docker state: started enabled: yes - name: Add user to docker group ansible.builtin.user: name: "{{ ansible_user }}" groups: docker append: yes - name: Create Directory Structure block: - name: Create base directories ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0755' loop: - "{{ base_path }}" - "{{ base_path }}/compose" - "{{ base_path }}/prowlarr" - "{{ base_path }}/sonarr" - "{{ base_path }}/radarr" - "{{ base_path }}/lidarr" - "{{ base_path }}/whisparr" - "{{ base_path }}/bazarr" - "{{ base_path }}/jellyseerr" - "{{ base_path }}/sabnzbd" - "{{ base_path }}/deluge" - "{{ base_path }}/plex" - "{{ base_path }}/tautulli" - "{{ base_path }}/gluetun" - "{{ base_path }}/media" - "{{ base_path }}/downloads" - name: Deploy Docker Compose Configuration block: - name: Generate docker-compose.yml ansible.builtin.template: src: docker-compose.yml.j2 dest: "{{ compose_file }}" owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0644' notify: restart stack - name: Create environment file ansible.builtin.template: src: .env.j2 dest: "{{ base_path }}/compose/.env" owner: "{{ ansible_user }}" group: "{{ ansible_user }}" mode: '0600' notify: restart stack - name: Deploy Stack block: - name: Pull latest images ansible.builtin.command: cmd: docker compose pull chdir: "{{ base_path }}/compose" become_user: "{{ ansible_user }}" - name: Start the stack ansible.builtin.command: cmd: docker compose up -d chdir: "{{ base_path }}/compose" become_user: "{{ ansible_user }}" - name: Wait for services to be ready ansible.builtin.wait_for: host: "{{ tailscale_ip }}" port: "{{ item.value }}" timeout: 300 loop: "{{ services | dict2items }}" when: item.key != 'plex' # Plex binds to 0.0.0.0 - name: Wait for Plex specifically ansible.builtin.wait_for: host: "0.0.0.0" port: "{{ services.plex }}" timeout: 300 - name: Verify Deployment block: - name: Check container health ansible.builtin.command: cmd: docker ps --filter "status=running" --format "table {{.Names}}\t{{.Status}}" register: container_status become_user: "{{ ansible_user }}" - name: Display container status ansible.builtin.debug: var: container_status.stdout_lines - name: Test service endpoints ansible.builtin.uri: url: "http://{{ tailscale_ip }}:{{ item.value }}" method: GET status_code: [200, 302, 307, 403] # Accept various redirect/auth responses loop: "{{ services | dict2items }}" when: item.key != 'plex' register: service_tests - name: Test Plex endpoint ansible.builtin.uri: url: "http://{{ ansible_default_ipv4.address }}:{{ services.plex }}/web" method: GET status_code: [200, 302, 307] register: plex_test - name: Display test results ansible.builtin.debug: msg: "All services are responding correctly!" when: service_tests is succeeded and plex_test is succeeded handlers: - name: restart stack ansible.builtin.command: cmd: docker compose down && docker compose up -d chdir: "{{ base_path }}/compose" become_user: "{{ ansible_user }}" # Post-deployment configuration tasks - name: Configure Service Integrations hosts: all become: no vars: prowlarr_url: "http://{{ tailscale_ip }}:9696" sonarr_url: "http://{{ tailscale_ip }}:8989" radarr_url: "http://{{ tailscale_ip }}:7878" tasks: - name: Wait for API endpoints to be ready ansible.builtin.uri: url: "{{ item }}/api/v1/system/status" method: GET headers: X-Api-Key: "{{ api_keys[item.split(':')[2].split('/')[0]] }}" status_code: 200 loop: - "{{ prowlarr_url }}" - "{{ sonarr_url }}" - "{{ radarr_url }}" retries: 10 delay: 30 when: api_keys[item.split(':')[2].split('/')[0]] != "" - name: Display post-deployment information ansible.builtin.debug: msg: | 🎉 *arr Stack Deployment Complete! 📊 Service URLs: - Prowlarr: {{ prowlarr_url }} - Sonarr: {{ sonarr_url }} - Radarr: {{ radarr_url }} - Lidarr: http://{{ tailscale_ip }}:8686 - Whisparr: http://{{ tailscale_ip }}:6969 - Bazarr: http://{{ tailscale_ip }}:6767 - Jellyseerr: http://{{ tailscale_ip }}:5055 - SABnzbd: http://{{ tailscale_ip }}:8080 - Deluge: http://{{ tailscale_ip }}:8081 - Plex: http://{{ ansible_default_ipv4.address }}:32400 - Tautulli: http://{{ tailscale_ip }}:8181 🔧 Next Steps: 1. Configure indexers in Prowlarr 2. Set up download clients in *arr apps 3. Configure media libraries in Plex 4. Set up request workflows in Jellyseerr 📁 Data Locations: - Config: {{ base_path }}/[service-name] - Media: {{ base_path }}/media - Downloads: {{ base_path }}/downloads