No description
  • Shell 69.3%
  • Jinja 30.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jeremy Collin c35bb176e7
fix(arr_configure): fix Lidarr root folder — add required name and profile IDs
Lidarr v1 API requires name, defaultQualityProfileId, and
defaultMetadataProfileId on POST /rootfolder. Look up the first
available quality and metadata profile IDs before posting.
Sonarr/Radarr loop now excludes Lidarr via rejectattr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:25:47 +02:00
.claude remove jellyseerr role 2026-05-21 10:18:36 +02:00
.github/workflows ci: upload ansible-lint output artifact on failure 2026-05-20 17:01:07 +02:00
group_vars feat(arr_configure): enable forms auth on all apps 2026-05-21 14:20:04 +02:00
roles fix(arr_configure): fix Lidarr root folder — add required name and profile IDs 2026-05-21 14:25:47 +02:00
.ansible-lint ci: fix yamllint config and skip two architectural ansible-lint rules 2026-05-20 11:52:02 +02:00
.gitignore Add project skeleton: playbook, inventories, and all.yml config 2026-05-20 10:37:07 +02:00
.yamllint.yml fix(lint): fix remaining partial-become and yamllint v24 incompatibility 2026-05-20 12:05:37 +02:00
barr-vm.sh fix(lint): resolve all ShellCheck and ansible-lint violations 2026-05-20 11:51:44 +02:00
install.yml remove homarr and nodejs roles 2026-05-21 11:20:37 +02:00
inventory.ini Add project skeleton: playbook, inventories, and all.yml config 2026-05-20 10:37:07 +02:00
inventory_local.ini Add project skeleton: playbook, inventories, and all.yml config 2026-05-20 10:37:07 +02:00
README.md Add README — full setup guide and architecture overview 2026-05-20 10:49:07 +02:00

barr

Ansible playbook that installs and fully wires the *arr media automation stack on a Debian server. A single ansible-playbook run takes a fresh Debian 13 (Trixie) machine to a complete, interconnected media server with reverse proxy, monitoring, and a test VM harness.

What gets installed

Service Port Purpose
Prowlarr 9696 Indexer manager — syncs to all arr apps
Sonarr 8989 TV series automation
Radarr 7878 Movie automation
Lidarr 8686 Music automation
Bazarr 6767 Subtitle automation
qBittorrent 8080 Download client
FlareSolverr 8191 Cloudflare bypass for Prowlarr
Unpackerr Extracts completed downloads
Recyclarr Syncs TRaSH Guides quality profiles
Decluttarr Removes stalled/failed queue items
Jellyseerr 5055 Media request management
Homarr 7575 Dashboard
Caddy 80/443 Reverse proxy with automatic TLS

All services run as dedicated system users under systemd, with shared access to media directories via a common media group.

Prerequisites

  • Debian 13 (Trixie) target host
  • Ansible 2.14+ on the control machine
  • SSH access to the target with a sudo-capable user

No external Ansible collections are required — the playbook uses only ansible.builtin.* modules.

Quick start

1. Clone and configure

git clone https://github.com/youruser/barr.git
cd barr

Edit group_vars/all.yml:

caddy_domain: "home.example.com"   # your domain
caddy_tls: internal                # or "auto" for Let's Encrypt

arr_media_root: /data/media        # adjust to your mount point
arr_downloads_root: /data/downloads

Edit inventory.ini with your server details:

[arr_servers]
mediaserver ansible_host=192.168.1.100 ansible_user=debian

2. Run the playbook

ansible-playbook -i inventory.ini install.yml

The playbook is fully idempotent — re-running it is safe and will only apply changes.

Configuration reference

All variables live in group_vars/all.yml. Key sections:

Media paths

arr_media_root: /data/media
arr_media_movies: "{{ arr_media_root }}/movies"
arr_media_tv: "{{ arr_media_root }}/tv"
arr_media_music: "{{ arr_media_root }}/music"

arr_downloads_root: /data/downloads
arr_downloads_complete: "{{ arr_downloads_root }}/complete"
arr_downloads_incomplete: "{{ arr_downloads_root }}/incomplete"

GitHub API rate limiting

The playbook queries the GitHub Releases API to resolve current download URLs. If you hit the unauthenticated rate limit (60 req/h), provide a token:

github_token: ghp_xxxxxxxxxxxx

Caddy TLS

caddy_domain: "home.example.com"
caddy_tls: internal   # self-signed cert for LAN — no ports 80/443 required
# caddy_tls: auto     # Let's Encrypt — requires ports 80 and 443 reachable

Caddy exposes each service on a subdomain:

URL Service
home.example.com Homarr dashboard
requests.home.example.com Jellyseerr
prowlarr.home.example.com Prowlarr
sonarr.home.example.com Sonarr
radarr.home.example.com Radarr
lidarr.home.example.com Lidarr
bazarr.home.example.com Bazarr
qbit.home.example.com qBittorrent

Healthchecks.io monitoring

Each service has a corresponding entry in healthchecks_services. Fill in the ping_url from your healthchecks.io dashboard to enable heartbeat monitoring:

healthchecks_services:
  - name: sonarr
    systemd_unit: sonarr
    ping_url: "https://hc-ping.com/your-uuid-here"
  # ...

A systemd timer fires every healthchecks_ping_interval_minutes (default 5) minutes. It pings the URL on success, or appends /fail if the service is down. Discord (and any other) alerts are configured on the healthchecks.io side — no further Ansible config needed.

How service wiring works

After all services are installed, the arr_configure role handles all inter-service configuration automatically:

  1. Waits for every Servarr and Bazarr API to become responsive
  2. Reads API keys from each app's config.xml (slurped from the remote host)
  3. Registers Sonarr/Radarr/Lidarr in Prowlarr via the /api/v1/applications endpoint, so all indexers sync automatically
  4. Registers FlareSolverr as a proxy in Prowlarr
  5. Adds qBittorrent as the download client in each arr app, with per-app download categories (tv-sonarr, radarr, lidarr)
  6. Configures Bazarr connections to Sonarr and Radarr, and installs webhooks so arr apps notify Bazarr on import
  7. Writes Unpackerr config with all arr app endpoints and API keys, then starts the service
  8. Writes Recyclarr config with Sonarr/Radarr endpoints and API keys, runs an initial TRaSH Guides sync, then arms the daily timer
  9. Writes Decluttarr config with all app credentials, then starts the service

Services that need API keys (Unpackerr, Recyclarr, Decluttarr) are installed and enabled during their own role but only started by arr_configure, ensuring they never launch with a blank config.

Role overview

roles/
├── common/          # libicu, libssl, media group, managed directories
├── nodejs/          # Node.js 20 via NodeSource apt repo + yarn
├── servarr/         # Generic role for .NET arr apps (loop over servarr_apps)
├── bazarr/          # Python venv install
├── qbittorrent/     # apt install + LocalHostAuth=false config
├── flaresolverr/    # Binary + chromium
├── unpackerr/       # Binary, deferred start
├── recyclarr/       # Binary + daily timer, deferred start
├── decluttarr/      # Python venv, deferred start
├── jellyseerr/      # Node.js, npm build on first install
├── homarr/          # Node.js, yarn build on first install
├── caddy/           # apt install + Caddyfile template
├── arr_configure/   # API wiring (see above)
└── healthchecks/    # Per-service ping scripts + systemd timers

The servarr role is called in a loop over servarr_apps defined in group_vars/all.yml. Each app entry specifies its name, port, install/data dirs, GitHub repo, and asset filename pattern. The role queries the GitHub Releases API at runtime to always fetch the current version.

Test VM

barr-vm.sh boots a local Debian 12 QEMU VM and runs the full playbook inside it — useful for testing before deploying to a real machine.

# Full setup (download image → boot → copy playbook → install Ansible)
./barr-vm.sh

# Run the playbook inside the VM
./barr-vm.sh run

# Other commands
./barr-vm.sh start   # boot a previously prepared VM
./barr-vm.sh stop    # graceful ACPI shutdown
./barr-vm.sh ssh     # interactive shell into the VM
./barr-vm.sh status  # show running state
./barr-vm.sh clean   # delete all VM artefacts (.vm/)

The script downloads the official Debian 12 Genericcloud image, creates an overlay qcow2 disk (40 GB by default), generates a cloud-init seed ISO with a deploy user (passwordless sudo, SSH key), and starts QEMU with KVM/HVF/TCG acceleration auto-detected. SSH is forwarded to localhost:2222.

Override defaults via environment variables:

VM_RAM=8192 VM_CPUS=4 SSH_PORT=2223 ./barr-vm.sh

Dependencies: qemu-system-x86_64, qemu-img, ssh, curl, rsync, and one of cloud-localds / genisoimage / mkisofs / xorriso.

Directory layout

barr/
├── install.yml              # Main playbook
├── inventory.ini            # Remote host inventory
├── inventory_local.ini      # Localhost inventory (for use inside the VM)
├── group_vars/
│   └── all.yml              # All configuration variables
├── roles/
│   └── <role>/
│       ├── tasks/
│       ├── templates/
│       ├── handlers/
│       └── vars/            # (common only)
└── barr-vm.sh               # QEMU test VM helper