migrate
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
---
|
||||
tags:
|
||||
- Docker
|
||||
- Media
|
||||
---
|
||||
Directory Structure
|
||||
cd docker
|
||||
mkdir -p ~/arr-stack/{config/{gluetun,qbittorrent,prowlarr,radarr,sonarr},data/{torrents,media/{movies,tv}}}
|
||||
cd ~/arr-stack
|
||||
touch docker-compose.yml .env
|
||||
|
||||
|
||||
```
|
||||
# Tom Spark's ARR Stack — Automated Media Server
|
||||
# https://github.com/loponai/arrstack
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy .env.example to .env and fill in your VPN credentials
|
||||
# 2. Run: bash setup-folders.sh
|
||||
# 3. Run: docker compose up -d
|
||||
#
|
||||
# All VPN-protected services (qBittorrent, Prowlarr, FlareSolverr) run
|
||||
# through Gluetun. If the VPN drops, traffic stops. Zero leaks.
|
||||
#
|
||||
# Radarr, Sonarr, Lidarr, Bazarr, Jellyfin, and Seerr do NOT run through
|
||||
# the VPN — they need direct network access for speed and local connectivity.
|
||||
|
||||
networks:
|
||||
arrnetwork:
|
||||
name: arrnetwork
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.39.0.0/24
|
||||
|
||||
services:
|
||||
|
||||
# ============================================================
|
||||
# GLUETUN — VPN Container (kill switch + tunnel)
|
||||
# All VPN-protected services route through this container.
|
||||
# Ports for those services are mapped HERE, not on the services themselves.
|
||||
# Docs: https://github.com/qdm12/gluetun-wiki
|
||||
# ============================================================
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun:latest
|
||||
container_name: gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_GLUETUN}
|
||||
ports:
|
||||
- 8000:8000 # Gluetun Control Server
|
||||
- 8080:8080 # qBittorrent WebUI
|
||||
- 6881:6881 # qBittorrent torrenting port
|
||||
- 6881:6881/udp
|
||||
- 9696:9696 # Prowlarr
|
||||
- 8191:8191 # FlareSolverr
|
||||
volumes:
|
||||
- ./gluetun:/gluetun
|
||||
environment:
|
||||
- VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
||||
- VPN_TYPE=${VPN_TYPE}
|
||||
# --- WireGuard credentials (most providers) ---
|
||||
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
|
||||
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
|
||||
# - WIREGUARD_PUBLIC_KEY=${WIREGUARD_PUBLIC_KEY}
|
||||
- WIREGUARD_PRESHARED_KEY=${WIREGUARD_PRESHARED_KEY}
|
||||
# --- OpenVPN credentials (if using OpenVPN instead) ---
|
||||
# - OPENVPN_USER=${OPENVPN_USER}
|
||||
# - OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
|
||||
# --- Server selection ---
|
||||
- SERVER_COUNTRIES=${SERVER_COUNTRIES}
|
||||
# --- Port forwarding (ProtonVPN, AirVPN, PIA) ---
|
||||
# - VPN_PORT_FORWARDING=${VPN_PORT_FORWARDING}
|
||||
- FIREWALL_VPN_INPUT_PORTS=${FIREWALL_VPN_INPUT_PORTS}
|
||||
# --- General ---
|
||||
- TZ=${TZ}
|
||||
- BLOCK_MALICIOUS=off
|
||||
- HTTP_CONTROL_SERVER_ADDRESS=:8000
|
||||
- HTTP_CONTROL_SERVER_LOG=on
|
||||
- HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE={"auth":"none"}
|
||||
healthcheck:
|
||||
test: wget -qO /dev/null http://127.0.0.1:9999 || exit 1
|
||||
interval: 20s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# QBITTORRENT — Torrent Client (runs through Gluetun VPN)
|
||||
# ALL traffic goes through the VPN tunnel. No direct internet.
|
||||
# ============================================================
|
||||
qbittorrent:
|
||||
image: lscr.io/linuxserver/qbittorrent:latest
|
||||
container_name: qbittorrent
|
||||
network_mode: service:gluetun
|
||||
depends_on:
|
||||
gluetun:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
labels:
|
||||
- deunhealth.restart.on.unhealthy=true
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
- WEBUI_PORT=8080
|
||||
- TORRENTING_PORT=${FIREWALL_VPN_INPUT_PORTS}
|
||||
volumes:
|
||||
- ./qbittorrent:/config
|
||||
- /data:/data
|
||||
healthcheck:
|
||||
test: wget -q --spider http://localhost:8080 || exit 1
|
||||
interval: 60s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# DEUNHEALTH — Auto-restarts unhealthy containers
|
||||
# If qBittorrent loses VPN connection, this restarts it automatically.
|
||||
# ============================================================
|
||||
deunhealth:
|
||||
image: qmcgaw/deunhealth
|
||||
container_name: deunhealth
|
||||
network_mode: none
|
||||
environment:
|
||||
- LOG_LEVEL=info
|
||||
- HEALTH_SERVER_ADDRESS=127.0.0.1:9999
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
restart: always
|
||||
# ============================================================
|
||||
# PROWLARR — Indexer Manager (runs through Gluetun VPN)
|
||||
# Manages torrent/usenet indexers. Syncs to Radarr/Sonarr/Lidarr.
|
||||
# ============================================================
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:latest
|
||||
container_name: prowlarr
|
||||
network_mode: service:gluetun
|
||||
depends_on:
|
||||
gluetun:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ./prowlarr:/config
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# FLARESOLVERR — Cloudflare Bypass (runs through Gluetun VPN)
|
||||
# Some indexers use Cloudflare protection. This gets around it.
|
||||
# ============================================================
|
||||
flaresolverr:
|
||||
image: ghcr.io/flaresolverr/flaresolverr:latest
|
||||
container_name: flaresolverr
|
||||
network_mode: service:gluetun
|
||||
depends_on:
|
||||
gluetun:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
environment:
|
||||
- LOG_LEVEL=info
|
||||
- TZ=${TZ}
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# RADARR — Movie Manager (NOT behind VPN)
|
||||
# Searches via Prowlarr, sends downloads to qBittorrent,
|
||||
# renames and hard-links completed files to media folder.
|
||||
# ============================================================
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ./radarr:/config
|
||||
- /data:/data
|
||||
ports:
|
||||
- 7878:7878
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_RADARR}
|
||||
restart: unless-stopped
|
||||
# ============================================================
|
||||
# SONARR — TV Show Manager (NOT behind VPN)
|
||||
# Same pattern as Radarr but for TV series.
|
||||
# ============================================================
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ./sonarr:/config
|
||||
- /data:/data
|
||||
ports:
|
||||
- 8989:8989
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_SONARR}
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# LIDARR — Music Manager (NOT behind VPN)
|
||||
# Optional. Comment out if you don't need music automation.
|
||||
# ============================================================
|
||||
lidarr:
|
||||
image: lscr.io/linuxserver/lidarr:latest
|
||||
container_name: lidarr
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ./lidarr:/config
|
||||
- /data:/data
|
||||
ports:
|
||||
- 8686:8686
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_LIDARR}
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# BAZARR — Subtitle Manager (NOT behind VPN)
|
||||
# Automatically downloads subtitles for movies and TV shows.
|
||||
# ============================================================
|
||||
bazarr:
|
||||
image: lscr.io/linuxserver/bazarr:latest
|
||||
container_name: bazarr
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ./bazarr:/config
|
||||
- /data:/data
|
||||
ports:
|
||||
- 6767:6767
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_BAZARR}
|
||||
restart: unless-stopped
|
||||
# ============================================================
|
||||
# JELLYFIN — Media Server (NOT behind VPN)
|
||||
# Your personal streaming service. Plays movies, TV, music.
|
||||
# Needs full bandwidth — never put this behind the VPN.
|
||||
# ============================================================
|
||||
jellyfin:
|
||||
image: lscr.io/linuxserver/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- UMASK=002
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ./jellyfin:/config
|
||||
- /data/media:/data/media
|
||||
ports:
|
||||
- 8096:8096
|
||||
# Uncomment the lines below to enable hardware transcoding (Intel Quick Sync / VAAPI).
|
||||
# Only works if your system has Intel/AMD integrated graphics (/dev/dri must exist).
|
||||
# If you get an error about /dev/dri not found, leave these commented out.
|
||||
# devices:
|
||||
# - /dev/dri:/dev/dri
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_JELLYFIN}
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# SEERR — Request System (NOT behind VPN)
|
||||
# Netflix-like UI for requesting movies and TV shows.
|
||||
# Share this with family — they never need to touch Radarr.
|
||||
#
|
||||
# Seerr is the unified successor to Overseerr and Jellyseerr
|
||||
# (merged under seerr-team). Supports Plex, Jellyfin, and Emby.
|
||||
#
|
||||
# Config uses a NAMED Docker volume (not a bind mount). This is
|
||||
# required: Seerr runs as the `node` user (UID 1000) and a
|
||||
# bind-mounted host folder is created root-owned, causing a
|
||||
# permission-denied crash loop. On Windows/WSL, bind mounts also
|
||||
# corrupt the SQLite DB over SMB. Named volumes fix both cases
|
||||
# (matches upstream Seerr docs).
|
||||
#
|
||||
# Migrating from ./jellyseerr or ./seerr bind mount? See README
|
||||
# troubleshooting "Migrating Seerr config to a named volume".
|
||||
# ============================================================
|
||||
seerr:
|
||||
image: ghcr.io/seerr-team/seerr:v3.0.1
|
||||
init: true
|
||||
container_name: seerr
|
||||
environment:
|
||||
- LOG_LEVEL=info
|
||||
- TZ=${TZ}
|
||||
- PORT=5055
|
||||
volumes:
|
||||
- seerr_config:/app/config
|
||||
ports:
|
||||
- 5055:5055
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:5055/api/v1/settings/public || exit 1
|
||||
start_period: 20s
|
||||
timeout: 3s
|
||||
interval: 15s
|
||||
retries: 3
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_SEERR}
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# AUDIO BookShelf — Server (NOT behind VPN)
|
||||
# Your personal streaming service. Plays all audiobooks.
|
||||
# Needs full bandwidth — never put this behind the VPN.
|
||||
# ============================================================
|
||||
audiobookshelf:
|
||||
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||
container_name: audiobookshelf
|
||||
ports:
|
||||
- 13378:80
|
||||
volumes:
|
||||
- /data/media/audiobookshelf/books:/audiobooks
|
||||
- /data/media/audiobookshelf/podcasts:/podcasts
|
||||
- /data/media/audiobookshelf/metadata:/metadata
|
||||
- ./audiobookshelf/config:/config
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_AUDIO}
|
||||
|
||||
# ============================================================
|
||||
# NAVIDROME — Music Media Server (NOT behind VPN)
|
||||
# Your personal streaming service. Plays music.
|
||||
# Needs full bandwidth — never put this behind the VPN.
|
||||
# ============================================================
|
||||
navidrome:
|
||||
image: deluan/navidrome:latest
|
||||
user: 1000:1000
|
||||
container_name: navidrome
|
||||
ports:
|
||||
- 4533:4533
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ND_SCANSCHEDULE: "1h"
|
||||
ND_LOGLEVEL: info
|
||||
ND_SESSIONTIMEOUT: "24h"
|
||||
volumes:
|
||||
- ./navi/data:/data
|
||||
- /data/media/music:/music:ro
|
||||
networks:
|
||||
arrnetwork:
|
||||
ipv4_address: ${IP_NAVI}
|
||||
|
||||
volumes:
|
||||
seerr_config:
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 2. Step-by-Step qBittorrent Configuration
|
||||
|
||||
Once your stack is running via `docker compose up -d`, navigate to your qBittorrent interface at `http://localhost:8118` or your server's IP address (e.g., `[http://192.168.1.50:8118](http://192.168.1.50:8118)`).
|
||||
|
||||
### Step 2a: Log In and Find Credentials
|
||||
|
||||
- Look at your container logs (`docker logs qbittorrent`) to locate the **temporary password** generated automatically by linuxserver/qbittorrent for security.
|
||||
|
||||
- Use `admin` as the username and paste that password.
|
||||
|
||||
- _Recommended:_ Instantly go to **Tools > Options > Web UI** to change the password to a permanent one.
|
||||
|
||||
|
||||
### Step 2b: Configure Connection & Port Forwarding
|
||||
|
||||
This ensures your traffic efficiently routes through AirVPN's system:
|
||||
|
||||
**1.Open Connection Settings:**Inside Web UI.
|
||||
|
||||
Go to **Tools** in the top navigation bar and select **Options**. In the sidebar menu that pops up, click on **Connection**.
|
||||
|
||||
**2.Set the Torrenting Port:**Match Gluetun mapping.
|
||||
|
||||
Locate **Port used for incoming connections** and type in exactly: `29261`.
|
||||
|
||||
**3.Disable UPnP / NAT-PMP:**Security step.
|
||||
|
||||
**Uncheck** the box next to _Use UPnP / NAT-PMP port forwarding from my router_. Your VPN handles port assignment, so your local router shouldn't intervene.
|
||||
|
||||
### Step 2c: Bind to the VPN Interface (The Hard Kill Switch)
|
||||
|
||||
Binding ensures that if Gluetun ever collapses, leaks, or drops connection, qBittorrent immediately stops downloading or seeding instead of trying to pass data via an unprotected network.
|
||||
|
||||
**1.Open Advanced Panel:**Scroll down sidebar.
|
||||
|
||||
While still in the Options menu, scroll down the left sidebar panel and select **Advanced**.
|
||||
|
||||
**2.Bind Network Interface:**Target VPN tunnel.
|
||||
|
||||
Look for **Network interface** near the top of the list. Change the dropdown menu from _Any interface_ to exactly **`tun0`**.
|
||||
|
||||
**3.Bind IP Address:**Optional but safer.
|
||||
|
||||
Look right underneath at the **Optional IP address to bind to** setting. Change it from _All addresses_ to **`All IPv4 addresses`**.
|
||||
|
||||
**4.Save and Restart:**Apply changes.
|
||||
|
||||
Click **Save / Apply** at the bottom right. Restart your container with `docker compose restart qbittorrent` to guarantee the network interfaces lock into place cleanly.
|
||||
@@ -0,0 +1,254 @@
|
||||
---
|
||||
title: Configuring Arr Stack
|
||||
source: https://mafyuh.com/posts/arr-stack-config-guide/
|
||||
author:
|
||||
- "[[Matt]]"
|
||||
published: 2024-02-24
|
||||
created: 2026-07-09
|
||||
description: This is blog post 2/2 on setting up a Arr stack using docker. This post will touch on configuring these services and what I have learned when using these services. I will be referring to TRaSH-Guides often in this post. It’s like the bible of the Arr’s so I would look there for more options, especially if your setup differs from mine.PrerequisitesAny Usenet Server Subscription (preferred)Any Usenet Indexer Subscription (preferred)Real-Debrid Subscription (if you like torrents being fast)VPN Subscription (Bare minimum needed to download torrents)ConfigurationProwlarrWe will first start with Prowlarr and Sabnzbd to get all of this out of the way, and it’s the part where you’re gonna spend some $
|
||||
tags:
|
||||
- clippings
|
||||
- Media
|
||||
---
|
||||
This is blog post 2/2 on setting up a Arr stack using docker. This post will touch on configuring these services and what I have learned when using these services. I will be referring to [TRaSH-Guides](https://trash-guides.info/) often in this post. It’s like the bible of the Arr’s so I would look there for more options, especially if your setup differs from mine.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Any Usenet Server Subscription (preferred)
|
||||
- Any Usenet Indexer Subscription (preferred)
|
||||
- Real-Debrid Subscription (if you like torrents being fast)
|
||||
- VPN Subscription (Bare minimum needed to download torrents)
|
||||
|
||||
## Configuration
|
||||
|
||||
## Prowlarr
|
||||
|
||||
We will first start with Prowlarr and Sabnzbd to get all of this out of the way, and it’s the part where you’re gonna spend some $
|
||||
|
||||
1st thing to do is load up your web browser and go to Prowlarr. In your URL bar put http://{IP-ADDRESS}:9696.
|
||||
|
||||
You should be prompted to create an account. It doesn’t matter much if you choose Basic or Forms for authentication.
|
||||
|
||||
This guide is just going to be using Usenet, as I recommend it over torrenting.
|
||||
|
||||
Prowlarr’s job is to search thru all the indexers and return those results over to Radarr/Sonarr.
|
||||
|
||||
### Indexers
|
||||
|
||||
You can find links to all the indexers out there on this Usenet [subreddit](https://www.reddit.com/r/usenet/wiki/indexers/). I will provide a screenshot of my Indexers list:
|
||||
|
||||

|
||||
|
||||
I would say the most downloaded stuff I have is from NZBgeek, followed by DrunkenSlug then NZBFinder. If I were to only be able to have 1 it would be NZBgeek for sure.
|
||||
|
||||
Sign up for whatever service you want, once you pay they should give you your API key, usually under profile on the indexer website. Take that API over to Prowlarr and you should be able to select your indexer from the list, paste in your API key and save. You can also click on the wrench icon next to the indexer and set your VIP expiration date, so Prowlarr reminds you to renew.
|
||||
|
||||
I paid for as lifetime altHUB account as well as yearly for Slug and Geek, A lot of the others that are under Interactive Search Sync Profile are free accounts that they limit the amount of API hits per day. You can tell Prowlarr this to by setting up Sync Profiles like I have so it doesn’t burn your API hits fast. [Link](https://trash-guides.info/Prowlarr/prowlarr-setup-limited-api/)
|
||||
|
||||
Thats pretty much it for indexers, now you just need a provider which is covered in the Sabnzbd portion.
|
||||
|
||||
### Usenet vs Torrenting
|
||||
|
||||
Usenet wins all day IMO. I started off with torrenting years ago and there would always be something I couldn’t find. Once I found usenet that problem went away and hasn’t returned. I still have a Real-Debrid subscription, however I am not planning on renewing when it runs up as I just don’t need torrents anymore for media. The only exception being PPV fights, which at least with the indexers I use, I cant find much on usenet. Usenet grabs about 95% of what I need. And I’m sure the other 5% would have still been found, but torrent won the algorithm battle. You also do not need to use a VPN when using Usenet as all big providers use HTTPS.
|
||||
|
||||
If you are new to Usenet, their [subreddit](https://www.reddit.com/r/usenet/wiki/index/) wiki will help you out. Unlike torrenting which is peer to peer and fully decentralized, usenet is more centralized and has many servers that host the content (Providers). There is a monthly fee for being able to access these servers. You also need an indexer in order to find NZB files, which is then sent to your provider for downloading. Indexers are much cheaper at usually a few bucks a year.
|
||||
|
||||
### Connecting Prowlarr to Radarr/Sonarr
|
||||
|
||||
For now skip to setting up Radarr and Sonarr, after setting up you will need their API keys which can be found under **Settings - General - API Key**.
|
||||
|
||||
In Prowlarr go to **Settings - Apps - Create an application** and click Radarr, filling in all api key and changing the host if needed. Do the same thing for Sonarr. This automatically syncs all your indexers into Radarr/Sonarr and if you want to add more indexers you just have to add it in Prowlarr and not 2 separate places.
|
||||
|
||||
## Sabnzbd
|
||||
|
||||
Sab is what connects to our Usenet providers and downloads the NZB files that Radarr/Sonarr gave to it.
|
||||
|
||||
First load up sab at http://{IP\_ADDRESS}:8080. One of the first things it has you do is enter you server details, which takes us into providers.
|
||||
|
||||
### Providers
|
||||
|
||||
These are the costliest part of the process, although for good reason. There are a bunch of providers out there, which all can be found [here](https://www.reddit.com/r/usenet/wiki/providers/).
|
||||
|
||||
I have used Newshosting, NewsDemon and UsenetNow. You only need 1 of these to download most stuff, but I prefer to have 2 as I have seen some servers don’t have a file that another one did. It’s rare but happens. As of the time of me writing this I am using UsenetNow for $6/month and Newsdemon $50 for 12 months using this [link](https://members.newsdemon.com/billinginfo.php?pricepointid=20230413) (Not referral just found a promo link on Reddit I saved)
|
||||
|
||||
Once you select your provider and pay, they should email you your login credentials that you just put into Sab. Remember to set the connections to whatever your provider allows to maximize download speeds.
|
||||
|
||||
### Folders
|
||||
|
||||
All we really need to do to configure Sab is to set the Temp and Completed Download folders. For Temporary:
|
||||
|
||||
```
|
||||
/data/usenet/incomplete
|
||||
```
|
||||
|
||||
and for complete:
|
||||
|
||||
```
|
||||
/data/usenet/complete
|
||||
```
|
||||
|
||||
Make sure you save changes.
|
||||
|
||||
## Radarr
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1st thing to do is load up your web browser and go to Radarr. In your URL bar put http://{IP-ADDRESS}:7878.
|
||||
|
||||
Putting in the IP of you Ubuntu machine. If you are using something like WSL just use localhost as IP.
|
||||
|
||||
You should be prompted to create an account. It doesn’t matter much if you choose Basic or Forms for authentication.
|
||||
|
||||
If you are starting fresh with no content, go to **Settings - Media Management - Root Folders** and add a root folder, you can use the movies folder that was located at /data/media/movies or you can go into /data/media and name your folders however you want to.
|
||||
|
||||
My media directory breaks down the movies by category: 4K, Movies, Marvel, DC, Kids, Stand-Up, Requested Content and Fights. They are added to Radarr individually as follows:
|
||||
|
||||

|
||||
|
||||
If you already have media you want to import into Radarr, click **Movies - Library Import - Start Import**. Remember all you files are going to be under /data/media if you followed my installation guide. This also adds the chosen directory as a root folder.
|
||||
|
||||
### Connect Sab to Radarr
|
||||
|
||||
To connect Sab to Radarr, in Radarr go to **Settings - Download Clients** - add - Sabnzbd - fill in all your Sab details, getting the API key from **Sab - General - Api Key** (Not NZB Key)
|
||||
|
||||
### Naming Files
|
||||
|
||||
It’s best practice to rename your files, Radarr does this automatically for you. To do this go to **Settings - Media Management - Movie Naming**. Make sure you check the Rename Movies checkbox. Everything should be fine by default besides Standard Movie Format, Jellyfin recommends this:
|
||||
|
||||
```
|
||||
{Movie CleanTitle} {(Release Year)} [imdbid-{ImdbId}] - {Edition Tags }{[Custom Formats]}{[Quality Full]}{[MediaInfo 3D]}{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}[{Mediainfo VideoCodec}]{-Release Group}
|
||||
```
|
||||
|
||||
Make sure you hit save.
|
||||
|
||||
Note this only automatically renames the file if it was downloaded from Radarr, if you imported your own media you need to manually click rename files under **Movies** - Click Edit Movies - Select All - Rename Files on bottom of screen.
|
||||
|
||||
### Custom Formats
|
||||
|
||||
Every single one of these custom formats is from trash-guides, you can see how to import them [here](https://trash-guides.info/Radarr/Radarr-import-custom-formats/). 
|
||||
|
||||
Add all custom formats that you want to look out for, whether good or bad. What custom formats do is essentially put a label on files just based off the files name, as well as provide these labels a score which is used when auto-searching for a movie. Just adding custom formats on it’s own does nothing, setting up a Quality profile to rank these custom formats is how we pick and choose what we want. Reading through trash guides will yield good results.
|
||||
|
||||
### Quality Profiles
|
||||
|
||||
Now you need to ask yourself what your plan here is, do you want the highest possible quality content with no regards to storage space? Do you want a giant library filled with lower quality content? Do you have a 4K TV? Do you have HDR/HDR10/Dolby Vision on your TV? Do you have a Dolby Atmos system and want the top notch audio? What about the people you maybe plan on sharing these files with, do you know their TV’s capabilities? Are you gonna be using Plex or Jellyfin to stream this media?
|
||||
|
||||
You should consider all these things when setting this up so you don’t have to manually search for files. That’s the point of this whole section is to automate the searching process and find the correct kinds of files every time. Trash-Guides put together multiple flow charts on what custom formats to use in certain situations which can be found [here](https://trash-guides.info/Radarr/radarr-setup-quality-profiles/#which-quality-profile-should-you-choose)
|
||||
|
||||
I am going to put my logic behind how I have my setup configured, although you should review [this Trash-Guide](https://trash-guides.info/Radarr/radarr-setup-quality-profiles/) for more details.
|
||||
|
||||
1080P:  \*Not Shown custom format scores
|
||||
|
||||
- 3D:-10000
|
||||
- BR-DISK:-10000
|
||||
- Bad Dual Groups:-10000
|
||||
- EVO (no WEBDL):-10000
|
||||
- LQ (Release Title):-10000
|
||||
- x265 (HD):-10000 (This is Trash-Guides Golden Rule, 1080p=x264 | 4k=x265)
|
||||
|
||||
Not much logic here other than not getting crappy releases and only downloading x264 if its 1080P. File size limits can be put to get smaller files. I just follow [this](https://trash-guides.info/Radarr/Radarr-Quality-Settings-File-Size/).
|
||||
|
||||
I want x264 as its gonna have the highest compatibility with the most devices and the less transcoding I need to do the better.
|
||||
|
||||
4K:  
|
||||
|
||||
I have a 4K TV with Dolby Vision Support, along with a Dolby Atmos sound system. So making sure my 4K files have Dolby Vision is pretty important, also having the extended screen of IMAX on a home sized TV is amazing. Having both of those together is the perfect recipe IMO, yet not all movies support one or the other so not as common as you may think. But generally if I want something 4K it will have DV support. I read on trash guides that if you have DV files without DV HDR10 as a fallback, the video will appear off-color to TVs that don’t have DV. So I try to make sure DV with HDR10 fallback is 1st. File size is not taken into consideration at all, I have multiple ~90GB movies.
|
||||
|
||||
Also if your TV doesn’t have DV support, get one. In my experience the difference between HDR10/10+ and DV is night and day. And I usually am not a fan of proprietary software at all. The next best if you don’t have DV is HDR10+. But ultimately having any HDR is gonna be better than SDR.
|
||||
|
||||
I ended up removing the Atmos custom format as most of the time the insane high quality files will have Atmos, and it could just be my setup but I don’t notice too much a difference between DD+ and Atmos.
|
||||
|
||||
### Conclusion
|
||||
|
||||
With all this done you should be able to a add a new movie and watch it download over in Sab. Then when finished downloading it should be automatically moved to your target directory.
|
||||
|
||||
### Tips
|
||||
|
||||
1. If you ever find a file that works well and you don’t want Radarr to mess with it, unmonitor it. That way Radarr doesn’t upgrade it.
|
||||
2. If you are brand new to Radarr I would learn the basics of it in the web-ui, then proceed to utilizing the API with apps like [Nzb360](https://nzb360.com/), [LunaSea](https://www.lunasea.app/), and [Overseerr](https://overseerr.dev/) / [Jellyseerr](https://github.com/Fallenbagel/jellyseerr) to request stuff with a better front end. There’s even [Doplarr](https://docs.linuxserver.io/images/docker-doplarr/#application-setup) the Discord bot.
|
||||
3. Say you found a specific version of a movie, but the audio is bad or a different language, and you can’t find another video quality the same, you can use a tool called [mkvtoolnix](https://mkvtoolnix.download/) to merge audio tracks from one file to another’s video track. You will probably run into this at some point if you’re really looking for something specific.
|
||||
4. You can manually import Boxing/WWE/UFC events into Radarr and the metadata will apply, but searching in Radarr doesn’t work for these types of events. Manually finding the NZB’s or torrents and moving files seems to be only way.
|
||||
|
||||
## Sonarr
|
||||
|
||||
### Initial Setup
|
||||
|
||||
Sonarr is Radarr but for TV Shows.
|
||||
|
||||
First thing load up Sonarr on your web browser at http://{IP\_ADDRESS}:8989.
|
||||
|
||||
Make an account. Then do the same thing as Radarr and add a new root folder if starting new, or import your existing media. The root folder location should be /data/media/tv
|
||||
|
||||
Then we need to connect Sab to Sonarr, to do this Go to **Settings - Download Clients - Add** just like we did with Radarr, fill in all your Sab details.
|
||||
|
||||
### Custom Formats
|
||||
|
||||
Again all these custom formats are on trash-guides. Link [here](https://trash-guides.info/Sonarr/sonarr-collection-of-custom-formats/).  I just picked all major streaming services and the Tier’s. Import them the same way you did Radarr.
|
||||
|
||||
### Quality Profile
|
||||
|
||||
Go to **Settings - Profiles - Quality Profiles**  \*Not Shown custom format scores
|
||||
|
||||
- x265 (HD):-10000
|
||||
- BR-DISK:-10000
|
||||
|
||||
I only have this 1 1080p profile as I personally do not want to waste that storage space on a long TV series. I do not have any 4K TV shows at all. This generally always grabs the best available file. Again for Size Limit I follow [this trash-guide](https://trash-guides.info/Sonarr/Sonarr-Quality-Settings-File-Size/).
|
||||
|
||||
Having x265(HD) as -10000 means it will not download a 1080p file if it is encoded in x265, again this is for compatibility as x264 just works on more devices.
|
||||
|
||||
### Naming Files
|
||||
|
||||
I also rename Sonarr’s files, to do this go to **Settings - Media Management** - check Rename Episodes and under Standard Episode format set:
|
||||
|
||||
```
|
||||
{Series TitleYear} - S{season:00}E{episode:00} - {Episode CleanTitle} [{Custom Formats }{Quality Full}]{[MediaInfo VideoDynamicRangeType]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{[MediaInfo VideoCodec]}{-Release Group}
|
||||
```
|
||||
|
||||
Make sure you save changes.
|
||||
|
||||
### Tips
|
||||
|
||||
1. Don’t download a whole series at once if it has many seasons, you may fill up on space on your VM. Which will stop everything on that VM. Usually they download faster than they transfer to target directory. So space can add up quick. I usually do 2-3 seasons at a time.
|
||||
|
||||
## Bazarr
|
||||
|
||||
Bazarr is used to get subtitles for all your content. Sometimes the files come with subtitle tracks, Bazarr covers you when they don’t.
|
||||
|
||||
### Initial Setup
|
||||
|
||||
First thing load up Bazarr on your web browser at http://{IP\_ADDRESS}:6767.
|
||||
|
||||
First thing is to set your language, go to **Settings - Languages - Languages Filter** and set the filter to your language. I set to English.
|
||||
|
||||
Then under Language Profiles click Add New Profile:
|
||||
|
||||
- Name: English
|
||||
- Click Add Language, english should pop up by default.
|
||||
- Set the cutoff to en
|
||||
- Save
|
||||
|
||||
At the bottom of this page there is Default Settings, check both boxes for Series and Movies and choose your language profile.
|
||||
|
||||
### Providers
|
||||
|
||||
Under **Settings - Providers** - Add a Provider:
|
||||
|
||||

|
||||
|
||||
YIFY, TVSubtitles, Supersubtitles, are all free and dont require an account.
|
||||
|
||||
opensubtitles.com requires you to create an account first but is free.
|
||||
|
||||
I have a OpenAI whisper model running on a separate VM which uses my GPU and AI to generate subtitles for content as well, And its pretty good even with the base model. It’s rarely needed as usually I find better subtitles thru another provider first with a higher score. But for those times when subtitles can’t be found its nice they can be generated. I found [this](https://wiki.bazarr.media/Additional-Configuration/Whisper-Provider/) in Bazarr’s docs.
|
||||
|
||||
Make sure you save your changes.
|
||||
|
||||
### Connecting Bazarr to Radarr/Sonarr
|
||||
|
||||
Now you just need to tell Bazarr where your arr’s are located. Go to **Settings - Sonarr** for Sonarr and **Settings - Radarr** for Radarr. Filling in all your details and saving.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Congrats you now have a fully automated backend for downloading media! Good time to cancel those 10 streaming subscriptions and start downloading what you wanna watch. There’s not many guides out there about this sort of thing, as piracy leaves some ethical concerns. But idrc, I’ve been a pirate my whole life, the fact that you can make a system like this all for way cheaper than streaming services is mind-blowing to me.
|
||||
|
||||
Now just hook up Jellyfin/Plex up to you /data/media directory and start watching with no ads!
|
||||
@@ -0,0 +1,493 @@
|
||||
|
||||
> ## ⚡ Want the easy, full version? → **[SparkBox](https://tomsparkbox.com)**
|
||||
>
|
||||
> This is one of my original one-shot scripts. It still works — but these days everything I build goes into **[SparkBox](https://tomsparkbo>
|
||||
>
|
||||
> ### 👉 Get it free at **[tomsparkbox.com](https://tomsparkbox.com)**
|
||||
>
|
||||
> _Built by [Tom Spark Reviews](https://youtube.com/@TomSparkReviews)._
|
||||
|
||||
---
|
||||
|
||||
# Tom Spark's ARR Stack
|
||||
|
||||
One-command automated media server with VPN protection. Sonarr, Radarr, Prowlarr, qBittorrent, Gluetun, Jellyfin, and more.
|
||||
|
||||
**Full video tutorial:** [YouTube Link Coming Soon]
|
||||
|
||||
## What You Get
|
||||
|
||||
| Service | Port | Purpose |
|
||||
|---------|------|---------|
|
||||
| Gluetun | — | VPN tunnel with kill switch |
|
||||
| qBittorrent | 8080 | Torrent client (VPN protected) |
|
||||
| Prowlarr | 9696 | Indexer manager (VPN protected) |
|
||||
| FlareSolverr | 8191 | Cloudflare bypass (VPN protected) |
|
||||
| Radarr | 7878 | Movie automation |
|
||||
| Sonarr | 8989 | TV show automation |
|
||||
| Lidarr | 8686 | Music automation |
|
||||
| Bazarr | 6767 | Subtitle automation |
|
||||
| Jellyfin | 8096 | Media server / streaming |
|
||||
| Seerr | 5055 | Netflix-like request UI (Overseerr/Jellyseerr successor) |
|
||||
|
||||
All download traffic routes through Gluetun's VPN tunnel. If the VPN drops, all traffic stops — zero leaks. The deunhealth container auto-r>
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install Docker
|
||||
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
sudo usermod -aG docker $USER
|
||||
# Log out and back in for group change to take effect
|
||||
```
|
||||
|
||||
### 2. Clone this repo
|
||||
|
||||
```bash
|
||||
git clone https://github.com/loponai/arrstack.git
|
||||
cd arrstack
|
||||
```
|
||||
|
||||
### 3. Create folder structure
|
||||
|
||||
```bash
|
||||
sudo bash setup-folders.sh
|
||||
```
|
||||
|
||||
This creates:
|
||||
```
|
||||
/data/
|
||||
├── torrents/ ← qBittorrent downloads here
|
||||
│ ├── movies/
|
||||
│ ├── tv/
|
||||
│ └── music/
|
||||
└── media/ ← Radarr/Sonarr organize files here (Jellyfin reads from here)
|
||||
├── movies/
|
||||
├── tv/
|
||||
└── music/
|
||||
```
|
||||
|
||||
> **Hard links:** Both folders MUST be on the same drive/filesystem. Radarr and Sonarr create hard links (not copies) — the file appears in>
|
||||
|
||||
### 4. Configure your VPN
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
Fill in your VPN provider and credentials. See [VPN Setup Guides](#vpn-setup-guides) below.
|
||||
|
||||
### 5. Launch
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 6. Verify everything is working
|
||||
|
||||
```bash
|
||||
bash test-stack.sh
|
||||
```
|
||||
|
||||
This runs a full health check — Docker status, VPN connection, IP leak test, service accessibility, hard link support, and folder permissio>
|
||||
|
||||
You can also check manually:
|
||||
```bash
|
||||
# Check Gluetun's IP (should be VPN, not your real IP)
|
||||
docker exec gluetun wget -qO- ifconfig.me
|
||||
|
||||
# qBittorrent shares Gluetun's network, so the above proves both are tunneled.
|
||||
docker exec qbittorrent wget -qO- ifconfig.me
|
||||
|
||||
# Check health status of all containers
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
### 7. Configure services
|
||||
|
||||
Open each service in your browser at `http://YOUR-SERVER-IP:PORT` and follow the video tutorial for step-by-step configuration.
|
||||
|
||||
**Quick reference:**
|
||||
- **qBittorrent** (`:8080`) — Get temp password: `docker logs qbittorrent 2>&1 | grep "temporary password"`
|
||||
- **Prowlarr** (`:9696`) — Add indexers, connect to Radarr/Sonarr. If an indexer is blocked by Cloudflare, set up FlareSolverr as a proxy: >
|
||||
- **Radarr** (`:7878`) — Root folder: `/data/media/movies`, download client category: `movies`
|
||||
- **Sonarr** (`:8989`) — Root folder: `/data/media/tv`, download client category: `tv`
|
||||
- **Jellyfin** (`:8096`) — Add libraries: `/data/media/movies`, `/data/media/tv`, `/data/media/music`. To watch, open `http://YOUR-SERVER-I>
|
||||
- **Seerr** (`:5055`) — Connect to Jellyfin, Radarr, and Sonarr. Seerr is the unified successor to Overseerr/Jellyseerr. If you previously >
|
||||
|
||||
**Internal Docker IPs — use these when connecting services to each other (NOT localhost):**
|
||||
|
||||
| IP | Service |
|
||||
|----|---------|
|
||||
| `172.39.0.2` | Gluetun (also qBittorrent, Prowlarr, FlareSolverr) |
|
||||
| `172.39.0.3` | Radarr |
|
||||
| `172.39.0.4` | Sonarr |
|
||||
| `172.39.0.5` | Lidarr |
|
||||
| `172.39.0.6` | Bazarr |
|
||||
| `172.39.0.7` | Jellyfin |
|
||||
| `172.39.0.8` | Seerr |
|
||||
|
||||
These IPs are the same for everyone — they're hardcoded in the docker-compose file.
|
||||
**Common connections:**
|
||||
- Radarr/Sonarr → Download Client → qBittorrent: host `172.39.0.2`, port `8080`
|
||||
- Prowlarr → Apps → Radarr: server `http://172.39.0.3:7878`
|
||||
- Prowlarr → Apps → Sonarr: server `http://172.39.0.4:8989`
|
||||
- Prowlarr → Apps → Prowlarr Server: `http://172.39.0.2:9696`
|
||||
- Seerr → Radarr: host `172.39.0.3`, port `7878`
|
||||
- Seerr → Sonarr: host `172.39.0.4`, port `8989`
|
||||
- Seerr → Jellyfin: host `172.39.0.7`, port `8096`
|
||||
|
||||
**Important Radarr/Sonarr settings:**
|
||||
- Media Management → Show Advanced → **Use Hardlinks instead of Copy** → must be ON
|
||||
- Media Management → **Rename Movies/Episodes** → recommended ON
|
||||
|
||||
**Recommended quality profile (1080p baseline, 4K preferred):**
|
||||
|
||||
Go to Settings → Profiles and edit or create a profile:
|
||||
1. Uncheck everything below 1080p (720p, 480p, etc.)
|
||||
2. Check/enable everything from **HDTV-1080p** up through **Bluray-2160p**
|
||||
3. Set **Cutoff** to `Bluray-1080p` — this is the minimum quality Radarr/Sonarr will be happy with
|
||||
4. Set **Upgrade Until** to `Bluray-2160p` — it will automatically upgrade to 4K if one becomes available
|
||||
|
||||
This means it grabs a 1080p release right away so you can start watching, then silently upgrades to 4K later if it finds one.
|
||||
|
||||
## VPN Setup Guides
|
||||
|
||||
### Surfshark (Recommended)
|
||||
The best value for torrenting — cheapest long-term plans, fast WireGuard speeds, and easy setup with Gluetun. [Get Surfshark](https://get.s>
|
||||
|
||||
1. Go to [Surfshark Manual Setup](https://my.surfshark.com/vpn/manual-setup/main)
|
||||
2. Select **WireGuard** and get your credentials (private key + address)
|
||||
3. In your `.env`:
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=surfshark
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_ADDRESSES=10.14.0.2/16
|
||||
SERVER_COUNTRIES=United States
|
||||
```
|
||||
|
||||
> The `.env.example` file is pre-configured for Surfshark. Just paste your private key and you're good to go.
|
||||
|
||||
### NordVPN
|
||||
1. Go to [NordVPN Manual Setup](https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/)
|
||||
2. Select **NordLynx** (WireGuard) and generate a private key
|
||||
3. In your `.env`:
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=nordvpn
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_ADDRESSES=10.5.0.2/16
|
||||
SERVER_COUNTRIES=United States
|
||||
```
|
||||
|
||||
### ProtonVPN
|
||||
1. Go to [ProtonVPN WireGuard Config](https://account.protonvpn.com/) → Downloads → WireGuard
|
||||
2. Generate a config file, open it, copy `PrivateKey` and `Address`
|
||||
3. In your `.env`:
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=protonvpn
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_ADDRESSES=10.2.0.2/32
|
||||
SERVER_COUNTRIES=United States
|
||||
VPN_PORT_FORWARDING=on
|
||||
```
|
||||
### AirVPN
|
||||
1. Go to [AirVPN Config Generator](https://airvpn.org/) → Client Area → Config Generator
|
||||
2. Select Linux → WireGuard → pick server → Generate
|
||||
3. In your `.env`:
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=airvpn
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_PUBLIC_KEY=server_public_key
|
||||
WIREGUARD_PRESHARED_KEY=your_preshared_key
|
||||
WIREGUARD_ADDRESSES=your_ip/32
|
||||
FIREWALL_VPN_INPUT_PORTS=your_port
|
||||
VPN_PORT_FORWARDING=on
|
||||
```
|
||||
|
||||
### Other Providers
|
||||
Gluetun supports 30+ providers. Check the [full provider list](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers).
|
||||
|
||||
## Starting and Stopping
|
||||
|
||||
**Start the stack:**
|
||||
```bash
|
||||
cd arrstack
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**Stop the stack:**
|
||||
```bash
|
||||
cd arrstack
|
||||
docker compose down
|
||||
```
|
||||
|
||||
**Check status:**
|
||||
```bash
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
### Auto-Start After Reboot
|
||||
|
||||
All containers are set to `restart: unless-stopped`, which means they automatically come back once Docker is running. You just need to make>
|
||||
|
||||
**Linux (dedicated server or VM):**
|
||||
|
||||
Run this once and you're done:
|
||||
```bash
|
||||
sudo systemctl enable docker
|
||||
```
|
||||
|
||||
**Windows (running Docker inside WSL):**
|
||||
|
||||
WSL (Windows Subsystem for Linux) doesn't start Docker automatically when your PC boots. Here's how to fix that:
|
||||
|
||||
**Step 1:** Open your WSL terminal and run this command to edit the WSL config file:
|
||||
```bash
|
||||
sudo nano /etc/wsl.conf
|
||||
```
|
||||
|
||||
**Step 2:** Your file might already have some lines in it (like `[boot]` or `[user]`). Look for a `[boot]` section. If it exists, add the `>
|
||||
```ini
|
||||
[boot]
|
||||
command=service docker start
|
||||
```
|
||||
|
||||
**Step 3:** Save the file by pressing `Ctrl+X`, then `Y`, then `Enter`.
|
||||
**Step 4 (optional):** By default, WSL only starts when you open a terminal. If you want it to start automatically when Windows boots (so y>
|
||||
|
||||
1. Press `Win+R` on your keyboard
|
||||
2. Type `shell:startup` and press Enter — this opens your Windows Startup folder
|
||||
3. Right-click in the folder → New → Text Document
|
||||
4. Name it `wsl.vbs` (make sure it ends in `.vbs`, not `.vbs.txt` — if you can't see file extensions, go to View → Show → File name extensi>
|
||||
5. Right-click the file → Edit (or Open with Notepad) and paste this:
|
||||
```vbs
|
||||
Set ws = CreateObject("Wscript.Shell")
|
||||
ws.Run "wsl -d Ubuntu -u root -- service docker start", 0
|
||||
```
|
||||
6. Save and close
|
||||
|
||||
That's it — next time your PC restarts, WSL starts Docker automatically and all your containers come back up on their own. No commands need>
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd arrstack
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Remote Access with Tailscale (Optional)
|
||||
|
||||
Want to access Jellyfin, Seerr, or any service from outside your home? [Tailscale](https://tailscale.com/) creates a private network betwee>
|
||||
|
||||
**On your server:**
|
||||
```bash
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
sudo tailscale up
|
||||
```
|
||||
|
||||
**On your phone/laptop/TV:**
|
||||
1. Install Tailscale from your app store
|
||||
2. Sign in with the same account
|
||||
|
||||
**Access your services from anywhere:**
|
||||
```
|
||||
http://YOUR-TAILSCALE-IP:8096 ← Jellyfin
|
||||
http://YOUR-TAILSCALE-IP:5055 ← Seerr
|
||||
http://YOUR-TAILSCALE-IP:7878 ← Radarr
|
||||
http://YOUR-TAILSCALE-IP:8989 ← Sonarr
|
||||
```
|
||||
|
||||
Find your Tailscale IP with `tailscale ip -4` on the server.
|
||||
|
||||
Tailscale is free for personal use (up to 100 devices). Everything is encrypted with WireGuard — nobody can see your traffic, not even Tail>
|
||||
|
||||
> **Do NOT expose Jellyfin directly to the internet** (no port forwarding on your router). Use Tailscale or a reverse proxy instead. Direct>
|
||||
|
||||
### Sharing with Family and Friends
|
||||
|
||||
Your family and friends only need two things — **Seerr** to request movies/shows and **Jellyfin** to watch them. They never see Radarr, Son>
|
||||
|
||||
**Step 1: Invite them to your Tailscale network**
|
||||
|
||||
1. Go to the [Tailscale admin console](https://login.tailscale.com/admin/machines)
|
||||
2. Click **Share** on your server's machine
|
||||
3. Enter their email — they'll get an invite link
|
||||
|
||||
**Step 2: They install Tailscale**
|
||||
|
||||
1. Download Tailscale on their phone, laptop, or TV from [tailscale.com/download](https://tailscale.com/download)
|
||||
2. Accept your invite and sign in
|
||||
**Step 3: They access your services**
|
||||
|
||||
Give them these two links (replace with your Tailscale IP):
|
||||
```
|
||||
http://YOUR-TAILSCALE-IP:5055 ← Seerr (request movies and shows)
|
||||
http://YOUR-TAILSCALE-IP:8096 ← Jellyfin (watch everything)
|
||||
```
|
||||
|
||||
**For TVs and phones**, they can install the **Jellyfin app** (available on Roku, Fire TV, Apple TV, Android TV, iOS, Android) and enter yo>
|
||||
|
||||
That's it — they request, you automatically download, they watch. No technical knowledge needed on their end.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Gluetun unhealthy / won't connect:**
|
||||
- Double-check VPN credentials in `.env` — these are NOT your login email/password
|
||||
- Try removing the gluetun folder and restarting: `rm -rf gluetun && docker compose up -d`
|
||||
- Check logs: `docker logs gluetun`
|
||||
|
||||
**qBittorrent can't connect:**
|
||||
- Make sure Gluetun is healthy: `docker ps` (should show "healthy")
|
||||
- Check qBit is using VPN: `docker exec gluetun wget -qO- ifconfig.me`
|
||||
- In qBittorrent settings → Advanced → set Network Interface to `tun0`
|
||||
|
||||
**Movie or show not downloading:**
|
||||
- **Quality profile too strict** — If Radarr/Sonarr can't find a release matching your quality profile, it won't download anything. Go to t>
|
||||
- **Not enough indexers** — Public indexers have limited catalogs. If you only have one or two indexers in Prowlarr, add more (1337x, The P>
|
||||
- **Not enough seeders** — Some torrents just don't have anyone sharing them, especially older or niche content. Check qBittorrent — if the>
|
||||
- **Indexer blocked by Cloudflare** — See the Prowlarr setup note above about setting up FlareSolverr with tags.
|
||||
|
||||
**Hard links not working (files copying instead):**
|
||||
- Both `/data/torrents` and `/data/media` must be on the same filesystem
|
||||
- Check Radarr/Sonarr → Settings → Media Management → "Use Hardlinks" is checked
|
||||
- Verify with: `ls -i /data/torrents/movies/yourfile` and `ls -i /data/media/movies/YourMovie/yourfile` — inode numbers should match
|
||||
|
||||
**Permission errors:**
|
||||
- Run `id` and make sure PUID/PGID in `.env` match your user
|
||||
- Re-run: `sudo chown -R $(id -u):$(id -g) /data`
|
||||
|
||||
**Service won't start — "port already in use":**
|
||||
|
||||
Another program on your system might be using the same port. This is common with port 5055 (Seerr) but can happen with any service.
|
||||
|
||||
1. Find what's using the port (replace `5055` with the port number from the error):
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
sudo ss -tlnp | grep 5055
|
||||
```
|
||||
|
||||
**Windows (WSL users) — run in PowerShell:**
|
||||
```powershell
|
||||
netstat -ano | findstr :5055
|
||||
```
|
||||
This gives you a PID (process ID). Find the program name:
|
||||
```powershell
|
||||
Get-Process -Id <PID> | Select-Object ProcessName, Id, Path
|
||||
```
|
||||
|
||||
2. Either stop/disable that program, or change the port in `docker-compose.yml` to an unused one (e.g. `5056:5055`).
|
||||
|
||||
3. If it's a Windows service hogging the port, disable it in an admin PowerShell:
|
||||
```powershell
|
||||
Stop-Service <ServiceName> -Force
|
||||
Set-Service <ServiceName> -StartupType Disabled
|
||||
```
|
||||
4. Then recreate the container:
|
||||
```bash
|
||||
docker compose up -d --force-recreate <service-name>
|
||||
```
|
||||
|
||||
**Seerr stuck restarting / crash-looping:**
|
||||
|
||||
The current `docker-compose.yml` uses a named volume (`seerr_config`) for Seerr's config, which fixes both causes of the crash loop. If you>
|
||||
|
||||
Root cause (for reference):
|
||||
- **Linux/macOS:** Seerr runs as the `node` user (UID 1000). A bind-mounted `./seerr` folder is created root-owned on first `docker compose>
|
||||
- **Windows/WSL:** Bind mounts go through an SMB share inside Docker Desktop's VM, which doesn't support file locking. Seerr's SQLite DB co>
|
||||
|
||||
Named volumes sidestep both problems because Docker creates them with correct ownership inside its own managed storage.
|
||||
|
||||
If you still see it after pulling:
|
||||
- Check logs: `docker logs seerr | tail -30`
|
||||
- Nuke the volume and start fresh (you'll lose Seerr settings, not media):
|
||||
```bash
|
||||
docker compose down seerr && docker volume rm arrstack_seerr_config && docker compose up -d seerr
|
||||
```
|
||||
|
||||
**Migrating Seerr config to a named volume (existing installs):**
|
||||
|
||||
If you had Seerr working on an older version of this repo with a bind-mounted `./jellyseerr` or `./seerr` folder and want to keep your sett>
|
||||
|
||||
```bash
|
||||
docker compose down seerr
|
||||
# pick whichever folder you actually have
|
||||
SRC=./seerr
|
||||
[ -d ./jellyseerr ] && SRC=./jellyseerr
|
||||
docker volume create arrstack_seerr_config
|
||||
docker run --rm -v "$(pwd)/${SRC#./}":/src -v arrstack_seerr_config:/dest alpine sh -c "cp -a /src/. /dest/ && chown -R 1000:1000 /dest"
|
||||
docker compose up -d seerr
|
||||
```
|
||||
|
||||
If you don't care about preserving settings, just `docker compose up -d seerr` — Seerr will start fresh and walk you through setup again.
|
||||
|
||||
**Can't log into qBittorrent:**
|
||||
- qBittorrent generates a temporary password every time it starts. Get it with:
|
||||
```bash
|
||||
docker logs qbittorrent 2>&1 | grep "temporary password"
|
||||
```
|
||||
- Default username is `admin`. Once logged in, go to Tools → Options → Web UI and set a permanent password.
|
||||
|
||||
**Services can't connect to each other (connection refused, timeout):**
|
||||
- Don't use `localhost` when connecting services together — that won't work across Docker containers.
|
||||
- Use the internal Docker IPs instead:
|
||||
- qBittorrent/Prowlarr/FlareSolverr: `172.39.0.2`
|
||||
- Radarr: `172.39.0.3`
|
||||
- Sonarr: `172.39.0.4`
|
||||
- Jellyfin: `172.39.0.7`
|
||||
- The one exception: Prowlarr → FlareSolverr can use `localhost:8191` because they both run through Gluetun and share the same network.
|
||||
|
||||
**"Root folder does not exist" in Radarr/Sonarr:**
|
||||
- Make sure you ran `sudo bash setup-folders.sh` to create the `/data` directory structure.
|
||||
- Double-check the root folder path — it should be `/data/media/movies` for Radarr and `/data/media/tv` for Sonarr (not `/movies` or `/data>
|
||||
|
||||
**Downloads stuck at "importing" or "waiting to import":**
|
||||
- This is almost always a permissions issue. Fix it with:
|
||||
```bash
|
||||
sudo chown -R $(id -u):$(id -g) /data
|
||||
sudo chmod -R 775 /data
|
||||
```
|
||||
- Make sure PUID/PGID in your `.env` match your user (check with `id`).
|
||||
**Jellyfin library is empty after downloads finish:**
|
||||
- Make sure your Jellyfin libraries point to the correct paths: `/data/media/movies`, `/data/media/tv`, `/data/media/music`
|
||||
- Jellyfin doesn't scan instantly. Go to Dashboard → Libraries → click the `...` menu → **Scan Library** to force a refresh.
|
||||
- You can also set up scheduled scans in Dashboard → Scheduled Tasks.
|
||||
|
||||
**Subtitles not downloading (Bazarr):**
|
||||
- Bazarr needs to be connected to Radarr and Sonarr: Settings → Radarr / Sonarr → enter the IP (`172.39.0.3` / `172.39.0.4`) and API key.
|
||||
- You also need at least one subtitle provider: Settings → Providers → Add → **OpenSubtitles.com** is the most popular (free account requir>
|
||||
|
||||
**Everything works but downloads are slow:**
|
||||
- Your VPN server might be too far away. Change `SERVER_COUNTRIES` in your `.env` to a country closer to you, then restart:
|
||||
```bash
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
- Check your VPN speed: `docker exec gluetun wget -qO- https://speed.cloudflare.com/__down?measId=10000000 > /dev/null` — if it's very slow>
|
||||
|
||||
**Disk space filling up:**
|
||||
- By default, qBittorrent keeps torrents after Radarr/Sonarr imports them. To auto-clean:
|
||||
- In Radarr/Sonarr → Settings → Download Clients → click on qBittorrent → enable **Remove Completed**
|
||||
- This deletes the torrent from qBittorrent after the file has been imported (the hard link in your media folder is kept, so you don't lo>
|
||||
|
||||
**VPN IP leak — want to make sure your real IP isn't exposed:**
|
||||
```bash
|
||||
# Check the VPN container's IP (should NOT be your real IP)
|
||||
docker exec gluetun wget -qO- ifconfig.me
|
||||
|
||||
# Compare with your real IP (run this outside Docker)
|
||||
curl -s ifconfig.me
|
||||
```
|
||||
If both IPs are the same, your VPN isn't working — check Gluetun logs with `docker logs gluetun`.
|
||||
|
||||
## Credits
|
||||
|
||||
Built by [Tom Spark](https://youtube.com/@tomspark) following [Trash Guides](https://trash-guides.info/) and [Servarr Wiki](https://wiki.se>
|
||||
|
||||
Uses [Gluetun](https://github.com/qdm12/gluetun) for VPN, [LinuxServer.io](https://linuxserver.io) container images, and [Seerr](https://gi>
|
||||
@@ -0,0 +1,66 @@
|
||||
```
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# Tom Spark's ARR Stack — Folder Structure Setup
|
||||
# https://github.com/loponai/arrstack
|
||||
#
|
||||
# Creates the /data directory structure required for hard links
|
||||
# to work correctly. Run this ONCE before starting the stack.
|
||||
#
|
||||
# Usage: sudo bash setup-folders.sh
|
||||
# ============================================================
|
||||
|
||||
set -e
|
||||
|
||||
DATA_DIR="/data"
|
||||
|
||||
echo ""
|
||||
echo "=== Tom Spark's ARR Stack — Folder Setup ==="
|
||||
echo ""
|
||||
echo "This will create the following structure:"
|
||||
echo ""
|
||||
echo " /data/"
|
||||
echo " ├── torrents/"
|
||||
echo " │ ├── movies/"
|
||||
echo " │ ├── tv/"
|
||||
echo " │ └── music/"
|
||||
echo " └── media/"
|
||||
echo " ├── movies/"
|
||||
echo " ├── tv/"
|
||||
echo " └── music/"
|
||||
echo ""
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "ERROR: This script needs sudo to create /data and set permissions."
|
||||
echo "Run: sudo bash setup-folders.sh"
|
||||
exit 1
|
||||
fi
|
||||
# Get the real user (not root) for ownership
|
||||
REAL_USER=${SUDO_USER:-$USER}
|
||||
REAL_UID=$(id -u "$REAL_USER")
|
||||
REAL_GID=$(id -g "$REAL_USER")
|
||||
|
||||
echo "Creating folders..."
|
||||
mkdir -p "$DATA_DIR"/{torrents/{movies,tv,music},media/{movies,tv,music}}
|
||||
|
||||
echo "Setting ownership to $REAL_USER ($REAL_UID:$REAL_GID)..."
|
||||
chown -R "$REAL_UID":"$REAL_GID" "$DATA_DIR"
|
||||
|
||||
echo "Setting permissions..."
|
||||
chmod -R 775 "$DATA_DIR"
|
||||
|
||||
echo ""
|
||||
echo "Done! Folder structure:"
|
||||
if command -v tree &> /dev/null; then
|
||||
tree "$DATA_DIR"
|
||||
else
|
||||
find "$DATA_DIR" -type d | head -20
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Your PUID=$REAL_UID and PGID=$REAL_GID"
|
||||
echo "Make sure these match your .env file."
|
||||
echo ""
|
||||
|
||||
```
|
||||
@@ -0,0 +1,328 @@
|
||||
```
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# Tom Spark's ARR Stack — Health Check & Troubleshooting
|
||||
# https://github.com/loponai/arrstack
|
||||
#
|
||||
# Run this after 'docker compose up -d' to verify everything
|
||||
# is working correctly. It checks each service, tests VPN
|
||||
# connectivity, and provides specific fixes for any issues.
|
||||
#
|
||||
# Usage: bash test-stack.sh
|
||||
# ============================================================
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
PASS="${GREEN}✓ PASS${NC}"
|
||||
FAIL="${RED}✗ FAIL${NC}"
|
||||
WARN="${YELLOW}! WARN${NC}"
|
||||
TOTAL_PASS=0
|
||||
TOTAL_FAIL=0
|
||||
TOTAL_WARN=0
|
||||
|
||||
pass() { echo -e " ${PASS} $1"; ((TOTAL_PASS++)); }
|
||||
fail() { echo -e " ${FAIL} $1"; ((TOTAL_FAIL++)); }
|
||||
warn() { echo -e " ${WARN} $1"; ((TOTAL_WARN++)); }
|
||||
header() { echo -e "\n${CYAN}${BOLD}[$1]${NC}"; }
|
||||
fix() { echo -e " ${YELLOW}Fix: $1${NC}"; }
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Tom Spark's ARR Stack — Health Check"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# TEST 1: Docker running?
|
||||
# ============================================================
|
||||
header "Docker"
|
||||
if docker info > /dev/null 2>&1; then
|
||||
pass "Docker is running"
|
||||
else
|
||||
fail "Docker is not running"
|
||||
fix "Start Docker: sudo systemctl start docker"
|
||||
fix "Or install: curl -fsSL https://get.docker.com | sh"
|
||||
echo ""
|
||||
echo "Cannot continue without Docker. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# TEST 2: .env file exists and has VPN credentials?
|
||||
# ============================================================
|
||||
header "Configuration"
|
||||
|
||||
if [ -f .env ]; then
|
||||
pass ".env file exists"
|
||||
else
|
||||
fail ".env file not found"
|
||||
fix "Run: cp .env.example .env && nano .env"
|
||||
fix "Then fill in your VPN credentials"
|
||||
fi
|
||||
|
||||
if [ -f .env ]; then
|
||||
VPN_KEY=$(grep -E "^WIREGUARD_PRIVATE_KEY=" .env 2>/dev/null | cut -d= -f2)
|
||||
VPN_PROVIDER=$(grep -E "^VPN_SERVICE_PROVIDER=" .env 2>/dev/null | cut -d= -f2)
|
||||
|
||||
if [ -n "$VPN_KEY" ] && [ "$VPN_KEY" != "" ]; then
|
||||
pass "VPN private key is set (provider: $VPN_PROVIDER)"
|
||||
else
|
||||
fail "VPN private key is empty"
|
||||
fix "Edit .env and paste your WireGuard private key"
|
||||
fix "Get it from your VPN provider's manual setup page"
|
||||
fi
|
||||
fi
|
||||
# ============================================================
|
||||
# TEST 3: Folder structure exists?
|
||||
# ============================================================
|
||||
header "Folder Structure"
|
||||
|
||||
ALL_FOLDERS_OK=true
|
||||
for dir in /data/torrents/movies /data/torrents/tv /data/torrents/music /data/media/movies /data/media/tv /data/media/music; do
|
||||
if [ -d "$dir" ]; then
|
||||
pass "$dir exists"
|
||||
else
|
||||
fail "$dir missing"
|
||||
ALL_FOLDERS_OK=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$ALL_FOLDERS_OK" = false ]; then
|
||||
fix "Run: sudo bash setup-folders.sh"
|
||||
fi
|
||||
|
||||
# Check permissions
|
||||
if [ -d /data ]; then
|
||||
OWNER=$(stat -c '%u' /data 2>/dev/null)
|
||||
ENV_PUID=$(grep -E "^PUID=" .env 2>/dev/null | cut -d= -f2)
|
||||
if [ "$OWNER" = "$ENV_PUID" ] || [ "$OWNER" = "$(id -u)" ]; then
|
||||
pass "/data ownership matches PUID ($OWNER)"
|
||||
else
|
||||
warn "/data owned by $OWNER but PUID is ${ENV_PUID:-1000}"
|
||||
fix "Run: sudo chown -R ${ENV_PUID:-1000}:${ENV_PUID:-1000} /data"
|
||||
fi
|
||||
fi
|
||||
# ============================================================
|
||||
# TEST 4: Container status
|
||||
# ============================================================
|
||||
header "Containers"
|
||||
|
||||
EXPECTED_SERVICES="gluetun qbittorrent deunhealth prowlarr flaresolverr radarr sonarr lidarr bazarr jellyfin seerr"
|
||||
|
||||
for svc in $EXPECTED_SERVICES; do
|
||||
STATUS=$(docker inspect --format '{{.State.Status}}' "$svc" 2>/dev/null)
|
||||
HEALTH=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' "$svc" 2>/dev/null)
|
||||
|
||||
if [ -z "$STATUS" ]; then
|
||||
fail "$svc — not found (not created)"
|
||||
fix "Run: docker compose up -d"
|
||||
elif [ "$STATUS" = "running" ]; then
|
||||
if [ "$HEALTH" = "healthy" ]; then
|
||||
pass "$svc — running (healthy)"
|
||||
elif [ "$HEALTH" = "unhealthy" ]; then
|
||||
fail "$svc — running but UNHEALTHY"
|
||||
if [ "$svc" = "gluetun" ]; then
|
||||
fix "VPN probably can't connect. Check credentials in .env"
|
||||
fix "Check logs: docker logs gluetun | tail -20"
|
||||
fix "Try: rm -rf gluetun && docker compose up -d gluetun"
|
||||
elif [ "$svc" = "qbittorrent" ]; then
|
||||
fix "Usually means VPN dropped. Deunhealth should auto-restart it."
|
||||
fix "Check: docker logs qbittorrent | tail -20"
|
||||
fi
|
||||
elif [ "$HEALTH" = "starting" ]; then
|
||||
warn "$svc — running (health check starting, wait 30s and rerun)"
|
||||
else
|
||||
pass "$svc — running"
|
||||
fi
|
||||
elif [ "$STATUS" = "created" ]; then
|
||||
warn "$svc — created but not started"
|
||||
if [ "$svc" = "qbittorrent" ] || [ "$svc" = "prowlarr" ] || [ "$svc" = "flaresolverr" ]; then
|
||||
fix "Waiting for Gluetun to be healthy. Check Gluetun status first."
|
||||
fix "If Gluetun is healthy, try: docker compose up -d $svc"
|
||||
elif [ "$svc" = "seerr" ]; then
|
||||
fix "Port 5055 may be in use. Check: ss -tlnp | grep 5055"
|
||||
fix "Or change the port in docker-compose.yml"
|
||||
else
|
||||
fix "Try: docker compose up -d $svc"
|
||||
fi
|
||||
elif [ "$STATUS" = "restarting" ]; then
|
||||
fail "$svc — crash-looping (restarting)"
|
||||
fix "Check logs: docker logs $svc | tail -30"
|
||||
if [ "$svc" = "seerr" ]; then
|
||||
fix "Seerr may have a corrupt config. Try: docker compose down seerr && rm -rf seerr && docker compose up -d seerr"
|
||||
fix "WSL/Windows users: if it keeps crashing, try a named volume instead of a bind mount"
|
||||
else
|
||||
fix "Try: docker compose down $svc && docker compose up -d $svc"
|
||||
fi
|
||||
elif [ "$STATUS" = "exited" ]; then
|
||||
fail "$svc — exited (crashed)"
|
||||
fix "Check logs: docker logs $svc | tail -30"
|
||||
fix "Try restarting: docker compose up -d $svc"
|
||||
else
|
||||
warn "$svc — status: $STATUS"
|
||||
fi
|
||||
done
|
||||
# ============================================================
|
||||
# TEST 5: VPN connectivity
|
||||
# ============================================================
|
||||
header "VPN Connection"
|
||||
|
||||
GLUETUN_STATUS=$(docker inspect --format '{{.State.Status}}' gluetun 2>/dev/null)
|
||||
GLUETUN_HEALTH=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' gluetun 2>/dev/null)
|
||||
|
||||
if [ "$GLUETUN_STATUS" = "running" ] && [ "$GLUETUN_HEALTH" = "healthy" ]; then
|
||||
# Get VPN IP
|
||||
VPN_IP=$(docker exec gluetun wget -qO- --timeout=10 ipinfo.io/ip 2>/dev/null)
|
||||
if [ -n "$VPN_IP" ]; then
|
||||
pass "Gluetun VPN IP: $VPN_IP"
|
||||
|
||||
# Get VPN location
|
||||
VPN_LOCATION=$(docker exec gluetun wget -qO- --timeout=10 "ipinfo.io/${VPN_IP}/city" 2>/dev/null)
|
||||
VPN_COUNTRY=$(docker exec gluetun wget -qO- --timeout=10 "ipinfo.io/${VPN_IP}/country" 2>/dev/null)
|
||||
if [ -n "$VPN_LOCATION" ]; then
|
||||
pass "VPN location: $VPN_LOCATION, $VPN_COUNTRY"
|
||||
fi
|
||||
else
|
||||
fail "Gluetun is healthy but can't reach the internet"
|
||||
fix "Check logs: docker logs gluetun | tail -20"
|
||||
fi
|
||||
|
||||
# Check if qBittorrent is tunneled
|
||||
QBIT_STATUS=$(docker inspect --format '{{.State.Status}}' qbittorrent 2>/dev/null)
|
||||
if [ "$QBIT_STATUS" = "running" ]; then
|
||||
QBIT_IP=$(docker exec qbittorrent wget -qO- --timeout=10 ipinfo.io/ip 2>/dev/null)
|
||||
if [ "$QBIT_IP" = "$VPN_IP" ]; then
|
||||
pass "qBittorrent tunneled through VPN ($QBIT_IP)"
|
||||
elif [ -n "$QBIT_IP" ]; then
|
||||
fail "qBittorrent IP ($QBIT_IP) doesn't match VPN IP ($VPN_IP)!"
|
||||
fix "This should not happen. Check network_mode in docker-compose.yml"
|
||||
else
|
||||
warn "Could not check qBittorrent IP (container may still be starting)"
|
||||
fi
|
||||
fi
|
||||
# Check if Prowlarr is tunneled
|
||||
PROWLARR_STATUS=$(docker inspect --format '{{.State.Status}}' prowlarr 2>/dev/null)
|
||||
if [ "$PROWLARR_STATUS" = "running" ]; then
|
||||
PROWLARR_IP=$(docker exec prowlarr wget -qO- --timeout=10 ipinfo.io/ip 2>/dev/null)
|
||||
if [ "$PROWLARR_IP" = "$VPN_IP" ]; then
|
||||
pass "Prowlarr tunneled through VPN ($PROWLARR_IP)"
|
||||
elif [ -n "$PROWLARR_IP" ]; then
|
||||
fail "Prowlarr IP ($PROWLARR_IP) doesn't match VPN IP ($VPN_IP)!"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify your real IP is different
|
||||
REAL_IP=$(wget -qO- --timeout=10 ipinfo.io/ip 2>/dev/null)
|
||||
if [ -n "$REAL_IP" ] && [ "$REAL_IP" != "$VPN_IP" ]; then
|
||||
pass "Real IP ($REAL_IP) differs from VPN IP — VPN is working!"
|
||||
elif [ "$REAL_IP" = "$VPN_IP" ]; then
|
||||
warn "Real IP matches VPN IP — are you already running a system-wide VPN?"
|
||||
fi
|
||||
else
|
||||
if [ "$GLUETUN_HEALTH" = "unhealthy" ]; then
|
||||
fail "Gluetun is unhealthy — VPN not connected"
|
||||
fix "Check credentials in .env (these are NOT your VPN login email/password)"
|
||||
fix "Check logs: docker logs gluetun 2>&1 | tail -30"
|
||||
fix "Try resetting: docker compose down && rm -rf gluetun && docker compose up -d"
|
||||
elif [ "$GLUETUN_HEALTH" = "starting" ]; then
|
||||
warn "Gluetun health check still starting — wait 30-60 seconds and rerun"
|
||||
else
|
||||
warn "Gluetun not running — can't test VPN"
|
||||
fix "Run: docker compose up -d"
|
||||
fi
|
||||
fi
|
||||
# ============================================================
|
||||
# TEST 6: Service web UI accessibility
|
||||
# ============================================================
|
||||
header "Web UI Access"
|
||||
|
||||
check_http() {
|
||||
local name=$1 port=$2
|
||||
local code=$(curl -sL -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:$port" 2>/dev/null)
|
||||
if [ "$code" = "200" ] || [ "$code" = "302" ] || [ "$code" = "301" ] || [ "$code" = "307" ]; then
|
||||
pass "$name — http://localhost:$port (HTTP $code)"
|
||||
elif [ "$code" = "000" ]; then
|
||||
# Container might be behind gluetun, check if it's running
|
||||
local status=$(docker inspect --format '{{.State.Status}}' "$name" 2>/dev/null)
|
||||
if [ "$status" = "running" ]; then
|
||||
warn "$name — container running but port $port not reachable from host"
|
||||
fix "Port may be mapped on Gluetun. Try: http://localhost:$port"
|
||||
else
|
||||
fail "$name — not reachable (container not running)"
|
||||
fi
|
||||
else
|
||||
warn "$name — http://localhost:$port returned HTTP $code"
|
||||
fi
|
||||
}
|
||||
|
||||
check_http qbittorrent 8080
|
||||
check_http prowlarr 9696
|
||||
check_http radarr 7878
|
||||
check_http sonarr 8989
|
||||
check_http lidarr 8686
|
||||
check_http bazarr 6767
|
||||
check_http jellyfin 8096
|
||||
check_http seerr 5055
|
||||
# ============================================================
|
||||
# TEST 7: Hard link capability
|
||||
# ============================================================
|
||||
header "Hard Links"
|
||||
|
||||
if [ -d /data/torrents ] && [ -d /data/media ]; then
|
||||
# Check if same filesystem
|
||||
FS_TORRENTS=$(df /data/torrents --output=source 2>/dev/null | tail -1)
|
||||
FS_MEDIA=$(df /data/media --output=source 2>/dev/null | tail -1)
|
||||
|
||||
if [ "$FS_TORRENTS" = "$FS_MEDIA" ]; then
|
||||
pass "torrents/ and media/ are on the same filesystem ($FS_TORRENTS)"
|
||||
pass "Hard links will work correctly"
|
||||
else
|
||||
fail "torrents/ ($FS_TORRENTS) and media/ ($FS_MEDIA) are on DIFFERENT filesystems!"
|
||||
fix "Hard links only work on the same filesystem/drive"
|
||||
fix "Move both directories to the same drive"
|
||||
fi
|
||||
|
||||
# Quick hard link test
|
||||
TEST_FILE="/data/torrents/.hardlink_test_$$"
|
||||
TEST_LINK="/data/media/.hardlink_test_$$"
|
||||
if touch "$TEST_FILE" 2>/dev/null && ln "$TEST_FILE" "$TEST_LINK" 2>/dev/null; then
|
||||
pass "Hard link test succeeded"
|
||||
rm -f "$TEST_FILE" "$TEST_LINK" 2>/dev/null
|
||||
elif [ -f "$TEST_FILE" ]; then
|
||||
fail "Hard link test failed — filesystem may not support hard links"
|
||||
fix "Check filesystem type: df -T /data"
|
||||
fix "Hard links work on ext4, btrfs, xfs. NOT on exFAT or ntfs-3g"
|
||||
rm -f "$TEST_FILE" 2>/dev/null
|
||||
else
|
||||
warn "Could not write to /data/torrents (permission issue?)"
|
||||
fix "Run: sudo chown -R $(id -u):$(id -g) /data"
|
||||
fi
|
||||
else
|
||||
warn "Folder structure not found — skipping hard link test"
|
||||
fix "Run: sudo bash setup-folders.sh"
|
||||
fi
|
||||
# ============================================================
|
||||
# SUMMARY
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo -e " ${GREEN}Passed: $TOTAL_PASS${NC} ${RED}Failed: $TOTAL_FAIL${NC} ${YELLOW}Warnings: $TOTAL_WARN${NC}"
|
||||
echo "========================================="
|
||||
|
||||
if [ $TOTAL_FAIL -eq 0 ] && [ $TOTAL_WARN -eq 0 ]; then
|
||||
echo -e "\n ${GREEN}${BOLD}All checks passed! Your stack is ready to go.${NC}\n"
|
||||
elif [ $TOTAL_FAIL -eq 0 ]; then
|
||||
echo -e "\n ${YELLOW}${BOLD}No failures, but check the warnings above.${NC}\n"
|
||||
else
|
||||
echo -e "\n ${RED}${BOLD}Some checks failed. Follow the fix instructions above.${NC}"
|
||||
echo -e " ${BOLD}If stuck, check: docker logs <container-name>${NC}\n"
|
||||
fi
|
||||
```
|
||||
+546
@@ -0,0 +1,546 @@
|
||||
---
|
||||
title: "loponai/arrstack: One-command media server (Sonarr/Radarr/Prowlarr/qBittorrent behind a VPN). Now maintained as SparkBox → tomsparkbox.com"
|
||||
source: https://github.com/loponai/arrstack
|
||||
author:
|
||||
published:
|
||||
created: 2026-07-09
|
||||
description: One-command media server (Sonarr/Radarr/Prowlarr/qBittorrent behind a VPN). Now maintained as SparkBox → tomsparkbox.com - loponai/arrstack
|
||||
tags:
|
||||
- clippings
|
||||
- Media
|
||||
---
|
||||
> ## ⚡ Want the easy, full version? → SparkBox
|
||||
>
|
||||
> This is one of my original one-shot scripts. It still works — but these days everything I build goes into **[SparkBox](https://tomsparkbox.com/)**: a free, self-hosted home server that sets up this whole stack (and a lot more — photos, files, password manager, ad-blocking) with **one command**, a real web dashboard, automatic updates with one-click rollback, and a built-in AI assistant that troubleshoots and fixes things for you. No hand-editing config files.
|
||||
>
|
||||
> ### 👉 Get it free at tomsparkbox.com
|
||||
>
|
||||
> *Built by [Tom Spark Reviews](https://youtube.com/@TomSparkReviews).*
|
||||
|
||||
---
|
||||
|
||||
## Tom Spark's ARR Stack
|
||||
|
||||
One-command automated media server with VPN protection. Sonarr, Radarr, Prowlarr, qBittorrent, Gluetun, Jellyfin, and more.
|
||||
|
||||
**Full video tutorial:** \[YouTube Link Coming Soon\]
|
||||
|
||||
## What You Get
|
||||
|
||||
| Service | Port | Purpose |
|
||||
| --- | --- | --- |
|
||||
| Gluetun | — | VPN tunnel with kill switch |
|
||||
| qBittorrent | 8080 | Torrent client (VPN protected) |
|
||||
| Prowlarr | 9696 | Indexer manager (VPN protected) |
|
||||
| FlareSolverr | 8191 | Cloudflare bypass (VPN protected) |
|
||||
| Radarr | 7878 | Movie automation |
|
||||
| Sonarr | 8989 | TV show automation |
|
||||
| Lidarr | 8686 | Music automation |
|
||||
| Bazarr | 6767 | Subtitle automation |
|
||||
| Jellyfin | 8096 | Media server / streaming |
|
||||
| Seerr | 5055 | Netflix-like request UI (Overseerr/Jellyseerr successor) |
|
||||
|
||||
All download traffic routes through Gluetun's VPN tunnel. If the VPN drops, all traffic stops — zero leaks. The deunhealth container auto-restarts services that become unhealthy.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1\. Install Docker
|
||||
|
||||
```
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
sudo usermod -aG docker $USER
|
||||
# Log out and back in for group change to take effect
|
||||
```
|
||||
|
||||
### 2\. Clone this repo
|
||||
|
||||
```
|
||||
git clone https://github.com/loponai/arrstack.git
|
||||
cd arrstack
|
||||
```
|
||||
|
||||
### 3\. Create folder structure
|
||||
|
||||
```
|
||||
sudo bash setup-folders.sh
|
||||
```
|
||||
|
||||
This creates:
|
||||
|
||||
```
|
||||
/data/
|
||||
├── torrents/ ← qBittorrent downloads here
|
||||
│ ├── movies/
|
||||
│ ├── tv/
|
||||
│ └── music/
|
||||
└── media/ ← Radarr/Sonarr organize files here (Jellyfin reads from here)
|
||||
├── movies/
|
||||
├── tv/
|
||||
└── music/
|
||||
```
|
||||
|
||||
> **Hard links:** Both folders MUST be on the same drive/filesystem. Radarr and Sonarr create hard links (not copies) — the file appears in both locations but only uses disk space once.
|
||||
|
||||
### 4\. Configure your VPN
|
||||
|
||||
```
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
Fill in your VPN provider and credentials. See [VPN Setup Guides](#vpn-setup-guides) below.
|
||||
|
||||
### 5\. Launch
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 6\. Verify everything is working
|
||||
|
||||
```
|
||||
bash test-stack.sh
|
||||
```
|
||||
|
||||
This runs a full health check — Docker status, VPN connection, IP leak test, service accessibility, hard link support, and folder permissions. If anything is wrong, it tells you exactly what to fix.
|
||||
|
||||
You can also check manually:
|
||||
|
||||
```
|
||||
# Check Gluetun's IP (should be VPN, not your real IP)
|
||||
docker exec gluetun wget -qO- ifconfig.me
|
||||
|
||||
# qBittorrent shares Gluetun's network, so the above proves both are tunneled.
|
||||
docker exec qbittorrent wget -qO- ifconfig.me
|
||||
|
||||
# Check health status of all containers
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
### 7\. Configure services
|
||||
|
||||
Open each service in your browser at `http://YOUR-SERVER-IP:PORT` and follow the video tutorial for step-by-step configuration.
|
||||
|
||||
**Quick reference:**
|
||||
|
||||
- **qBittorrent** (`:8080`) — Get temp password: `docker logs qbittorrent 2>&1 | grep "temporary password"`
|
||||
- **Prowlarr** (`:9696`) — Add indexers, connect to Radarr/Sonarr. If an indexer is blocked by Cloudflare, set up FlareSolverr as a proxy: Settings → Indexers → Add Proxy → FlareSolverr → host `http://localhost:8191` → give it a tag (e.g. `flaresolverr`). Then edit the blocked indexer and add the **same tag** so Prowlarr routes it through FlareSolverr.
|
||||
- **Radarr** (`:7878`) — Root folder: `/data/media/movies`, download client category: `movies`
|
||||
- **Sonarr** (`:8989`) — Root folder: `/data/media/tv`, download client category: `tv`
|
||||
- **Jellyfin** (`:8096`) — Add libraries: `/data/media/movies`, `/data/media/tv`, `/data/media/music`. To watch, open `http://YOUR-SERVER-IP:8096` in a browser or use the Jellyfin app (available on Roku, Fire TV, Apple TV, Android TV, iOS, Android). Find your server IP by running `hostname -I` in the terminal. If watching remotely with Tailscale, use your Tailscale IP instead.
|
||||
- **Seerr** (`:5055`) — Connect to Jellyfin, Radarr, and Sonarr. Seerr is the unified successor to Overseerr/Jellyseerr. If you previously ran Jellyseerr here, your existing config is migrated automatically on first start.
|
||||
|
||||
**Internal Docker IPs — use these when connecting services to each other (NOT localhost):**
|
||||
|
||||
| IP | Service |
|
||||
| --- | --- |
|
||||
| `172.39.0.2` | Gluetun (also qBittorrent, Prowlarr, FlareSolverr) |
|
||||
| `172.39.0.3` | Radarr |
|
||||
| `172.39.0.4` | Sonarr |
|
||||
| `172.39.0.5` | Lidarr |
|
||||
| `172.39.0.6` | Bazarr |
|
||||
| `172.39.0.7` | Jellyfin |
|
||||
| `172.39.0.8` | Seerr |
|
||||
|
||||
These IPs are the same for everyone — they're hardcoded in the docker-compose file.
|
||||
|
||||
**Common connections:**
|
||||
|
||||
- Radarr/Sonarr → Download Client → qBittorrent: host `172.39.0.2`, port `8080`
|
||||
- Prowlarr → Apps → Radarr: server `http://172.39.0.3:7878`
|
||||
- Prowlarr → Apps → Sonarr: server `http://172.39.0.4:8989`
|
||||
- Prowlarr → Apps → Prowlarr Server: `http://172.39.0.2:9696`
|
||||
- Seerr → Radarr: host `172.39.0.3`, port `7878`
|
||||
- Seerr → Sonarr: host `172.39.0.4`, port `8989`
|
||||
- Seerr → Jellyfin: host `172.39.0.7`, port `8096`
|
||||
|
||||
**Important Radarr/Sonarr settings:**
|
||||
|
||||
- Media Management → Show Advanced → **Use Hardlinks instead of Copy** → must be ON
|
||||
- Media Management → **Rename Movies/Episodes** → recommended ON
|
||||
|
||||
**Recommended quality profile (1080p baseline, 4K preferred):**
|
||||
|
||||
Go to Settings → Profiles and edit or create a profile:
|
||||
|
||||
1. Uncheck everything below 1080p (720p, 480p, etc.)
|
||||
2. Check/enable everything from **HDTV-1080p** up through **Bluray-2160p**
|
||||
3. Set **Cutoff** to `Bluray-1080p` — this is the minimum quality Radarr/Sonarr will be happy with
|
||||
4. Set **Upgrade Until** to `Bluray-2160p` — it will automatically upgrade to 4K if one becomes available
|
||||
|
||||
This means it grabs a 1080p release right away so you can start watching, then silently upgrades to 4K later if it finds one.
|
||||
|
||||
## VPN Setup Guides
|
||||
|
||||
### Surfshark (Recommended)
|
||||
|
||||
The best value for torrenting — cheapest long-term plans, fast WireGuard speeds, and easy setup with Gluetun. [Get Surfshark](https://get.surfshark.net/aff_c?offer_id=1126&aff_id=9447&aff_sub=8amjxr)
|
||||
|
||||
1. Go to [Surfshark Manual Setup](https://my.surfshark.com/vpn/manual-setup/main)
|
||||
2. Select **WireGuard** and get your credentials (private key + address)
|
||||
3. In your `.env`:
|
||||
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=surfshark
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_ADDRESSES=10.14.0.2/16
|
||||
SERVER_COUNTRIES=United States
|
||||
```
|
||||
|
||||
> The `.env.example` file is pre-configured for Surfshark. Just paste your private key and you're good to go.
|
||||
|
||||
### NordVPN
|
||||
|
||||
1. Go to [NordVPN Manual Setup](https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/)
|
||||
2. Select **NordLynx** (WireGuard) and generate a private key
|
||||
3. In your `.env`:
|
||||
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=nordvpn
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_ADDRESSES=10.5.0.2/16
|
||||
SERVER_COUNTRIES=United States
|
||||
```
|
||||
|
||||
### ProtonVPN
|
||||
|
||||
1. Go to [ProtonVPN WireGuard Config](https://account.protonvpn.com/) → Downloads → WireGuard
|
||||
2. Generate a config file, open it, copy `PrivateKey` and `Address`
|
||||
3. In your `.env`:
|
||||
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=protonvpn
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_ADDRESSES=10.2.0.2/32
|
||||
SERVER_COUNTRIES=United States
|
||||
VPN_PORT_FORWARDING=on
|
||||
```
|
||||
|
||||
### AirVPN
|
||||
|
||||
1. Go to [AirVPN Config Generator](https://airvpn.org/) → Client Area → Config Generator
|
||||
2. Select Linux → WireGuard → pick server → Generate
|
||||
3. In your `.env`:
|
||||
|
||||
```
|
||||
VPN_SERVICE_PROVIDER=airvpn
|
||||
VPN_TYPE=wireguard
|
||||
WIREGUARD_PRIVATE_KEY=your_key_here
|
||||
WIREGUARD_PUBLIC_KEY=server_public_key
|
||||
WIREGUARD_PRESHARED_KEY=your_preshared_key
|
||||
WIREGUARD_ADDRESSES=your_ip/32
|
||||
FIREWALL_VPN_INPUT_PORTS=your_port
|
||||
VPN_PORT_FORWARDING=on
|
||||
```
|
||||
|
||||
### Other Providers
|
||||
|
||||
Gluetun supports 30+ providers. Check the [full provider list](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers).
|
||||
|
||||
## Starting and Stopping
|
||||
|
||||
**Start the stack:**
|
||||
|
||||
```
|
||||
cd arrstack
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**Stop the stack:**
|
||||
|
||||
```
|
||||
cd arrstack
|
||||
docker compose down
|
||||
```
|
||||
|
||||
**Check status:**
|
||||
|
||||
```
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
### Auto-Start After Reboot
|
||||
|
||||
All containers are set to `restart: unless-stopped`, which means they automatically come back once Docker is running. You just need to make sure Docker itself starts when your computer boots up.
|
||||
|
||||
**Linux (dedicated server or VM):**
|
||||
|
||||
Run this once and you're done:
|
||||
|
||||
```
|
||||
sudo systemctl enable docker
|
||||
```
|
||||
|
||||
**Windows (running Docker inside WSL):**
|
||||
|
||||
WSL (Windows Subsystem for Linux) doesn't start Docker automatically when your PC boots. Here's how to fix that:
|
||||
|
||||
**Step 1:** Open your WSL terminal and run this command to edit the WSL config file:
|
||||
|
||||
```
|
||||
sudo nano /etc/wsl.conf
|
||||
```
|
||||
|
||||
**Step 2:** Your file might already have some lines in it (like `[boot]` or `[user]`). Look for a `[boot]` section. If it exists, add the `command=` line under it. If it doesn't exist, add both lines. It should look like this when you're done:
|
||||
|
||||
```
|
||||
[boot]
|
||||
command=service docker start
|
||||
```
|
||||
|
||||
**Step 3:** Save the file by pressing `Ctrl+X`, then `Y`, then `Enter`.
|
||||
|
||||
**Step 4 (optional):** By default, WSL only starts when you open a terminal. If you want it to start automatically when Windows boots (so your stack is always running), do this:
|
||||
|
||||
1. Press `Win+R` on your keyboard
|
||||
2. Type `shell:startup` and press Enter — this opens your Windows Startup folder
|
||||
3. Right-click in the folder → New → Text Document
|
||||
4. Name it `wsl.vbs` (make sure it ends in `.vbs`, not `.vbs.txt` — if you can't see file extensions, go to View → Show → File name extensions in File Explorer)
|
||||
5. Right-click the file → Edit (or Open with Notepad) and paste this:
|
||||
```
|
||||
Set ws = CreateObject("Wscript.Shell")
|
||||
ws.Run "wsl -d Ubuntu -u root -- service docker start", 0
|
||||
```
|
||||
6. Save and close
|
||||
|
||||
That's it — next time your PC restarts, WSL starts Docker automatically and all your containers come back up on their own. No commands needed.
|
||||
|
||||
## Updating
|
||||
|
||||
```
|
||||
cd arrstack
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Remote Access with Tailscale (Optional)
|
||||
|
||||
Want to access Jellyfin, Seerr, or any service from outside your home? [Tailscale](https://tailscale.com/) creates a private network between your devices — no port forwarding, no exposing anything to the public internet.
|
||||
|
||||
**On your server:**
|
||||
|
||||
```
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
sudo tailscale up
|
||||
```
|
||||
|
||||
**On your phone/laptop/TV:**
|
||||
|
||||
1. Install Tailscale from your app store
|
||||
2. Sign in with the same account
|
||||
|
||||
**Access your services from anywhere:**
|
||||
|
||||
```
|
||||
http://YOUR-TAILSCALE-IP:8096 ← Jellyfin
|
||||
http://YOUR-TAILSCALE-IP:5055 ← Seerr
|
||||
http://YOUR-TAILSCALE-IP:7878 ← Radarr
|
||||
http://YOUR-TAILSCALE-IP:8989 ← Sonarr
|
||||
```
|
||||
|
||||
Find your Tailscale IP with `tailscale ip -4` on the server.
|
||||
|
||||
Tailscale is free for personal use (up to 100 devices). Everything is encrypted with WireGuard — nobody can see your traffic, not even Tailscale.
|
||||
|
||||
> **Do NOT expose Jellyfin directly to the internet** (no port forwarding on your router). Use Tailscale or a reverse proxy instead. Direct exposure is a security risk.
|
||||
|
||||
### Sharing with Family and Friends
|
||||
|
||||
Your family and friends only need two things — **Seerr** to request movies/shows and **Jellyfin** to watch them. They never see Radarr, Sonarr, qBittorrent, or any of the behind-the-scenes stuff.
|
||||
|
||||
**Step 1: Invite them to your Tailscale network**
|
||||
|
||||
1. Go to the [Tailscale admin console](https://login.tailscale.com/admin/machines)
|
||||
2. Click **Share** on your server's machine
|
||||
3. Enter their email — they'll get an invite link
|
||||
|
||||
**Step 2: They install Tailscale**
|
||||
|
||||
1. Download Tailscale on their phone, laptop, or TV from [tailscale.com/download](https://tailscale.com/download)
|
||||
2. Accept your invite and sign in
|
||||
|
||||
**Step 3: They access your services**
|
||||
|
||||
Give them these two links (replace with your Tailscale IP):
|
||||
|
||||
```
|
||||
http://YOUR-TAILSCALE-IP:5055 ← Seerr (request movies and shows)
|
||||
http://YOUR-TAILSCALE-IP:8096 ← Jellyfin (watch everything)
|
||||
```
|
||||
|
||||
**For TVs and phones**, they can install the **Jellyfin app** (available on Roku, Fire TV, Apple TV, Android TV, iOS, Android) and enter your Tailscale IP as the server address during setup.
|
||||
|
||||
That's it — they request, you automatically download, they watch. No technical knowledge needed on their end.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Gluetun unhealthy / won't connect:**
|
||||
|
||||
- Double-check VPN credentials in `.env` — these are NOT your login email/password
|
||||
- Try removing the gluetun folder and restarting: `rm -rf gluetun && docker compose up -d`
|
||||
- Check logs: `docker logs gluetun`
|
||||
|
||||
**qBittorrent can't connect:**
|
||||
|
||||
- Make sure Gluetun is healthy: `docker ps` (should show "healthy")
|
||||
- Check qBit is using VPN: `docker exec gluetun wget -qO- ifconfig.me`
|
||||
- In qBittorrent settings → Advanced → set Network Interface to `tun0`
|
||||
|
||||
**Movie or show not downloading:**
|
||||
|
||||
- **Quality profile too strict** — If Radarr/Sonarr can't find a release matching your quality profile, it won't download anything. Go to the movie/show → check if it says "No results found" or similar. Try lowering your cutoff temporarily (e.g. from Bluray-1080p to WEBDL-1080p) or enabling more quality tiers in your profile.
|
||||
- **Not enough indexers** — Public indexers have limited catalogs. If you only have one or two indexers in Prowlarr, add more (1337x, The Pirate Bay, LimeTorrents, EZTV). The more indexers you have, the more results you'll get.
|
||||
- **Not enough seeders** — Some torrents just don't have anyone sharing them, especially older or niche content. Check qBittorrent — if the torrent is stuck at 0% with 0 seeds, there's nothing to download. Try searching manually in Radarr/Sonarr for a different release with more seeders.
|
||||
- **Indexer blocked by Cloudflare** — See the Prowlarr setup note above about setting up FlareSolverr with tags.
|
||||
|
||||
**Hard links not working (files copying instead):**
|
||||
|
||||
- Both `/data/torrents` and `/data/media` must be on the same filesystem
|
||||
- Check Radarr/Sonarr → Settings → Media Management → "Use Hardlinks" is checked
|
||||
- Verify with: `ls -i /data/torrents/movies/yourfile` and `ls -i /data/media/movies/YourMovie/yourfile` — inode numbers should match
|
||||
|
||||
**Permission errors:**
|
||||
|
||||
- Run `id` and make sure PUID/PGID in `.env` match your user
|
||||
- Re-run: `sudo chown -R $(id -u):$(id -g) /data`
|
||||
|
||||
**Service won't start — "port already in use":**
|
||||
|
||||
Another program on your system might be using the same port. This is common with port 5055 (Seerr) but can happen with any service.
|
||||
|
||||
1. Find what's using the port (replace `5055` with the port number from the error):
|
||||
**Linux:**
|
||||
```
|
||||
sudo ss -tlnp | grep 5055
|
||||
```
|
||||
**Windows (WSL users) — run in PowerShell:**
|
||||
```
|
||||
netstat -ano | findstr :5055
|
||||
```
|
||||
This gives you a PID (process ID). Find the program name:
|
||||
```
|
||||
Get-Process -Id <PID> | Select-Object ProcessName, Id, Path
|
||||
```
|
||||
2. Either stop/disable that program, or change the port in `docker-compose.yml` to an unused one (e.g. `5056:5055`).
|
||||
3. If it's a Windows service hogging the port, disable it in an admin PowerShell:
|
||||
```
|
||||
Stop-Service <ServiceName> -Force
|
||||
Set-Service <ServiceName> -StartupType Disabled
|
||||
```
|
||||
4. Then recreate the container:
|
||||
```
|
||||
docker compose up -d --force-recreate <service-name>
|
||||
```
|
||||
|
||||
**Seerr stuck restarting / crash-looping:**
|
||||
|
||||
The current `docker-compose.yml` uses a named volume (`seerr_config`) for Seerr's config, which fixes both causes of the crash loop. If you're still hitting it, you're almost certainly on an older version of this repo that used a bind mount. `git pull` first.
|
||||
|
||||
Root cause (for reference):
|
||||
|
||||
- **Linux/macOS:** Seerr runs as the `node` user (UID 1000). A bind-mounted `./seerr` folder is created root-owned on first `docker compose up`, so the container can't write to `/app/config` and crash-loops. Upstream Seerr docs require `chown -R 1000:1000` on the config dir before first start.
|
||||
- **Windows/WSL:** Bind mounts go through an SMB share inside Docker Desktop's VM, which doesn't support file locking. Seerr's SQLite DB corrupts on first write. Upstream Seerr docs explicitly say: **do not bind-mount `/app/config` on Windows** — use a named volume.
|
||||
|
||||
Named volumes sidestep both problems because Docker creates them with correct ownership inside its own managed storage.
|
||||
|
||||
If you still see it after pulling:
|
||||
|
||||
- Check logs: `docker logs seerr | tail -30`
|
||||
- Nuke the volume and start fresh (you'll lose Seerr settings, not media):
|
||||
```
|
||||
docker compose down seerr && docker volume rm arrstack_seerr_config && docker compose up -d seerr
|
||||
```
|
||||
|
||||
**Migrating Seerr config to a named volume (existing installs):**
|
||||
|
||||
If you had Seerr working on an older version of this repo with a bind-mounted `./jellyseerr` or `./seerr` folder and want to keep your settings, copy the data into the new named volume before starting:
|
||||
|
||||
```
|
||||
docker compose down seerr
|
||||
# pick whichever folder you actually have
|
||||
SRC=./seerr
|
||||
[ -d ./jellyseerr ] && SRC=./jellyseerr
|
||||
docker volume create arrstack_seerr_config
|
||||
docker run --rm -v "$(pwd)/${SRC#./}":/src -v arrstack_seerr_config:/dest alpine sh -c "cp -a /src/. /dest/ && chown -R 1000:1000 /dest"
|
||||
docker compose up -d seerr
|
||||
```
|
||||
|
||||
If you don't care about preserving settings, just `docker compose up -d seerr` — Seerr will start fresh and walk you through setup again.
|
||||
|
||||
**Can't log into qBittorrent:**
|
||||
|
||||
- qBittorrent generates a temporary password every time it starts. Get it with:
|
||||
```
|
||||
docker logs qbittorrent 2>&1 | grep "temporary password"
|
||||
```
|
||||
- Default username is `admin`. Once logged in, go to Tools → Options → Web UI and set a permanent password.
|
||||
|
||||
**Services can't connect to each other (connection refused, timeout):**
|
||||
|
||||
- Don't use `localhost` when connecting services together — that won't work across Docker containers.
|
||||
- Use the internal Docker IPs instead:
|
||||
- qBittorrent/Prowlarr/FlareSolverr: `172.39.0.2`
|
||||
- Radarr: `172.39.0.3`
|
||||
- Sonarr: `172.39.0.4`
|
||||
- Jellyfin: `172.39.0.7`
|
||||
- The one exception: Prowlarr → FlareSolverr can use `localhost:8191` because they both run through Gluetun and share the same network.
|
||||
|
||||
**"Root folder does not exist" in Radarr/Sonarr:**
|
||||
|
||||
- Make sure you ran `sudo bash setup-folders.sh` to create the `/data` directory structure.
|
||||
- Double-check the root folder path — it should be `/data/media/movies` for Radarr and `/data/media/tv` for Sonarr (not `/movies` or `/data/movies`).
|
||||
|
||||
**Downloads stuck at "importing" or "waiting to import":**
|
||||
|
||||
- This is almost always a permissions issue. Fix it with:
|
||||
```
|
||||
sudo chown -R $(id -u):$(id -g) /data
|
||||
sudo chmod -R 775 /data
|
||||
```
|
||||
- Make sure PUID/PGID in your `.env` match your user (check with `id`).
|
||||
|
||||
**Jellyfin library is empty after downloads finish:**
|
||||
|
||||
- Make sure your Jellyfin libraries point to the correct paths: `/data/media/movies`, `/data/media/tv`, `/data/media/music`
|
||||
- Jellyfin doesn't scan instantly. Go to Dashboard → Libraries → click the `...` menu → **Scan Library** to force a refresh.
|
||||
- You can also set up scheduled scans in Dashboard → Scheduled Tasks.
|
||||
|
||||
**Subtitles not downloading (Bazarr):**
|
||||
|
||||
- Bazarr needs to be connected to Radarr and Sonarr: Settings → Radarr / Sonarr → enter the IP (`172.39.0.3` / `172.39.0.4`) and API key.
|
||||
- You also need at least one subtitle provider: Settings → Providers → Add → **OpenSubtitles.com** is the most popular (free account required).
|
||||
|
||||
**Everything works but downloads are slow:**
|
||||
|
||||
- Your VPN server might be too far away. Change `SERVER_COUNTRIES` in your `.env` to a country closer to you, then restart:
|
||||
```
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
- Check your VPN speed: `docker exec gluetun wget -qO- https://speed.cloudflare.com/__down?measId=10000000 > /dev/null` — if it's very slow, try a different country.
|
||||
|
||||
**Disk space filling up:**
|
||||
|
||||
- By default, qBittorrent keeps torrents after Radarr/Sonarr imports them. To auto-clean:
|
||||
- In Radarr/Sonarr → Settings → Download Clients → click on qBittorrent → enable **Remove Completed**
|
||||
- This deletes the torrent from qBittorrent after the file has been imported (the hard link in your media folder is kept, so you don't lose anything).
|
||||
|
||||
**VPN IP leak — want to make sure your real IP isn't exposed:**
|
||||
|
||||
```
|
||||
# Check the VPN container's IP (should NOT be your real IP)
|
||||
docker exec gluetun wget -qO- ifconfig.me
|
||||
|
||||
# Compare with your real IP (run this outside Docker)
|
||||
curl -s ifconfig.me
|
||||
```
|
||||
|
||||
If both IPs are the same, your VPN isn't working — check Gluetun logs with `docker logs gluetun`.
|
||||
|
||||
## Credits
|
||||
|
||||
Built by [Tom Spark](https://youtube.com/@tomspark) following [Trash Guides](https://trash-guides.info/) and [Servarr Wiki](https://wiki.servarr.com/) best practices.
|
||||
|
||||
Uses [Gluetun](https://github.com/qdm12/gluetun) for VPN, [LinuxServer.io](https://linuxserver.io/) container images, and [Seerr](https://github.com/seerr-team/seerr) for the request system (the unified successor to Overseerr/Jellyseerr).
|
||||
@@ -0,0 +1,135 @@
|
||||
```
|
||||
# ============================================================
|
||||
# Tom Spark's ARR Stack — Environment Configuration
|
||||
# https://github.com/loponai/arrstack
|
||||
#
|
||||
# INSTRUCTIONS:
|
||||
# 1. Copy this file: cp .env.example .env
|
||||
# 2. Fill in your VPN credentials below
|
||||
# 3. Adjust timezone and user IDs if needed
|
||||
# 4. Run: docker compose up -d
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
# SYSTEM SETTINGS
|
||||
# ============================================================
|
||||
|
||||
# Your timezone (list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
|
||||
TZ=America/New_York
|
||||
|
||||
# Your Linux user/group ID. Find yours with: id
|
||||
# Most systems default to 1000. If yours is different, change these.
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
|
||||
# ============================================================
|
||||
# VPN SETTINGS — Pick your provider and fill in credentials
|
||||
# Full provider list: https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers
|
||||
#
|
||||
# IMPORTANT: VPN credentials are NOT your login email/password!
|
||||
# You need service credentials from your VPN provider's manual
|
||||
# setup or API section. See the provider examples at the bottom of this file.
|
||||
# ============================================================
|
||||
|
||||
# Your VPN provider (surfshark, nordvpn, protonvpn, airvpn, mullvad, private internet access, windscribe, etc.)
|
||||
VPN_SERVICE_PROVIDER=airvpn
|
||||
|
||||
# Protocol: wireguard (recommended, faster) or openvpn
|
||||
VPN_TYPE=wireguard
|
||||
|
||||
# --- WIREGUARD CREDENTIALS ---
|
||||
# For Surfshark: go to https://my.surfshark.com/vpn/manual-setup/main → WireGuard
|
||||
# For other providers: see the examples at the bottom of this file
|
||||
WIREGUARD_PRIVATE_KEY=eDgf3GPFy2ltgx2RkD/Vx5wKZ4dVi28YbQmSJOrbWmk=
|
||||
WIREGUARD_ADDRESSES=10.175.23.30
|
||||
# Only needed for some providers (AirVPN). Leave blank if not required.
|
||||
WIREGUARD_PUBLIC_KEY=
|
||||
WIREGUARD_PRESHARED_KEY=+0+hrSdzRUxKDNk1Q37PNmNJ2jsj2EzF45JtbZad4lI=
|
||||
|
||||
# --- OPENVPN CREDENTIALS ---
|
||||
# Only needed if VPN_TYPE=openvpn. Leave blank if using WireGuard.
|
||||
OPENVPN_USER=
|
||||
OPENVPN_PASSWORD=
|
||||
|
||||
# --- SERVER SELECTION ---
|
||||
# Pick a country close to you for best speeds
|
||||
SERVER_COUNTRIES=Canada
|
||||
|
||||
# --- PORT FORWARDING ---
|
||||
# Supported by: ProtonVPN, AirVPN, PIA. Can help with upload speeds and seeding.
|
||||
# Not required for downloading. Most users don't need this.
|
||||
# Set to "on" if your provider supports it, leave blank otherwise (Surfshark, NordVPN, etc.).
|
||||
VPN_PORT_FORWARDING=
|
||||
# If your provider requires manually specifying a port (e.g. AirVPN):
|
||||
FIREWALL_VPN_INPUT_PORTS=29261
|
||||
# ============================================================
|
||||
# NETWORK — Static IPs for each service
|
||||
# You shouldn't need to change these unless you have a conflict.
|
||||
# ============================================================
|
||||
IP_GLUETUN=172.39.0.2
|
||||
IP_RADARR=172.39.0.3
|
||||
IP_SONARR=172.39.0.4
|
||||
IP_LIDARR=172.39.0.5
|
||||
IP_BAZARR=172.39.0.6
|
||||
IP_JELLYFIN=172.39.0.7
|
||||
IP_SEERR=172.39.0.8
|
||||
IP_AUDIO=172.39.0.9
|
||||
IP_NAVI=172.39.0.10
|
||||
|
||||
# ============================================================
|
||||
# PROVIDER-SPECIFIC EXAMPLES
|
||||
# Uncomment and fill in the section for your VPN provider.
|
||||
# ============================================================
|
||||
|
||||
# --- NORDVPN ---
|
||||
# 1. Go to: https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/
|
||||
# 2. Generate a WireGuard private key (NordLynx)
|
||||
# 3. Paste the private key below
|
||||
# VPN_SERVICE_PROVIDER=nordvpn
|
||||
# VPN_TYPE=wireguard
|
||||
# WIREGUARD_PRIVATE_KEY=your_nordvpn_private_key_here
|
||||
# WIREGUARD_ADDRESSES=10.5.0.2/16
|
||||
# SERVER_COUNTRIES=United States
|
||||
|
||||
# --- PROTONVPN ---
|
||||
# 1. Go to: https://account.protonvpn.com/ → Downloads → WireGuard configuration
|
||||
# 2. Generate a config, open the file, copy the PrivateKey and Address
|
||||
# 3. Port forwarding is supported on paid plans
|
||||
# VPN_SERVICE_PROVIDER=protonvpn
|
||||
# VPN_TYPE=wireguard
|
||||
# WIREGUARD_PRIVATE_KEY=your_proton_private_key_here
|
||||
# WIREGUARD_ADDRESSES=10.2.0.2/32
|
||||
# SERVER_COUNTRIES=United States
|
||||
# VPN_PORT_FORWARDING=on
|
||||
|
||||
# --- SURFSHARK ---
|
||||
# 1. Go to: https://my.surfshark.com/vpn/manual-setup/main
|
||||
# 2. Get WireGuard credentials
|
||||
# VPN_SERVICE_PROVIDER=surfshark
|
||||
# VPN_TYPE=wireguard
|
||||
# WIREGUARD_PRIVATE_KEY=your_surfshark_private_key_here
|
||||
# WIREGUARD_ADDRESSES=10.14.0.2/16
|
||||
# SERVER_COUNTRIES=United States
|
||||
|
||||
# --- AIRVPN ---
|
||||
# 1. Go to: https://airvpn.org/ → Client Area → Config Generator
|
||||
# 2. Select Linux → WireGuard → pick a server → Generate
|
||||
# 3. Copy all keys and the assigned IP
|
||||
# VPN_SERVICE_PROVIDER=airvpn
|
||||
# VPN_TYPE=wireguard
|
||||
# WIREGUARD_PRIVATE_KEY=your_airvpn_private_key_here
|
||||
# WIREGUARD_PUBLIC_KEY=your_airvpn_public_key_here
|
||||
# WIREGUARD_PRESHARED_KEY=your_airvpn_preshared_key_here
|
||||
# WIREGUARD_ADDRESSES=your_assigned_ip/32
|
||||
# FIREWALL_VPN_INPUT_PORTS=your_forwarded_port
|
||||
# VPN_PORT_FORWARDING=on
|
||||
|
||||
# --- MULLVAD ---
|
||||
# 1. Go to: https://mullvad.net/en/account → WireGuard configuration
|
||||
# VPN_SERVICE_PROVIDER=mullvad
|
||||
# VPN_TYPE=wireguard
|
||||
# WIREGUARD_PRIVATE_KEY=your_mullvad_private_key_here
|
||||
# WIREGUARD_ADDRESSES=your_assigned_ip/32
|
||||
# SERVER_COUNTRIES=United States
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user