| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
There are dozens of *arr stack Docker Compose repos on GitHub. Almost all of them dump a compose file and leave you to figure out the rest. This one is different:
New to self-hosted media? Start with the basic version first.
| Service | What It Does |
|---|---|
| Tdarr | Native-first transcoding on macOS with quality-preserving HEVC flow preset |
| Recyclarr | TRaSH Guides quality profiles (penalizes bad release groups, scene releases) |
| Kometa | Plex metadata automation (franchise collections, resolution overlays, RT ratings) |
| Unpackerr | Auto-extracts RAR'd downloads for Radarr/Sonarr |
| Jellyfin | Free, open-source media server (alternative to Plex, runs in Docker) |
| Intro Skipper | Jellyfin plugin: auto-detects intros and adds a "Skip Intro" button on TV shows |
| TMDb Box Sets | Jellyfin plugin: auto-creates franchise collections from TMDb data (Jellyfin's Kometa) |
| Jellystat | Jellyfin analytics dashboard (like Tautulli for Plex: watch history, user stats, library insights) |
| Cloud / NAS Storage | rclone + mergerfs: transparent remote/local merged library (Google Drive, S3, B2, Dropbox, NAS via SFTP) |
| Plex | Jellyfin | |
|---|---|---|
| Cost | Free tier + optional Plex Pass | Completely free and open-source |
| Setup | Install macOS app, runs natively | Runs in Docker, no app install |
| Remote access | Built-in (Plex account) | Manual (reverse proxy) |
| Kometa support | Yes (metadata automation) | No (use Jellyfin's built-in collections) |
| Franchise sort | Yes (via franchise-sort.py) | No (TMDb Box Sets plugin auto-creates collections) |
| Analytics | Tautulli (external) | Jellystat (included, Docker) |
| Intro skip | Plex Pass feature | Intro Skipper plugin (free, auto-installed) |
| Client apps | Plex apps on all platforms | Jellyfin apps + browser |
Default is Plex. To use Jellyfin, pass --jellyfin to the bootstrap command.
When running with MEDIA_SERVER=jellyfin, the stack includes these extras that close the gap with Plex Pass users:
| Feature | What It Does | Plex Equivalent |
|---|---|---|
| Intro Skipper | Detects TV show intros via audio fingerprinting, adds a "Skip Intro" button | Plex Pass intro skip |
| TMDb Box Sets | Auto-creates franchise collections (Marvel, Star Wars, etc.) from TMDb data | Kometa collections |
| Jellystat | Watch history, user activity, library stats dashboard at http://localhost:3000 | Tautulli |
Intro Skipper and TMDb Box Sets are auto-installed as Jellyfin plugins by configure.sh. Jellystat runs as a separate Docker service behind the jellyfin profile.
After the stack is running:
Jellystat will start tracking watch history, user activity, and library statistics automatically.
Transparent cloud/local or NAS/local merged library. rclone FUSE-mounts your remote storage inside Docker (where FUSE works natively on macOS), mergerfs overlays it with local storage, and all existing services see a single unified path. Local-first writes keep downloads fast; a periodic upload script moves stable media to the remote.
Supports Google Drive, S3, Backblaze B2, Dropbox, NAS via SFTP (TrueNAS, Synology, Unraid), and 40+ other providers.
bash scripts/setup-cloud-storage.sh
docker compose -f docker-compose.yml -f docker-compose.cloud-storage.yml --profile cloud-storage --profile jellyfin --profile tdarr-docker up -dIf you also use optional profiles, append them to the same command (example: --profile jellyfin, --profile music, --profile tdarr-docker).
Or include it in bootstrap:
bash bootstrap.sh --jellyfin --cloud-storagebash scripts/setup-cloud-storage.sh --storage-type nas
docker compose -f docker-compose.yml -f docker-compose.cloud-storage.yml --profile cloud-storage --profile jellyfin --profile tdarr-docker up -dOr include it in bootstrap:
bash bootstrap.sh --jellyfin --nas-storagerclone-mount --> FUSE mounts cloud provider to /cloud mergerfs --> overlays /local + /cloud into /merged Radarr/Sonarr --> read/write /merged (local-first writes) cloud-upload --> periodically moves stable files to remote (6h/24h for cloud, 2h for NAS)
| Variable | Default | Description |
|---|---|---|
| CLOUD_STORAGE_ENABLED | false | Set to true to enable |
| RCLONE_REMOTE | (required) | Remote name from rclone.conf |
| RCLONE_REMOTE_PATH | (empty) | Subfolder on remote |
| RCLONE_VFS_CACHE_MODE | full | VFS cache mode (full recommended) |
| RCLONE_VFS_CACHE_MAX_SIZE | 50G | Max local cache size |
| RCLONE_VFS_CACHE_MAX_AGE | 72h | Max cache age |
| RCLONE_VFS_READ_CHUNK_SIZE | 128M | Read chunk size |
| CLOUD_UPLOAD_MIN_AGE_HOURS | 24 | Only upload files older than this |
| Variable | Default | Description |
|---|---|---|
| STORAGE_TYPE | (empty) | Set to nas for NAS mode |
| RCLONE_DIR_CACHE_TIME | 30s | Directory listing cache (shorter for LAN) |
| RCLONE_VFS_CACHE_MAX_SIZE | 10G | Smaller cache needed on fast LAN |
| RCLONE_VFS_CACHE_MAX_AGE | 1h | Shorter cache age on LAN |
| RCLONE_VFS_READ_CHUNK_SIZE | 32M | Read chunk size |
| CLOUD_UPLOAD_MIN_AGE_HOURS | 2 | Upload files older than 2h (vs 24h for cloud) |
Platform notes:
Performance: rclone SFTP delivers ~100MB/s on Gigabit LAN, adequate for multiple concurrent 4K streams.
Cloud mode note: Use TDARR_MODE=docker when cloud storage is enabled. Native Tdarr cannot access merged Docker FUSE mounts on macOS.
| Service | What It Does |
|---|---|
| Lidarr | Automatic music management (like Sonarr/Radarr but for music albums) |
| Tidarr | Downloads FLAC from Tidal (up to 24-bit/192kHz). Web UI + Lidarr integration |
Music services use Docker Compose profiles and are not started by default. To enable:
bash scripts/setup-music.sh
docker compose --profile music up -dIf cloud storage is enabled, use:
docker compose -f docker-compose.yml -f docker-compose.cloud-storage.yml --profile cloud-storage --profile music up -dThen open Tidarr at http://localhost:8484 to authenticate with your Tidal account, and Lidarr at http://localhost:8686 to configure your music library. See the Music Setup section below for details.
| Script | Schedule | What It Does |
|---|---|---|
| Auto-healer | Hourly | Restarts VPN/containers if they go down (includes Jellyfin + Jellystat when active) |
| Nightly backup | Daily | Backs up all configs and databases (14-day retention) |
| Download watchdog | Every 15 min | Detects stalled/slow torrents, auto-fixes or swaps them |
| Kometa | Every 4 hours | Updates Plex collections and metadata overlays |
| Log prune | Daily | Removes log files older than 30 days |
| Franchise sort | Optional/manual | Sorts franchise collection movies by release date in Plex (Plex only) |
| VPN failover | Every 2 min (optional) | Auto-switches between ProtonVPN and NordVPN on sustained failure |
| Cloud upload | Every 2-6 hours (optional) | Moves stable local media to remote storage (NAS: 2h, cloud: 6h) |
| Watchtower | Daily at 04:00 (optional) | Auto-pulls latest container images and recreates updated services |
Franchise sorting is kept manual by default because it requires your Plex token: PLEX_TOKEN=... python3 scripts/franchise-sort.py
The download watchdog reads qBittorrent credentials and behavior settings from environment variables or automatically detects them from your config files. Optional environment variables:
Set these in .env or your shell environment if you need to customize watchdog behavior.
Requires OrbStack (or Docker Desktop) and either Plex installed or the --jellyfin flag. Handles everything else.
curl -fsSL https://raw.githubusercontent.com/liamvibecodes/mac-media-stack-advanced/main/bootstrap.sh | bashOptional flags when running from a local clone:
bash bootstrap.sh --media-dir /Volumes/T9/Media --install-dir ~/mac-media-stack-advanced --non-interactiveUse Docker-based Tdarr instead of native Tdarr:
bash bootstrap.sh --tdarr-dockerTo use Jellyfin instead of Plex:
bash bootstrap.sh --jellyfinNAS storage via SFTP (TrueNAS, Synology, Unraid):
bash bootstrap.sh --jellyfin --nas-storageAlready cloned an older version and want the latest release tag without reinstalling?
One-liner (run inside your existing clone directory):
bash <(curl -fsSL https://raw.githubusercontent.com/liamvibecodes/mac-media-stack-advanced/main/scripts/update-to-latest-release.sh)Local script (once present):
bash scripts/update-to-latest-release.shAlready running mac-media-stack and want to keep your existing library/configs?
Use the one-shot upgrader: UPGRADE.md
bash scripts/upgrade-from-basic.sh
If you prefer to run each step yourself:
git clone https://github.com/liamvibecodes/mac-media-stack-advanced.git
cd mac-media-stack-advanced
bash scripts/setup.sh # or: bash scripts/setup.sh --media-dir /Volumes/T9/Media
# edit .env with VPN keys
bash scripts/doctor.sh # preflight validation before first boot
docker compose up -d
# if MEDIA_SERVER=jellyfin in .env:
docker compose --profile jellyfin up -d
# if TDARR_MODE=docker in .env:
docker compose --profile tdarr-docker up -d
# if TDARR_MODE=native in .env (default):
bash scripts/setup-tdarr-native.sh
docker compose --profile autoupdate up -d watchtower # optional auto-updates
bash scripts/configure.sh
bash scripts/install-launchd-jobs.shThe watchtower line above enables automatic container image updates (scheduled daily at 04:00 in compose). It's optional but recommended so your services stay patched without manual pulls.
See SETUP.md for the complete walkthrough. Upgrading from basic? See UPGRADE.md. Pinned digest matrix: IMAGE_LOCK.md
By default, Seerr is bound to 127.0.0.1 for safer local-only access. Set SEERR_BIND_IP=0.0.0.0 in .env only if you intentionally want LAN exposure.
Seerr (request) -> Radarr/Sonarr -> Prowlarr (search) -> qBittorrent (via VPN) -> Plex or Jellyfin (watch)
|
Unpackerr (extract) --+
Bazarr (subtitles) ----+
Tdarr (transcode) -----+
Kometa (metadata) ------> Plex
Recyclarr (quality) ----> Radarr/Sonarr
Optional music:
Lidarr (music management) -> Prowlarr / Tidarr -> Plex (listen)
Tidarr (Tidal FLAC downloads) ----^
All download traffic routes through ProtonVPN (with optional NordVPN failover). Gluetun's built-in kill switch blocks traffic if the VPN drops, so your real IP is never exposed through the tunnel. Everything else uses your normal connection. All services auto-start on boot and self-heal if they go down.
To manually switch providers after creating .env.nord from .env.nord.example:
bash scripts/vpn-mode.sh nord| Script | Purpose |
|---|---|
| scripts/setup.sh | Creates folders, generates .env, copies config templates |
| scripts/doctor.sh | Runs preflight checks (runtime, env, compose, ports) |
| scripts/upgrade-from-basic.sh | One-shot migration from basic stack to advanced |
| scripts/configure.sh | Auto-configures all service connections via API |
| scripts/health-check.sh | Full stack health diagnostic |
| scripts/install-launchd-jobs.sh | Installs all automation as background jobs |
| scripts/install-vpn-failover.sh | Installs VPN failover (requires NordVPN backup) |
| scripts/auto-heal.sh | Hourly self-healer |
| scripts/backup.sh | Config and database backup |
| scripts/download-watchdog.py | Stalled torrent detection and auto-fix |
| scripts/update-to-latest-release.sh | Updates an older clone to the latest tagged release safely |
| scripts/setup-tdarr-native.sh | Installs/updates native Tdarr and launchd services |
| scripts/tdarr-apply-quality-flow.sh | Loads quality-first HEVC flow preset into Tdarr DB |
| scripts/vpn-mode.sh | Manual VPN provider switcher |
| scripts/vpn-failover-watch.sh | Automatic VPN failover daemon |
| scripts/run-kometa.sh | Trigger Kometa metadata run |
| scripts/setup-cloud-storage.sh | Sets up rclone + mergerfs cloud storage integration |
| scripts/cloud-upload.sh | Periodic upload of local media to cloud storage |
| scripts/setup-music.sh | Creates music directories and Tidarr config (optional) |
| scripts/log-prune.sh | Prunes old log files (30-day default retention) |
| scripts/franchise-sort.py | Auto-sorts franchise collections in Plex by release date (Plex only) |
| scripts/music-cleanup.py | Fixes music metadata and folder naming (optional, music profile) |
| scripts/archive-media.sh | Move old/watched media to an external archive drive (supports Plex and Jellyfin) |
| scripts/refresh-image-lock.sh | Refreshes pinned image digests and regenerates IMAGE_LOCK.md |
Pre-configured templates in configs/ (copy to your Media folder after first boot):
Recyclarr API keys are auto-injected by scripts/configure.sh. Kometa still needs your manual Plex token + TMDB API key.
Music is optional and uses Docker Compose profiles. The core stack works without it.
# 1. Create music directories and config
bash scripts/setup-music.sh
# 2. Start the music services
docker compose --profile music up -d
# 3. Authenticate with Tidal
# Open http://localhost:8484 and follow the OAuth device flow
# 4. Configure Lidarr
# Open http://localhost:8686
# - Settings > Media Management > Add root folder: /music
# - Settings > Download Clients > Add SABnzbd:
# Host: tidarr, Port: 8484, URL Base: /api/sabnzbd
# - Settings > Indexers > Add Newznab:
# URL: http://tidarr:8484, API Path: /api/lidarr
# Categories: 3000, 3010, 3040
# - Settings > Download Clients > Add qBittorrent (for torrent fallback):
# Host: gluetun, Port: 8080If cloud storage is enabled, use this start command in step 2 instead:
docker compose -f docker-compose.yml -f docker-compose.cloud-storage.yml --profile cloud-storage --profile music up -dThe setup script creates a default tiddl config at <MEDIA_DIR>/config/tidarr/.tiddl/config.toml (default <MEDIA_DIR> is ~/Media). Key settings:
| What | Where |
|---|---|
| Search and download from Tidal manually | http://localhost:8484 |
| Manage music library (add artists, monitor) | http://localhost:8686 |
| Listen via Plex/Plexamp | http://localhost:32400/web |
If your music files have inconsistent metadata (different featuring formats, year suffixes in folder names, missing album artist tags), the cleanup script fixes common issues:
# Preview what would change (dry run, nothing is modified)
python3 scripts/music-cleanup.py
# Apply fixes
python3 scripts/music-cleanup.py --apply
# Custom music directory
python3 scripts/music-cleanup.py --path /Volumes/External/MusicRequires mutagen: pip install mutagen
# Start music services
docker compose --profile music up -d
# Stop only music services (keeps everything else running)
docker compose --profile music stop lidarr tidarr
# Include music in all future docker compose commands
# Add to your shell profile:
export COMPOSE_PROFILES=musicIf cloud storage is enabled, include both compose files and profiles when starting music services:
docker compose -f docker-compose.yml -f docker-compose.cloud-storage.yml --profile cloud-storage --profile music up -dIf you're running out of space on your primary drive, the archive script moves old or watched media to an external drive. Dry-run by default so you can preview what would be moved before committing.
# Preview candidates (nothing gets moved)
bash scripts/archive-media.sh --archive /Volumes/External/Media-Archive
# Archive movies older than 6 months that are 8GB+
bash scripts/archive-media.sh --execute --archive /Volumes/External/Media-Archive --type movies
# Only archive stuff you've already watched (uses Plex watch state)
bash scripts/archive-media.sh --execute --archive /Volumes/External/Media-Archive --only-watched
# Jellyfin users: pass your API key for watched-state filtering
bash scripts/archive-media.sh --execute --archive /Volumes/External/Media-Archive --only-watched --jellyfin-api-key YOUR_KEYProtecting favorites: scripts/setup.sh creates <MEDIA_DIR>/config/archive-exceptions.txt for you (default <MEDIA_DIR> is ~/Media). Add one title per line and anything listed won't be archived regardless of age or size. See configs/archive-exceptions.txt.example for the format.
The script verifies file counts after copying and only deletes the source if the counts match. If verification fails, your original files are untouched.
| Tool | What It Does |
|---|---|
| mac-media-stack-permissions | Audit and fix file permissions across your stack |
| mac-media-stack-backup | Automated backup and restore for configs and databases |
Built by @liamvibecodes
| Back | FazBrowse Home | New Git URL |