🎬 ARR Suite Template Bootstrap - Complete Media Automation Stack Features: - 16 production services (Prowlarr, Sonarr, Radarr, Plex, etc.) - One-command Ansible deployment - VPN-protected downloads via Gluetun - Tailscale secure access - Production-ready security (UFW, Fail2Ban) - Automated backups and monitoring - Comprehensive documentation Ready for customization and deployment to any VPS. Co-authored-by: openhands <openhands@all-hands.dev>
298 lines
9.7 KiB
YAML
298 lines
9.7 KiB
YAML
---
|
|
# 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 |