All notable changes to this project will be documented in this file.
- dnscrypt-proxy 2.1.14 → 2.1.16
- Tailscale subnet router (optional): new
docker-compose.tailscale.ymland docs/TAILSCALE.md for reaching the whole LAN (Pi-hole,*.landomains, admin UIs, Home Assistant) from anywhere — including hotel WiFi and CGNAT networks where IPv4 inbound is unavailable. Pinned totailscale/tailscale:v1.98.3, runs as a host-network container withNET_ADMIN, advertisesLAN_SUBNETfrom.env. Complementary to Cloudflared, not a replacement
- Tailscale surfaced across main docs (SETUP, README, REFERENCE, MAINTENANCE, ARCHITECTURE): TOC entry, Choose-Your-Setup table row, requirements block, component-table row, compose-files table row, dedicated "+ tailscale" section in SETUP.md, services/compose subsections in REFERENCE.md, independent maintenance commands in MAINTENANCE.md, access-levels diagram block in ARCHITECTURE.md. New users following SETUP.md now discover Tailscale alongside Cloudflared as a parallel setup option
- REMOTE-ACCESS.md: footer hint at the bottom of the Cloudflared walkthrough now links to TAILSCALE.md instead of saying "setup not covered here"
- Pi-hole 2026.04.1 → 2026.05.0
- Cloudflared 2026.3.0 → 2026.5.0
- Traefik v3.6 → v3.7
configure-apps.shhanging at "Configuring qBittorrent...":wait_for_servicepolled withcurland no--max-time, so a single hung connection (qBit accepting TCP but not responding during Gluetun init) silently extended the advertised 60s timeout into many minutes. Now bounded per-call, bounded wall-clock, with a 10s heartbeat showing the last HTTP code so users know it's working. Reported on Reddit- Cloudflared
No file cert.pemon tunnel create: thesudo chown -R 65532:65532 cloudflared/step (required on UGOS/Synology where NAS ACLs override POSIX perms) was buried as a "troubleshooting note" after thetunnel logincommand. Most users hit this on first run before seeing the note. Promoted to a required pre-step. Dropped thechmod 777line — doesn't actually work under ACLs. Reported on Reddit
configure-apps.shGluetun pre-flight check: bail fast with a clear message if Gluetun isn'thealthy. qBittorrent and the *arr services share Gluetun's network namespace, so without it they can't respond — without this check, the user just sees a long hang- TROUBLESHOOTING.md "Seerr: /app/config volume mount was not configured properly": documents the Seerr first-run startup check (stricter than Jellyseerr was), usually caused by a half-initialised
seerr-configvolume from an interrupted start. Fix is wipe + recreate. Reported on Reddit
- Sonarr 4.0.16 → 4.0.17 (patch)
- Radarr 6.0.4 → 6.1.1 (minor)
- Pi-hole 2026.04.0 → 2026.04.1 (Core 6.4.1 → 6.4.2, FTL 6.6 → 6.6.1)
- TROUBLESHOOTING.md: Added "Pi-hole: Gravity Update Fails With Empty Status" — the empty
Status: ()symptom comes from a root-owned file in/etc/pihole/listsCache/(a relic from older Pi-hole images). Includes diagnose andchownfix - Routine
up -dexamples (REFERENCE.md, UPGRADING.md): Note that users who also run utilities (beszel, configarr, duc, diun, deunhealth, uptime-kuma) should add-f docker-compose.utilities.ymlto suppress the "orphan containers" warning. Core-only users can ignore. MAINTENANCE.md already has a dedicated "All Stacks" section for the multi-file invocation
- SETUP.md Step 2.1: Added a short "How to edit
.env" note coveringnanofor SSH users and GUI options (NAS web file manager, VS Code Remote-SSH). Beginners were trying to paste.envline snippets straight into the shell because the docs never said which editor to use. Reported on Reddit
- SETUP.md clone block: The Ugreen and Synology sections referenced
$NAS_STACK_DIRinchownbefore.envexisted, causingchown: missing operand. The variable is now set as a shell var at the top of the clone block, so volume2 users change one line and the whole block works. Reported by u/OatStraw on Reddit
- Pi-hole startup failure on multi-volume NAS setups (#16): Introduced
NAS_STACK_DIRenv var so Pi-hole's config bind-mount resolves correctly when the stack lives on a non-default volume (e.g./volume2/docker/arr-stack). Pi-hole'sdnsmasq.dconfig moved underpihole/dnsmasq.d/— see UPGRADING.md for the migration command
- Pi-hole 2026.02.0 → 2026.04.0
- SETUP.md: Added multi-volume NAS note explaining
NAS_STACK_DIRvsMEDIA_ROOTsplit (stack on one volume, media library on another) - README: Clarified LLM attribution; mention Opus 4.7
- dnscrypt-proxy service (#15, from @gncnpk): Encrypts DNS queries between Pi-hole and upstream resolvers. Runs internally on the arr-stack network with no host port exposure. Configure-apps.sh now sets up Pi-hole to use it
- Seerr v3.1.0 → v3.2.0
- Queue-cleanup cron silently failing: Log path moved from
/var/log/(whichmooseadmincan't write to on UGOS) to$NAS_STACK_DIR/logs/. Also added detection forimportBlockedandimportPendingitems (already-imported packs, executable files, quality mismatches) that were accumulating unhandled - Pi-hole config: Removed unsupported
-qflag frompihole-FTL --configand switched frompihole restartdns(which fails undercap_drop: ALL) todocker restart pihole
- dnscrypt-proxy hardening: Pinned image to 2.1.14 (from
:latest), removed unnecessaryNET_ADMIN/NET_RAWcaps (port 5053 is unprivileged), setno-new-privileges: true
- MAINTENANCE.md and HA webhook: Updated queue-cleanup log path references
cap_drop: ALLbreaking non-LSIO services on fresh install: v1.7.6 dropped all Linux capabilities by default, but several non-LSIO services (Pi-hole, etc.) needCHOWN+DAC_OVERRIDEto write to volume directories. Added them back via targetedcap_add. Existing installs were unaffected — this only bit fresh deploys
- Pi-hole DNS in Uptime Kuma: Uptime Kuma now uses Pi-hole as its DNS resolver so
.lanmonitor URLs resolve correctly
- ARCHITECTURE.md: Corrected security docs — x-security services aren't "fully locked down"; volume-writing services need
CHOWN+DAC_OVERRIDE
- Container security hardening: Every container across all four compose files now runs with
cap_drop: ALLandno-new-privileges: true. LSIO images getCHOWN/SETUID/SETGID/DAC_OVERRIDEback for s6-overlay init. Gluetun keepsNET_ADMINfor VPN tunnels. Pi-hole gets targeted caps for the FTL binary. See Container Security
- Weekly queue cleanup script (
scripts/queue-cleanup.sh): Identifies stuck Sonarr/Radarr queue items (stalled torrents, metadata-stuck, failed imports, 0% for 24h+), removes them with blocklist, and triggers fresh searches. Dry-run by default; suggested cron: Thu 2am - Renovate config: Automated Docker image update PRs, grouped by category (LSIO, infrastructure, utilities), scheduled weekly Monday mornings. Auto-merges LSIO patch updates
- Bazarr 1.5.5 → 1.5.6
- Cloudflared 2026.2.0 → 2026.3.0
- Configarr 1.23.0 → 1.24.0
- Pre-commit image cache TTL increased from 1 hour to 24 hours to reduce registry rate-limiting during repeated commits
- Plex setup guide: Rewritten with full YAML example, option to run Plex alongside Jellyfin (not just replace), anchor link (
SETUP.md#plex), and reference to old Plex compose in git history. Clarified that Seerr supports Plex natively
fix-sonarr-folders.shscript: Renames Sonarr series folders via the API so that Sonarr's database stays in sync (renaming folders directly on disk breaks tracking). LLM-generated and human-reviewed — check the script before running- qBittorrent stall timeout: Pauses torrents after 30 minutes of inactivity so Sonarr/Radarr can detect them and automatically search for alternatives
- Pi-hole AAAA DNS fix:
address=/lan/::entry in dnsmasq config returns::for AAAA queries on.landomains instead of NXDOMAIN. Fixes DNS failures in Alpine/musl containers (e.g., Gluetun) that treat AAAA NXDOMAIN as a hard failure
- qbit-scheduler: Removed the cron-based torrent scheduler (paused all torrents overnight). Replaced by qBittorrent's built-in stall timeout (30-min inactivity → pause) which is more targeted — only pauses stalled torrents instead of everything
- Seerr library sync and quality defaults: Documented that Jellyfin libraries must be enabled in Seerr settings and synced, otherwise movies/shows stay stuck at "Requested". Default quality profiles set to
UHD Bluray + WEB(Radarr) andUltra-HD(Sonarr) - qBittorrent auth subnet whitelist: Documented local network whitelist (
172.20.0.0/24, 10.10.0.0/24, 127.0.0.0/8) to prevent IP bans from Sonarr/Radarr reconnections and API scripts after container restarts
- UPGRADING.md: v1.7.3 migration steps for Seerr library sync, quality profile defaults, and qBit auth whitelist
- APP-CONFIG docs: Seerr quality profiles and library sync steps added to both script-assisted and manual guides
- APP-CONFIG-ADVANCED.md: qBittorrent auth bypass section with subnet whitelist instructions
- SETUP.md: Clarified script-assisted vs manual setup trade-offs, security review note for configure-apps.sh
- Container renamed:
jellyseerr→seerr: Container name, service name, and Docker volume all renamed fromjellyseerr/jellyseerr-configtoseerr/seerr-config. Completes the rebrand started in v1.6.4. Existing users must migrate the volume — see UPGRADING.md - Parallel domain checks in pre-commit hook:
.lanand external domain lookups now run concurrently instead of sequentially — reduces check time from ~28s to <1s
- Uptime Kuma monitor URL: Updated from
http://jellyseerr:5055tohttp://seerr:5055 - Missing
sudoin UGOS setup:mkdirandchowncommands for media directories now usesudo, matching the Linux Server section. Fixes "not writable by user" errors for non-root NAS users (fixes #11)
- App configuration split into 3 focused guides: Script-Assisted (~5 min), Manual (~30 min), and Advanced (optional tuning). Clearer step-by-step flow with strict separation of access setup vs. configuration
- SETUP.md improvements: Slimmed Step 4 handoff, added SABnzbd/Bazarr to Stack Overview, clearer "Core Complete" section with service URLs and Quick Reference link
- VPN leak check script: New
scripts/check-vpn.shcompares Gluetun's exit IP against the NAS LAN IP and exits non-zero on leak. Suitable for cron monitoring - Backup encryption:
scripts/arr-backup.sh --encryptencrypts tarballs with GPG symmetric encryption (AES-256). Opt-in via--encryptflag .envincluded in backups:arr-backup.shnow backs up.env(saved asdot-envwith 600 permissions). Use--encryptto protect secrets at rest
- Network definition duplication:
arr-stacknetwork was fully defined in 3 compose files. Now owned bydocker-compose.arr-stack.ymlonly; traefik and utilities compose files useexternal: true - Bazarr missing healthcheck start_period: Added
start_period: 60sto prevent false unhealthy status during startup - Inconsistent script error handling: All scripts now use
set -euo pipefail(arr-backup.sh,check-network.sh) - Consolidated backup scripts: Merged
backup-volumes.shintoarr-backup.sh— one script for all backups (volumes,.env, encryption, USB discovery, HA webhooks)
--verbosemode for configure-apps.sh: Prints curl response bodies on API failures for easier debugging--helpflag for configure-apps.sh: Shows usage and confirms idempotency- Shared
qbit_auth()helper: Extracted qBittorrent authentication intoscripts/lib/configure-helpers.sh, reducing duplication withconfigure-apps.sh - VPN connectivity E2E test: Verifies VPN-tunneled services are reachable (Gluetun healthy)
- Maintenance guide (
docs/MAINTENANCE.md): Multi-compose command reference, VPN verification, health check guidance - Restore guide (
docs/RESTORE.md): Step-by-step restore procedures for volume backups and arr-backup tarballs
- SETUP.md: Pi-hole static IP warning added to Step 2.5
- APP-CONFIG.md: Idempotency note for configure-apps.sh (safe to re-run)
- Hardlinks and instant moves: All download services (qBittorrent, SABnzbd, Sonarr, Radarr) now share a single
/datavolume mount instead of separate/downloads,/tv,/moviesmounts. This enables hardlinks — imports are instant and use zero extra disk space. Follows TRaSH Guides hardlink recommendations - TRaSH naming schemes: Radarr and Sonarr now use TRaSH-recommended file naming with quality, codec, HDR, and release group info. Existing files mass-renamed on upgrade
- Separated download directories: Torrents and Usenet downloads now go to separate directories (
torrents/{tv,movies}andusenet/{incomplete,complete/{tv,movies}}) instead of a flatdownloads/folder - SABnzbd hardening: Sorting disabled, propagation delay, SFV checking, deobfuscation — follows TRaSH SABnzbd recommendations
- qBittorrent tuning: UPnP disabled, uTP rate limiting, LAN peer limiting, encryption mode — follows TRaSH qBittorrent recommendations
- NFO metadata for Radarr and Sonarr: Recommended setup step — Radarr and Sonarr now write
.nfofiles containing correct TMDB/IMDB/TVDB IDs alongside each media file. Jellyfin reads these instead of guessing from filenames, preventing metadata mismatches that cause Seerr to show "Requested" when files are already downloaded. Especially important for foreign-language films and titles shared by multiple movies - Configarr: New utility container that syncs TRaSH Guides quality profiles and custom formats to Sonarr/Radarr. One-shot job (runs once and exits) — run manually with
docker compose -f docker-compose.utilities.yml run --rm configarr. Includes dry-run mode - AI disclosure: README now discloses that this codebase was generated with Claude Code, with human oversight throughout
- Playwright E2E tests: Automated UI screenshot tests for all 9 services plus API assertions for root folders and media libraries. Run with
npm run test:e2e
- Volume mounts restructured: qBittorrent, SABnzbd, Sonarr, Radarr now mount
${MEDIA_ROOT}:/data(single mount). Jellyfin and Bazarr mount specific subdirectories under/data/. This is a breaking change for existing users — see UPGRADING.md - Download categories renamed: qBittorrent categories changed from
sonarr/radarrtotv/moviesto match directory structure and SABnzbd categories - Jellyfin library paths: Changed from
/media/moviesand/media/tvto/data/media/moviesand/data/media/tv— follows TRaSH recommendedmedia/subdirectory structure - Repo renamed:
arr-stack-ugreennas→ultimate-arr-stack. GitHub auto-redirects old URLs
- APP-CONFIG.md: Complete rewrite of paths, categories, and folder setup for all services
- APP-CONFIG.md: TRaSH naming scheme configuration added to Sonarr and Radarr setup steps
- APP-CONFIG.md: SABnzbd hardening section added with TRaSH recommendations
- APP-CONFIG.md: qBittorrent tuning section added with TRaSH recommendations
- APP-CONFIG.md: Bazarr subtitle sync (
ffsubsync) added as setup step - APP-CONFIG.md: NFO metadata added as step 4 in both Sonarr and Radarr setup
- SETUP.md: Updated directory structure diagram and mkdir commands for hardlink-compatible layout
- UPGRADING.md: Full v1.6.5→v1.7 migration guide with step-by-step root folder migration, category changes, naming config
- TROUBLESHOOTING.md: Updated SABnzbd paths from
downloads/tousenet/ - TROUBLESHOOTING.md: SSH post-quantum key exchange warning fix for macOS OpenSSH 10.x connecting to UGOS NAS
- UTILITIES.md: Configarr setup and usage guide
- REFERENCE.md: Configarr added to service tables
- CONTRIBUTING.md: E2E tests added to pre-release checklist
- FlareSolverr Cloudflare bypass fails (fixes #5): FlareSolverr was running outside the VPN, so it solved Cloudflare challenges from a different IP than Prowlarr's VPN exit IP — Cloudflare rejected the mismatched cookies. FlareSolverr now runs behind Gluetun (
network_mode: "service:gluetun"), sharing the same tunnel and IP as Prowlarr. This also fixes ISP DNS blocking of torrent domains, since FlareSolverr inherits Gluetun's Pi-hole DNS automatically
- Prowlarr FlareSolverr host:
http://172.20.0.10:8191→http://localhost:8191(same network namespace) - Uptime Kuma FlareSolverr monitor:
http://172.20.0.10:8191→http://172.20.0.3:8191(via Gluetun) - Removed
flaresolverr.lanDNS entry (no longer has its own IP) - Removed FlareSolverr static IP
172.20.0.10from network tables (docs, config templates)
- Jellyseerr → Seerr: Migrated from
fallenbagel/jellyseerr:2.7toghcr.io/seerr-team/seerr:v3.0.1(the official rebrand). Runs as non-root (UID 1000), requiresinit: true. Container and volume renamed toseerr/seerr-configin v1.7.2 - jellyseerr.lan → seerr.lan: Primary domain renamed. Permanent 301 redirects from
jellyseerr.lanandjellyseer.lantoseerr.lan(Traefik + external). Existing bookmarks continue to work - FlareSolverr healthcheck: Reduced
start_periodfrom 2m to 60s to exitstartingstate faster
- Plex compose file deleted:
docker-compose.plex-arr-stack.ymlandvpn-services-plex.yml.exampleremoved. Plex users should modify the Jellyfin compose directly — see "Prefer Plex?" section in SETUP.md - Compose drift pre-commit check: Removed (was comparing Jellyfin/Plex files for consistency — no longer needed)
- All docs updated: Jellyseerr → Seerr throughout (SETUP, REFERENCE, ARCHITECTURE, LEGAL, README, instructions)
- Pi-hole DNS: clarified that
pihole reloaddnsdoes NOT pick up bind-mount file changes — must usedocker restart pihole - CONTRIBUTING.md: updated scripts structure, pre-commit hooks table, project structure
- Bazarr, Overseerr, Plex, DIUN images fail to pull (fixes #4): Five image tags were wrong —
bazarr:v1.5.5(should be1.5.5),overseerr:1.33(should be1.35.0),plex:1.41(should be1.43.0),diun:v4.31.0(should be4.31.0). Containers kept running from cached:latestpulls so the bad tags were never caught - WireGuard secret detection test was always passing: Test fixture path matched the
tests/fixtures/*skip rule incheck_secrets, so the test never actually ran the detection logic
- Image tag registry validation test: New BATS test checks every
image:tagin all compose files actually exists on its registry (Docker Hub, GHCR, LSCR) via HTTP API. No Docker CLI or pull required — would have caught all five bad tags instantly - Pre-release checklist:
CONTRIBUTING.mdnow documents mandatory steps before any release — run BATS tests, fulldocker compose pullon the NAS, bring stack up and verify
- Swappiness tuning: Set
vm.swappiness=10via root@rebootcrontab. UGOS default (60) aggressively swaps out app pages even with plenty of free RAM. Reduces unnecessary zram overhead and keeps container memory resident - Backup to USB:
arr-backup.sh --usb DIR_NAMEdynamically finds USB devices under/mnt/@usb/sd*/(device letters change on reboot). Includes 7-day rotation - Backup failure notifications: Home Assistant webhook alerts when backup fails, with step-level error reporting (
HA_WEBHOOK_URLenv var)
- RAM upgrade 5-day analysis: Full Beszel comparison (97 pre vs 289 post samples) showing 91% disk read reduction, 40% CPU drop, zero disk swap. Container memory steady-state measurements. NVMe-for-Docker assessment reconfirmed as not worth it
- Swappiness troubleshooting: New section in TROUBLESHOOTING.md for diagnosing and fixing unnecessary swap with free RAM available
- Gluetun fails to start after power cut: On simultaneous restart, containers from other compose projects (e.g. therapy-stack Baserow) could grab Gluetun's reserved IP (172.20.0.3) dynamically, causing "Address already in use" and taking down all VPN-dependent services. Fixed by adding
ip_range: 172.20.0.128/25to the arr-stack network definition indocker-compose.traefik.yml, confining dynamic allocations to 128-255 and protecting static IPs - RAID5 tuning lost on reboot: UGOS firmware updates silently overwrite
/etc/rc.local, wiping custom tuning. Moved RAID5 streaming tuning (read-ahead + stripe cache) from rc.local to root crontab@rebootwhich survives UGOS updates
- arr-stack network ownership: Network definition moved from manual
docker network create(referenced asexternal: true) todocker-compose.traefik.ymlwith full IPAM config —ip_rangeis now version-controlled and applied automatically onup -d
- rc.local warning: All docs updated to recommend crontab
@rebootinstead of/etc/rc.localfor UGOS
- Pi-hole fails to start on every reboot: Pi-hole binds to
${NAS_IP}:53, but if the IP comes from DHCP, Docker starts before the address is assigned — causing a silent exit 128 that Docker never retries. Removed Pi-hole from unnecessaryvpn-netnetwork (was causing a secondary race condition). Documented the root cause and fix (static IP on NAS) across.env.example, SETUP.md, and TROUBLESHOOTING.md - Jellyfin 4K playback stuttering: UGOS default RAID5 read-ahead (384 KB) is too small for streaming large files, causing disk utilization to hit 96% and playback to freeze every 2-3 minutes. Increased read-ahead to 4096 KB and stripe cache to 4096 pages. Disk utilization during 4K playback drops from ~96% to ~10%
- Static IP requirement for Pi-hole:
.env.examplenow explains whyNAS_IPmust be a static IP (not DHCP reservation), how to check, and how to fix - Pi-hole reboot troubleshooting guide: Full diagnose/fix section in TROUBLESHOOTING.md with copy-paste commands
- SETUP.md Pi-hole prerequisite: Static IP callout with link to troubleshooting
- Clarified difference between static IP and DHCP reservation across all docs
- RAID5 streaming tuning: SETUP.md Jellyfin section + full diagnose/fix in TROUBLESHOOTING.md with iostat commands and permanent fix via crontab
- DIUN (Docker Image Update Notifier): New utility container that monitors all running containers and sends webhook notifications to Home Assistant when newer image versions are available on registries. Daily check at 6am (configurable via
DIUN_SCHEDULE) - Pre-commit check 11 — Image version staleness: Queries Docker Hub and GHCR to warn when pinned image versions have newer releases. Non-blocking (warning only), with 1-hour result cache for fast commits
- BATS test framework: 22 automated tests across 5 test suites validating compose structure, security policies, pre-commit checks, port/IP conflicts, and env var coverage. Run with
./tests/run-tests.sh
- Cross-file port/IP conflict detection: Pre-commit hook now detects duplicate ports and IPs across different compose files (not just within each file)
- New secret detection patterns: OpenVPN credentials, Bearer/auth tokens, and SSH/generic passwords now caught by pre-commit secret scanner
- Cron injection prevention: qbit-scheduler validates
PAUSE_HOUR/RESUME_HOURare 0-23 before interpolating into crontab - Traefik logging: Added missing
json-filelog driver with rotation (10m/3 files) — the only service that was missing it
- Home Assistant integration guide for DIUN webhook setup
- DIUN added to reference tables (network, utilities compose)
- SABnzbd troubleshooting guide: Step-by-step fix for stuck unpack loops (obfuscated filenames + par2 files, no RARs). Covers diagnosis,
_UNPACK_*cleanup, postproc queue reset, and Radarr re-import. - Beszel webhook setup: How to configure Beszel alert webhooks for Discord/ntfy notifications, plus UGOS antivirus scanning tip
- Fix Gluetun VPN check command: Changed
grep -i "connected"togrep "Public IP address" | tail -1— Gluetun (WireGuard) never logs "connected", it logs the public IP on successful connection - Fix VPN IP check command: Replaced
ifconfig.mewithipinfo.io/ip— ifconfig.me now returns HTML to wget instead of plain text
- Removed unused traefik labels: Services had
traefik.enable=truelabels that did nothing (routing uses file config, not Docker labels). Cleaned up to avoid confusion for users adding their own services.
- Using tunnel for other services: Added guide for routing additional subdomains through the same Cloudflare Tunnel (e.g., Home Assistant, blogs). Explains ingress rule ordering and DNS setup.
- Kodi for Fire TV: Added guide for using Kodi with Jellyfin add-on when experiencing passthrough issues (Dolby Vision, TrueHD Atmos). Includes Fire TV sideload instructions and fix for "Unable to connect" error caused by Docker networking.
- WireGuard VPN server (wg-easy): Removed from stack. WireGuard requires port forwarding, which doesn't work for users behind CGNAT (common with many ISPs). Cloudflare Tunnel covers the main use case of remote access to Jellyfin/Jellyseerr.
- Added Tailscale note for users who need full remote network access (admin UIs,
.landomains from outside home) - Clarified remote access is for watching/requesting (Jellyfin + Jellyseerr), not full network access
WireGuard as VPN client protocol (for Gluetun connecting to your VPN provider) is unchanged. This only removes the VPN server for incoming connections.
- Intel Quick Sync hardware transcoding: GPU-accelerated video transcoding for Jellyfin on Intel NAS (Ugreen DXP4800+, etc.). Reduces CPU usage from ~80% to ~20% when transcoding.
- Hardware transcoding setup guide with Transcoding and Trickplay screenshots
- Verification steps to confirm hardware acceleration is working
- Fork recommended over clone in setup guide
- Cloudflared healthcheck: Was always failing (missing tunnel ID), causing deunhealth to restart cloudflared every ~2.5 minutes. Now uses
cloudflared tunnel info nas-tunnel - DNS config git tracking:
pihole/02-local-dns.confwas tracked despite being in.gitignore, causinggit pullto overwrite user's local DNS config - DNS resolution conflicts: Stale entries in
pihole.tomlcould conflict with dnsmasq config, causing unpredictable.landomain resolution
- Beszel system monitoring: Lightweight metrics for CPU, RAM, disk, network, and Docker containers (hub + agent with healthchecks)
- DNS duplicate detection: Pre-commit hook (check 9) and standalone script (
./scripts/check-dns-duplicates.sh) to warn if same.landomain defined in both dnsmasq and pihole.toml - Domain accessibility check: Pre-commit hook (check 10) verifies all
.lanand external domains are reachable
- Renamed "Optional extras": Now "Utilities (optional)" for consistency with
docker-compose.utilities.yml
- Beszel setup instructions in SETUP.md
- Clarified
.lanDNS guidance: don't define same domain in both dnsmasq config and Pi-hole web UI - Clarified Docker requirements: NAS users often have Docker preinstalled (UGOS) or one-click install (Synology/QNAP)
- Auto-restart VPN services: When Gluetun reconnects to VPN, dependent services (qBittorrent, Sonarr, Radarr, Prowlarr, SABnzbd) now automatically restart via
deunhealthcontainer
- VPN reconnection previously left services with stale network attachments, causing "Unable to connect" errors until manual restart
- Removed env var fallbacks: Compose files no longer have default values for required variables. Missing variables now fail fast with clear errors instead of silently using defaults
- Clarified which variables are required vs optional in
.env.example
- Network renamed:
traefik-proxy→arr-stack(clearer - network is used by all services, not just Traefik) - qbit-scheduler configurable: Pause/resume hours now set via
QBIT_PAUSE_HOURandQBIT_RESUME_HOURenv vars
- Setup levels clarified: Core / + local DNS / + remote access terminology consistent throughout
- Step 4 reordered: Jellyfin first (user-facing), then backend services in dependency order
- Removed redundant tables: Service connection table now only in REFERENCE.md
See UPGRADING.md for network rename instructions.
- Network subnet: Changed from
192.168.100.0/24to172.20.0.0/24to avoid conflicts with common LAN ranges - Jellyfin discovery ports: Added 7359/udp (client discovery) and 1900/udp (DLNA) for better app auto-detection
- duc.lan support: duc now on arr-stack network (172.20.0.14) with .lan domain access
- Prerequisites consolidated: Simplified to just Hardware and Software/Services lists
- SETUP.md restructured: External Access moved to end; steps renumbered for clearer flow
- Cloudflare Tunnel expanded: No longer in collapsed section
See UPGRADING.md for network migration instructions.
- Restructured docs: Split into focused files (SETUP.md, REFERENCE.md, UPGRADING.md, HOME-ASSISTANT.md)
- Setup screenshots: Step-by-step Surfshark WireGuard and Cloudflare Tunnel setup with images
- Home Assistant integration: Notification setup guide for download events
- VPN provider agnostic: Documentation now generic; supports 30+ Gluetun providers (was Surfshark-specific)
- docker-compose.utilities.yml: Separate compose file for optional services:
- deunhealth: Auto-restart services when VPN recovers
- Uptime Kuma: Service monitoring dashboard
- duc: Disk usage analyzer with treemap UI
- qbit-scheduler: Pauses torrents overnight (20:00-06:00) for disk spin-down
- VueTorrent: Mobile-friendly alternative UI for qBittorrent
- Pre-commit hooks: Automated validation for secrets, env vars, YAML syntax, port/IP conflicts
- Cloudflare Tunnel: Now uses local config file instead of Cloudflare web dashboard - simpler setup, version controlled, supports wildcard routing with just 2 DNS records
- Security hardening: Admin services now local-only; only Jellyfin, Jellyseerr, WireGuard exposed via Cloudflare Tunnel
- Deployment workflow: Git-based deployment (commit/push locally, git pull on NAS)
- Pi-hole web UI: Now on port 8081
- qBittorrent API v5.0+ compatibility (
stop/startinstead ofpause/resume) - Pre-commit drift check service counting
- Initial public release
- Complete media automation stack with Jellyfin, Sonarr, Radarr, Prowlarr, Bazarr
- VPN-protected downloads via Gluetun
- Remote access via Cloudflare Tunnel
- WireGuard VPN server for secure home network access
- Pi-hole for DNS and ad-blocking