Compare commits

...

22 Commits

Author SHA1 Message Date
aki
79a7a0d4c6 fix(update-authelia): Use proper yq syntax since walk() does not exist like in jq
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-27 00:05:43 +08:00
aki
93e709841b refactor(update-setup): Simplify Authelia configuration updates and improve placeholder replacements
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 21:09:07 +08:00
aki
78338661b0 docs(README): Update README
- Add detailed setup instructions
- Clarify configuration variables
- Update sections on architecture, features, prerequisites, and troubleshooting to reflect recent changes.
2025-04-26 17:56:02 +08:00
aki
9f8ff41bf4 feat(update-setup)!: Replace update-config.sh with a new script to manage setup 2025-04-26 17:54:36 +08:00
aki
67ff6d585c feat!: Use Authelia authentication to protect endpoints
- Uses Authelia as an authentication middleware and access control, with sensible policy
- Redis as Authelia's backend for session data
- Add https-proto middleware
- Add/update example files
2025-04-26 17:53:18 +08:00
aki
cf78372b71 feat: Refactor Tailscale & Traefik Integration
Some checks failed
/ validate-docker-compose (push) Has been cancelled
- Integrate Traefik directly with Tailscale network using `network_mode: service:tailscale`.
- Remove direct port mappings for Traefik (80, 443).
- Configure Tailscale container to use `tailscale serve` (Tailnet only) or `tailscale funnel` (public HTTPS) based on `ENABLE_FUNNEL_HTTPS` env var.
- Update Traefik routing rules (`Host()`) to use `${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}` for all services.
- Update Homepage path to `/home` and adjust its Traefik rule.
- Remove Homepage basic authentication variables (`HOMEPAGE_AUTH_USER`, `HOMEPAGE_AUTH_HASH`) from `.env.example` and `README.md`.
- Update `README.md` to reflect new access methods, hostname configuration, and removal of basic auth.
- Add `extra_hosts` to Tailscale service for `host.docker.internal`.
2025-04-25 14:15:44 +08:00
aki
8c5cdb111d fix(env): Update Tailscale settings and add homepage configuration options
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 13:08:06 +08:00
aki
42ee02d8e7 fix(homepage): Update allowed hosts and add basic auth middleware configuration 2025-04-25 13:07:53 +08:00
aki
83016e268c fix(README): Update HOSTNAME and add TAILSCALE_TAILNET_DOMAIN for Tailscale configuration
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 12:56:01 +08:00
aki
3539cc3d3e fix(docker-compose): Simplify Traefik routing rules by removing hostname conditions 2025-04-25 12:54:58 +08:00
aki
4db5c27755 fix(traefik): Move extra_hosts over to tailscale
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 11:22:58 +08:00
aki
8051ea33e3 fix(tailscale): Use localhost and network_mode for Funnel/Serve
Some checks failed
/ validate-docker-compose (push) Has been cancelled
Tailscale Funnel/Serve requires proxying to localhost or 127.0.0.1.
This commit updates the target URL to http://localhost:80 and sets
Traefik's network_mode to service:tailscale to allow tailscale to
correctly forward traffic to the traefik container.
2025-04-25 11:09:40 +08:00
aki
cc51dd386c docs(README): Refactor and simplify documentation
Some checks failed
/ validate-docker-compose (push) Has been cancelled
- Restructure README with standard sections (Prerequisites, Quick Start, Config, Access, Optional, Troubleshooting).
- Rewrite introduction for clarity on project goals.
- Focus Quick Start on core Docker Compose setup.
- Detail .env variables with clear groupings.
- Clarify Tailscale's role for remote access and HTTPS.
- Remove outdated/overly specific setup details for individual services, VPN, Synology, NFS, etc.
- Add SELinux troubleshooting specific to Docker socket access.
2025-04-25 10:52:52 +08:00
aki
3ce7262e38 fix(docker-compose): use Docker backend instead of Podman 2025-04-25 10:43:33 +08:00
aki
a0fb667642 fix(tailscale): Incorrect usage of commands
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 10:37:19 +08:00
aki
bccf28da84 fix(docker-compose): update Docker socket volume mounts to use Podman socket
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 03:36:09 +08:00
aki
3f05975393 docs(setup): add Podman SELinux note and enhance Tailscale access instructions in README
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 03:29:00 +08:00
aki
44cdc60ab3 fix(docker-compose): remove SELinux context from Docker socket volume mounts
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 03:24:13 +08:00
aki
dc54b33281 fix(volumes): Edge case for SELinux contexts
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 03:15:01 +08:00
aki
6c1e6b5700 feat(tailscale): update Tailscale configuration to enable Funnel and remove Traefik targets
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 03:04:23 +08:00
aki
7162827ab3 refactor: Remove Cloudflare and Let's Encrypt configurations from environment variables and Traefik service
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 02:39:28 +08:00
aki
fba2c07e2c feat(vpn,sidecar)!: Remove VPN configuration and add Tailscale service with proxy settings in Docker Compose
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 02:19:59 +08:00
8 changed files with 2227 additions and 671 deletions

View File

@@ -1,32 +1,81 @@
# --- Docker Compose Settings ---
# Comma-separated list of optional service profiles to enable (e.g., lidarr,sabnzbd,adguardhome)
COMPOSE_PROFILES=
# Path separator for COMPOSE_FILE (use ';' for Windows)
COMPOSE_PATH_SEPARATOR=:
# Colon-separated list of compose files to use. Allows extending the base configuration.
COMPOSE_FILE=docker-compose.yml:adguardhome/docker-compose.yml:tandoor/docker-compose.yml:joplin/docker-compose.yml:homeassistant/docker-compose.yml:immich/docker-compose.yml
# --- Core System Settings ---
# Linux User ID. Find yours with `id -u`. Crucial for file permissions.
USER_ID=1000
# Linux Group ID. Find yours with `id -g`. Crucial for file permissions.
GROUP_ID=1000
# Your local timezone (e.g., America/New_York, Europe/London, Asia/Manila). See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TIMEZONE="America/New_York"
# --- Host Paths ---
# Base directory on host for storing service configuration files. '.' stores them in subdirectories within the project folder.
CONFIG_ROOT="."
# Main directory on host containing media libraries (movies, TV, music, books).
DATA_ROOT="/mnt/data"
# Directory on host for download clients (qBittorrent/SABnzbd). Should be on the same filesystem as DATA_ROOT for hardlinks.
DOWNLOAD_ROOT="/mnt/data/torrents"
# Upload location for Immich (if profile enabled)
IMMICH_UPLOAD_LOCATION="/mnt/data/photos"
PIA_LOCATION=ca
PIA_USER=
PIA_PASS=
PIA_LOCAL_NETWORK="192.168.0.0/16"
HOSTNAME=localhost
HOMEASSISTANT_HOSTNAME=
IMMICH_HOSTNAME=
ADGUARD_HOSTNAME=
ADGUARD_USERNAME=
ADGUARD_PASSWORD=
# --- Tailscale Settings ---
# Required. Auth key from Tailscale Admin Console (Settings > Keys). Use a reusable or ephemeral key.
TAILSCALE_AUTHKEY=
# Desired hostname for this NAS within your Tailscale network.
TAILSCALE_HOSTNAME=tailscale-nas
# Required. Your Tailnet domain (e.g., your-tailnet-name.ts.net).
TAILSCALE_TAILNET_DOMAIN=your-tailnet.ts.net
# Optional tags to apply to the Tailscale node (e.g., tag:nas).
TAILSCALE_TAGS=tag:nas
# Enable Tailscale Funnel (public access) for HTTPS? Set to 'true' or 'false'. 'false' uses Serve (Tailnet only, recommended).
ENABLE_FUNNEL_HTTPS=false
# --- Primary Application Hostname ---
# Primary hostname used by Traefik/Authelia. Derived from Tailscale settings by default.
# Renamed from HOSTNAME to avoid collision with host system environment variable.
APP_HOSTNAME=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
# --- Application Credentials ---
# qBittorrent Web UI Credentials (change default!)
QBITTORRENT_USERNAME=admin
QBITTORRENT_PASSWORD=adminadmin
DNS_CHALLENGE=true
DNS_CHALLENGE_PROVIDER=cloudflare
LETS_ENCRYPT_CA_SERVER="https://acme-v02.api.letsencrypt.org/directory"
LETS_ENCRYPT_EMAIL=
CLOUDFLARE_EMAIL=
CLOUDFLARE_DNS_API_TOKEN=
CLOUDFLARE_ZONE_API_TOKEN=
# Calibre-Web Credentials (if profile enabled)
CALIBRE_USERNAME=admin
CALIBRE_PASSWORD=admin123
# Immich Database Password (if profile enabled)
IMMICH_DB_PASSWORD=postgres
# --- Homepage Settings ---
HOMEPAGE_VAR_TITLE="Docker-Compose NAS"
HOMEPAGE_VAR_SEARCH_PROVIDER=google
HOMEPAGE_VAR_HEADER_STYLE=boxed
# Weather Widget (Optional)
HOMEPAGE_VAR_WEATHER_CITY=
HOMEPAGE_VAR_WEATHER_LAT=
HOMEPAGE_VAR_WEATHER_LONG=
HOMEPAGE_VAR_WEATHER_UNIT=metric
# --- Authelia Settings ---
# Generate strong random secrets for these using tools like `openssl rand -hex 32`
# These are all REQUIRED for Authelia to function properly
AUTHELIA_JWT_SECRET= # Secret used for JWT tokens (password reset, etc)
AUTHELIA_SESSION_SECRET= # Secret for encrypting session cookies
AUTHELIA_STORAGE_ENCRYPTION_KEY= # Secret for encrypting stored data
AUTHELIA_REDIS_PASSWORD= # Password for Redis session storage
# Note: The following variables are no longer needed with Authelia 4.38+ and the updated configuration
# They are preserved for backward compatibility but will be automatically mapped to the new structure
# AUTHELIA_SESSION_DOMAIN=${APP_HOSTNAME}
# AUTHELIA_DEFAULT_REDIRECT_URL=https://${APP_HOSTNAME}/home
# --- API Keys & Integration Tokens (Optional - Mainly for Homepage Widgets) ---
# Find API keys within each application's settings (usually Settings > General or Security)
SONARR_API_KEY=
RADARR_API_KEY=
LIDARR_API_KEY=
@@ -37,16 +86,12 @@ JELLYSEERR_API_KEY=
SABNZBD_API_KEY=
IMMICH_API_KEY=
HOMEASSISTANT_ACCESS_TOKEN=
HOMEPAGE_VAR_TITLE="Docker-Compose NAS"
HOMEPAGE_VAR_SEARCH_PROVIDER=google
HOMEPAGE_VAR_HEADER_STYLE=boxed
HOMEPAGE_VAR_WEATHER_CITY=
HOMEPAGE_VAR_WEATHER_LAT=
HOMEPAGE_VAR_WEATHER_LONG=
HOMEPAGE_VAR_WEATHER_UNIT=metric
IMMICH_DB_PASSWORD=postgres
CALIBRE_USERNAME=admin
CALIBRE_PASSWORD=admin123
# AdGuard Home Credentials (if profile enabled)
ADGUARD_USERNAME=
ADGUARD_PASSWORD=
# --- Optional Service Settings ---
# Decluttarr Settings (if profile enabled)
DECLUTTARR_TEST_RUN=True
DECLUTTARR_REMOVE_TIMER=60
DECLUTTARR_REMOVE_FAILED=True
@@ -54,3 +99,9 @@ DECLUTTARR_REMOVE_FAILED_IMPORTS=True
DECLUTTARR_REMOVE_METADATA_MISSING=True
DECLUTTARR_REMOVE_MISSING_FILES=True
DECLUTTARR_REMOVE_ORPHANS=True
# --- Other Hostnames (Optional Services) ---
# Set these if you need specific hostnames for these services (e.g., for Home Assistant integrations)
HOMEASSISTANT_HOSTNAME=
IMMICH_HOSTNAME=
ADGUARD_HOSTNAME=

3
.gitignore vendored
View File

@@ -1,6 +1,9 @@
*.env
*.bak
.idea
docker-compose.override.yml
/authelia/*.yml
!/authelia/*.example.yml
/homepage/logs
/homepage/*.yaml
/homepage/*.css

900
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
# Authelia Configuration File v4.38+
# Documentation: https://www.authelia.com/configuration/
# Server settings
server:
address: 'tcp://0.0.0.0:9091'
# Logging configuration
log:
level: info
format: text
# Session configuration for v4.38+
session:
name: authelia_session
secret: ${AUTHELIA_SESSION_SECRET}
expiration: 1h
inactivity: 5m
redis:
host: redis
port: 6379
password: ${AUTHELIA_SESSION_REDIS_PASSWORD}
database_index: 0
cookies:
# Using your specific Tailscale domain (e.g. example.ts.net) not just ts.net
- domain: 'your-tailnet.ts.net'
authelia_url: 'https://tailscale-nas.your-tailnet.ts.net'
default_redirection_url: 'https://tailscale-nas.your-tailnet.ts.net/home'
same_site: lax
# Regulation (brute force protection)
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
# Storage (for user preferences, etc. - encrypted using storage key)
storage:
encryption_key: ${AUTHELIA_STORAGE_ENCRYPTION_KEY}
local:
path: /config/db.sqlite3
# Authentication backend (using file-based user database)
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 1
memory: 1024
parallelism: 8
salt_length: 16
key_length: 32
# Access control rules
access_control:
default_policy: deny # Deny access by default
rules:
# Rules are processed in order. First match wins.
# It's recommended to put more specific rules first.
# 1. Bypass rules (No authentication required)
# Allow access to Authelia's own endpoints
- domain: '*.your-tailnet.ts.net'
path_regex: '^/auth.*' # Match /auth and anything after it
policy: bypass
# Allow access to the root path (will be redirected by Traefik later)
- domain: '*.your-tailnet.ts.net'
path: '/'
policy: bypass
# Allow access to API endpoints (as requested, review security implications)
- domain: '*.your-tailnet.ts.net'
path_regex: '^/api.*' # Match /api and anything after it
policy: bypass
# 2. One-Factor Authentication Rules (Requires login)
# Add rules for each service you want to protect.
# The domain should match your Tailscale domain.
# The path should match the Traefik PathPrefix for the service.
- domain: '*.your-tailnet.ts.net'
path_regex: '^/sonarr.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/radarr.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/lidarr.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/bazarr.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/qbittorrent.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/sabnzbd.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/calibre.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/home.*' # Protect the homepage
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/jellyseerr.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/prowlarr.*'
policy: one_factor
- domain: '*.your-tailnet.ts.net'
path_regex: '^/flaresolverr.*'
policy: one_factor
# Add other services here following the pattern:
# - domain: '*.your-tailnet.ts.net'
# path_regex: '^/<service_path>.*'
# policy: one_factor
# 3. Default rule for the domain (optional, if you want a catch-all)
# This rule will apply if no path-specific rule above matches.
# You might want to deny or require one_factor for unmatched paths.
# Example: Deny any other path on the domain
# - domain: '*.your-tailnet.ts.net'
# policy: deny
# Example: Require login for any other path
# - domain: '*.your-tailnet.ts.net'
# policy: one_factor
# Notifier configuration
notifier:
filesystem:
filename: /config/notification.txt
# Identity Validation (includes JWT secret for password reset)
identity_validation:
reset_password:
jwt_secret: ${AUTHELIA_JWT_SECRET}
# Identity Providers
identity_providers:
oidc: null

View File

@@ -0,0 +1,39 @@
# Authelia User Database
# Documentation: https://www.authelia.com/configuration/security/authentication/file/
# To add users:
# 1. Generate a password hash:
# docker run authelia/authelia:latest authelia hash-password 'your_strong_password'
# 2. Add the user entry below.
#
# To approve registered users (if registration is enabled in configuration.yml):
# 1. New users will appear here, possibly commented out or with 'disabled: true'.
# 2. Uncomment the user or set 'disabled: false' to grant access.
users:
# First user is typically considered the admin in access rules
admin:
displayname: "Admin User"
# Replace this hash with one generated for your desired password!
password: "$argon2id$v=19$m=102400,t=1,p=8$PBf/L9l3s7LwN6jX/B3tVg$9+q3kL8VAbpWj9Gv9Z6uA5bA4zT1fB2fH3aD5c6b7e8" # Example hash for 'password'
email: admin@example.com
groups:
- admins
- users
# Example of a regular user
# user1:
# displayname: "Regular User"
# password: "..." # Generate hash
# email: user1@example.com
# groups:
# - users
# Example of a registered user waiting for approval (if registration enabled)
# newuser:
# disabled: true
# displayname: "New User"
# password: "..." # Hash generated during registration
# email: newuser@example.com
# groups:
# - users

View File

@@ -3,42 +3,67 @@ services:
image: ghcr.io/traefik/traefik:3.3
container_name: traefik
restart: always
environment:
- CLOUDFLARE_EMAIL=${CLOUDFLARE_EMAIL}
- CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}
- CLOUDFLARE_ZONE_API_TOKEN=${CLOUDFLARE_ZONE_API_TOKEN}
- LETS_ENCRYPT_EMAIL=${LETS_ENCRYPT_EMAIL}
command:
- --ping=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.web-secure.address=:443
- --entrypoints.web.http.redirections.entryPoint.to=web-secure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.web.http.redirections.entrypoint.permanent=true
- --experimental.plugins.rewrite-body.modulename=github.com/packruler/rewrite-body
- --experimental.plugins.rewrite-body.version=v1.2.0
- --experimental.plugins.rewriteHeaders.modulename=github.com/XciD/traefik-plugin-rewrite-headers
- --experimental.plugins.rewriteHeaders.version=v0.0.3
- --certificatesresolvers.myresolver.acme.dnschallenge=${DNS_CHALLENGE:-true}
- --certificatesresolvers.myresolver.acme.dnschallenge.provider=${DNS_CHALLENGE_PROVIDER:-cloudflare}
- --certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53
- --certificatesresolvers.myresolver.acme.caserver=${LETS_ENCRYPT_CA_SERVER:-https://acme-v02.api.letsencrypt.org/directory}
- --certificatesresolvers.myresolver.acme.email=${LETS_ENCRYPT_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
ports:
- "80:80"
- "443:443"
- --providers.docker.network=docker-compose-nas
- --providers.docker.endpoint=unix:///var/run/docker.sock
network_mode: service:tailscale
volumes:
- ${CONFIG_ROOT:-.}/letsencrypt:/letsencrypt
- "/var/run/docker.sock:/var/run/docker.sock:ro"
extra_hosts:
- host.docker.internal:172.17.0.1
- /var/run/docker.sock:/var/run/docker.sock:ro
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
retries: 10
redis:
image: redis:alpine
container_name: redis
restart: always
environment:
- REDIS_PASSWORD=${AUTHELIA_REDIS_PASSWORD}
command: ["redis-server", "--requirepass", "${AUTHELIA_REDIS_PASSWORD}"]
volumes:
- ${CONFIG_ROOT:-.}/redis:/data:Z
healthcheck:
test: ["CMD", "redis-cli", "-a", "${AUTHELIA_REDIS_PASSWORD}", "ping"]
interval: 5s
timeout: 3s
retries: 5
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: always
user: ${USER_ID}:${GROUP_ID}
volumes:
- ${CONFIG_ROOT:-.}/authelia:/config:Z
environment:
- AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET}
- AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY}
- AUTHELIA_SESSION_REDIS_PASSWORD=${AUTHELIA_REDIS_PASSWORD}
- AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET=${AUTHELIA_JWT_SECRET}
- TZ=${TIMEZONE}
labels:
- traefik.enable=true
- traefik.http.routers.authelia.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/auth`) # Changed rule
- traefik.http.routers.authelia.entrypoints=web
# - traefik.http.routers.authelia.priority=100 # Removed priority
- traefik.http.services.authelia.loadbalancer.server.port=9091
- traefik.http.middlewares.authelia-auth.forwardAuth.address=http://authelia:9091/api/verify # Simplified forwardAuth address
- traefik.http.routers.authelia.middlewares=https-proto@docker
- traefik.http.middlewares.authelia-auth.forwardAuth.trustForwardHeader=true
- traefik.http.middlewares.authelia-auth.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email
- homepage.group=Security
- homepage.name=Authelia
- homepage.icon=authelia.png
- homepage.href=/auth # Updated href
- homepage.description=Authentication Portal
sonarr:
image: lscr.io/linuxserver/sonarr
container_name: sonarr
@@ -47,8 +72,8 @@ services:
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/sonarr:/config
- ${DATA_ROOT}:/data
- ${CONFIG_ROOT:-.}/sonarr:/config:Z
- ${DATA_ROOT}:/data:Z
restart: always
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:8989/sonarr/ping"]
@@ -56,9 +81,9 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.sonarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/sonarr`))
- traefik.http.routers.sonarr.tls=true
- traefik.http.routers.sonarr.tls.certresolver=myresolver
- traefik.http.routers.sonarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/sonarr`) # Added Host check
- traefik.http.routers.sonarr.entrypoints=web
- traefik.http.routers.sonarr.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.sonarr.loadbalancer.server.port=8989
- homepage.group=Media
- homepage.name=Sonarr
@@ -77,8 +102,8 @@ services:
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/radarr:/config
- ${DATA_ROOT}:/data
- ${CONFIG_ROOT:-.}/radarr:/config:Z
- ${DATA_ROOT}:/data:Z
restart: always
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:7878/radarr/ping"]
@@ -86,9 +111,9 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.radarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/radarr`))
- traefik.http.routers.radarr.tls=true
- traefik.http.routers.radarr.tls.certresolver=myresolver
- traefik.http.routers.radarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/radarr`) # Added Host check
- traefik.http.routers.radarr.entrypoints=web
- traefik.http.routers.radarr.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.radarr.loadbalancer.server.port=7878
- homepage.group=Media
- homepage.name=Radarr
@@ -107,8 +132,8 @@ services:
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/lidarr:/config
- ${DATA_ROOT}:/data
- ${CONFIG_ROOT:-.}/lidarr:/config:Z
- ${DATA_ROOT}:/data:Z
restart: always
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:8686/lidarr/ping"]
@@ -116,9 +141,9 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.lidarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/lidarr`))
- traefik.http.routers.lidarr.tls=true
- traefik.http.routers.lidarr.tls.certresolver=myresolver
- traefik.http.routers.lidarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/lidarr`) # Added Host check
- traefik.http.routers.lidarr.entrypoints=web
- traefik.http.routers.lidarr.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.lidarr.loadbalancer.server.port=8686
- homepage.group=Media
- homepage.name=Lidarr
@@ -139,8 +164,8 @@ services:
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/bazarr/config:/config
- ${DATA_ROOT}:/data
- ${CONFIG_ROOT:-.}/bazarr/config:/config:Z
- ${DATA_ROOT}:/data:Z
restart: always
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:6767/bazarr/ping"]
@@ -148,9 +173,9 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.bazarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/bazarr`))
- traefik.http.routers.bazarr.tls=true
- traefik.http.routers.bazarr.tls.certresolver=myresolver
- traefik.http.routers.bazarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/bazarr`) # Added Host check
- traefik.http.routers.bazarr.entrypoints=web
- traefik.http.routers.bazarr.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.bazarr.loadbalancer.server.port=6767
- homepage.group=Download
- homepage.name=Bazarr
@@ -168,7 +193,7 @@ services:
- LOG_LEVEL=debug
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/jellyseerr:/app/config
- ${CONFIG_ROOT:-.}/jellyseerr:/app/config:Z
restart: always
healthcheck:
test:
@@ -183,11 +208,10 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.jellyseerr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/jellyseerr`))
- traefik.http.routers.jellyseerr.tls=true
- traefik.http.routers.jellyseerr.tls.certresolver=myresolver
- traefik.http.routers.jellyseerr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/jellyseerr`) # Added Host check
- traefik.http.routers.jellyseerr.entrypoints=web
- traefik.http.services.jellyseerr.loadbalancer.server.port=5055
- traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders
- traefik.http.routers.jellyseerr.middlewares=https-proto@docker,jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders,authelia-auth@docker
- traefik.http.middlewares.jellyseerr-stripprefix.stripPrefix.prefixes=/jellyseerr
- traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].header=location
- traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].regex=^/(.+)$
@@ -250,7 +274,7 @@ services:
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/prowlarr:/config
- ${CONFIG_ROOT:-.}/prowlarr:/config:Z
restart: always
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:9696/prowlarr/ping"]
@@ -258,9 +282,9 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.prowlarr.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/prowlarr`))
- traefik.http.routers.prowlarr.tls=true
- traefik.http.routers.prowlarr.tls.certresolver=myresolver
- traefik.http.routers.prowlarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/prowlarr`) # Added Host check
- traefik.http.routers.prowlarr.entrypoints=web
- traefik.http.routers.prowlarr.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.prowlarr.loadbalancer.server.port=9696
- homepage.group=Download
- homepage.name=Prowlarr
@@ -282,8 +306,9 @@ services:
- TZ=${TIMEZONE}
labels:
- traefik.enable=true
- traefik.http.routers.flaresolverr.rule=PathPrefix(`/flaresolverr`)
- traefik.http.routers.flaresolverr.tls=true
- traefik.http.routers.flaresolverr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/flaresolverr`) # Added Host check
- traefik.http.routers.flaresolverr.entrypoints=web
- traefik.http.routers.flaresolverr.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.flaresolverr.loadbalancer.server.port=8191
profiles:
- flaresolverr
@@ -297,34 +322,24 @@ services:
- WEBUI_PORT=8080
- DOCKER_MODS=ghcr.io/gabe565/linuxserver-mod-vuetorrent
volumes:
- ${CONFIG_ROOT:-.}/qbittorrent:/config
- ${DOWNLOAD_ROOT}:/data/torrents
- ${CONFIG_ROOT:-.}/qbittorrent:/config:Z
- ${DOWNLOAD_ROOT}:/data/torrents:Z
restart: always
healthcheck:
# Container may fail if the PIA's token expired, so mark as unhealthy when there is no internet connection
# see: https://github.com/qdm12/gluetun/issues/641#issuecomment-933856220
test:
["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"]
interval: 30s
retries: 10
network_mode: "service:vpn"
depends_on:
vpn:
condition: service_healthy
labels:
- traefik.enable=true
- traefik.http.routers.qbittorrent.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/qbittorrent`))
- traefik.http.routers.qbittorrent.tls=true
- traefik.http.routers.qbittorrent.tls.certresolver=myresolver
- traefik.http.routers.qbittorrent.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/qbittorrent`) # Added Host check
- traefik.http.routers.qbittorrent.entrypoints=web
- traefik.http.services.qbittorrent.loadbalancer.server.port=8080
- traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix
# https://github.com/qbittorrent/qBittorrent/issues/5693#issuecomment-552146296
- traefik.http.routers.qbittorrent.middlewares=https-proto@docker,qbittorrent-strip-slash,qbittorrent-stripprefix,authelia-auth@docker
- traefik.http.middlewares.qbittorrent-stripprefix.stripPrefix.prefixes=/qbittorrent
# https://community.traefik.io/t/middleware-to-add-the-if-needed/1895/19
- traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.regex=(^.*\/qbittorrent$$)
- traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/
- traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.permanent=false
#- com.centurylinklabs.watchtower.depends-on=/vpn
- homepage.group=Download
- homepage.name=qBittorrent
- homepage.icon=qbittorrent.png
@@ -332,45 +347,14 @@ services:
- homepage.description=Bittorrent client
- homepage.weight=2
- homepage.widget.type=qbittorrent
- homepage.widget.url=http://vpn:8080
- homepage.widget.url=http://qbittorrent:8080
- homepage.widget.username=${QBITTORRENT_USERNAME}
- homepage.widget.password=${QBITTORRENT_PASSWORD}
vpn:
image: ghcr.io/thrnz/docker-wireguard-pia:latest
container_name: vpn
volumes:
- ${CONFIG_ROOT:-.}/pia:/pia
- ${CONFIG_ROOT:-.}/pia-shared:/pia-shared
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- LOC=${PIA_LOCATION}
- USER=${PIA_USER}
- PASS=${PIA_PASS}
- QBT_USER=${QBITTORRENT_USERNAME}
- QBT_PASS=${QBITTORRENT_PASSWORD}
- LOCAL_NETWORK=${PIA_LOCAL_NETWORK}
- PORT_FORWARDING=1
- PORT_PERSIST=1
- PORT_SCRIPT=/pia-shared/portupdate-qbittorrent.sh
- FIREWALL=0
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.default.disable_ipv6=1
- net.ipv6.conf.all.disable_ipv6=1
- net.ipv6.conf.lo.disable_ipv6=1
healthcheck:
test: ping -c 1 www.google.com || exit 1
interval: 30s
timeout: 10s
retries: 3
restart: always
unpackerr:
image: ghcr.io/unpackerr/unpackerr:latest
container_name: unpackerr
volumes:
- ${DOWNLOAD_ROOT}:/data/torrents
- ${DOWNLOAD_ROOT}:/data/torrents:Z
restart: always
user: ${USER_ID}:${GROUP_ID}
environment:
@@ -389,14 +373,14 @@ services:
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/sabnzbd:/config
- ${DATA_ROOT}:/data
- ${CONFIG_ROOT:-.}/sabnzbd:/config:Z
- ${DATA_ROOT}:/data:Z
restart: always
labels:
- traefik.enable=true
- traefik.http.routers.sabnzbd.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/sabnzbd`) || PathPrefix(`/sabnzbd`))
- traefik.http.routers.sabnzbd.tls=true
- traefik.http.routers.sabnzbd.tls.certresolver=myresolver
- traefik.http.routers.sabnzbd.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/sabnzbd`) # Added Host check
- traefik.http.routers.sabnzbd.entrypoints=web
- traefik.http.routers.sabnzbd.middlewares=https-proto@docker,authelia-auth@docker
- traefik.http.services.sabnzbd.loadbalancer.server.port=8080
- homepage.group=Download
- homepage.name=Sabnzbd
@@ -416,10 +400,10 @@ services:
- PUID=${USER_ID}
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
- JELLYFIN_PublishedServerUrl=${HOSTNAME}/jellyfin
- JELLYFIN_PublishedServerUrl=https://${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/jellyfin
volumes:
- ${CONFIG_ROOT:-.}/jellyfin:/config
- ${DATA_ROOT}:/data
- ${CONFIG_ROOT:-.}/jellyfin:/config:Z
- ${DATA_ROOT}:/data:Z
ports:
- "7359:7359/udp"
- "1900:1900/udp"
@@ -430,9 +414,9 @@ services:
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.jellyfin.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/jellyfin`))
- traefik.http.routers.jellyfin.tls=true
- traefik.http.routers.jellyfin.tls.certresolver=myresolver
- traefik.http.routers.jellyfin.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/jellyfin`) # Added Host check
- traefik.http.routers.jellyfin.entrypoints=web
- traefik.http.routers.jellyfin.middlewares=https-proto@docker # Only HTTPS, no auth
- traefik.http.services.jellyfin.loadbalancer.server.port=8096
- homepage.group=Media
- homepage.name=Jellyfin
@@ -453,18 +437,17 @@ services:
- DOCKER_MODS=linuxserver/mods:universal-calibre
- OAUTHLIB_RELAX_TOKEN_SCOPE=1
volumes:
- ${CONFIG_ROOT:-.}/calibre-web:/config
- ${DATA_ROOT}/books:/books
- ${CONFIG_ROOT:-.}/calibre-web:/config:Z
- ${DATA_ROOT}/books:/books:Z
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Scheme=https
- traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Script-Name=/calibre
- traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre
- traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex
- traefik.http.routers.calibre.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/calibre`))
- traefik.http.routers.calibre.tls=true
- traefik.http.routers.calibre.tls.certresolver=myresolver
- traefik.http.routers.calibre.middlewares=https-proto@docker,calibre-headers,calibre-stripprefixregex,authelia-auth@docker
- traefik.http.routers.calibre.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/calibre`) # Added Host check
- traefik.http.routers.calibre.entrypoints=web
- traefik.http.services.calibre.loadbalancer.server.port=8083
- homepage.group=Media
- homepage.name=Calibre-Web
@@ -526,20 +509,27 @@ services:
- HOMEPAGE_VAR_WEATHER_LONG=${HOMEPAGE_VAR_WEATHER_LONG}
- HOMEPAGE_VAR_WEATHER_TIME=${TIMEZONE}
- HOMEPAGE_VAR_WEATHER_UNIT=${HOMEPAGE_VAR_WEATHER_UNIT}
- HOMEPAGE_ALLOWED_HOSTS=${HOSTNAME}
- HOMEPAGE_ALLOWED_HOSTS=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
volumes:
- ${CONFIG_ROOT:-.}/homepage:/app/config
- ${CONFIG_ROOT:-.}/homepage:/app/config:Z
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${DATA_ROOT}:/data
- ${DATA_ROOT}:/data:Z
restart: always
command:
[sh, -c, "cp -n /app/config/tpl/*.yaml /app/config && node server.js"]
labels:
- traefik.enable=true
- traefik.http.routers.homepage.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/`))
- traefik.http.routers.homepage.tls=true
- traefik.http.routers.homepage.tls.certresolver=myresolver
- traefik.http.services.homepage.loadbalancer.server.port=3000
- traefik.http.routers.homepage.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/home`) # Changed rule to root
- traefik.http.routers.homepage.entrypoints=web
# - traefik.http.routers.homepage.priority=10 # Removed priority
# Global middleware for setting HTTPS header
- traefik.http.middlewares.https-proto.headers.customrequestheaders.X-Forwarded-Proto=https
- traefik.http.routers.homepage.middlewares=https-proto@docker,authelia-auth@docker
- homepage.group=Dashboard
- homepage.name=Homepage
- homepage.icon=homepage.png
- homepage.href=/ # Updated href
- homepage.description=Service Dashboard
watchtower:
image: ghcr.io/containrrr/watchtower:latest
container_name: watchtower
@@ -556,6 +546,60 @@ services:
- AUTOHEAL_CONTAINER_LABEL=all
volumes:
- /var/run/docker.sock:/var/run/docker.sock
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
hostname: ${TAILSCALE_HOSTNAME:-tailscale-nas}
environment:
TS_AUTHKEY: ${TAILSCALE_AUTHKEY}
TS_EXTRA_ARGS: "--advertise-tags=${TAILSCALE_TAGS:-tag:nas}"
TS_STATE_DIR: "/var/lib/tailscale"
TS_USERSPACE: "false"
ENABLE_FUNNEL_HTTPS: ${ENABLE_FUNNEL_HTTPS:-false}
volumes:
- ${CONFIG_ROOT:-.}/tailscale/state:/var/lib/tailscale:Z
- /var/run/docker.sock:/var/run/docker.sock
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
- NET_RAW
extra_hosts:
- host.docker.internal:172.17.0.1
restart: always
command:
- /bin/sh
- -c
- |
set -e
echo "Starting containerboot (tailscaled)..."
/usr/local/bin/containerboot &
echo "Waiting for tailscaled to achieve running state..."
retries=60
count=0
until tailscale status --json | grep -q '"BackendState": "Running"'; do
count=$$(($$count+1))
if [ $$count -gt $$retries ]; then
echo "Error: tailscaled did not reach running state after $$retries seconds."
exit 1
fi
echo -n "."
sleep 1
done
echo " Tailscaled is running."
if [ "${ENABLE_FUNNEL_HTTPS}" = "true" ]; then
echo "ENABLE_FUNNEL_HTTPS is true. Setting up Funnel -> http://localhost:80..."
tailscale funnel --bg http://localhost:80
echo "Tailscale Funnel configured."
else
echo "ENABLE_FUNNEL_HTTPS is false. Setting up Serve -> http://localhost:80..."
tailscale serve --bg http://localhost:80
echo "Tailscale Serve configured."
fi
echo "Tailscale forwarding configured. Container will remain running."
wait
networks:
default:

View File

@@ -1,49 +0,0 @@
#!/bin/bash
# See https://stackoverflow.com/a/44864004 for the sed GNU/BSD compatible hack
function update_arr_config {
echo "Updating ${container} configuration..."
until [ -f "${CONFIG_ROOT:-.}"/"$container"/config.xml ]; do sleep 1; done
sed -i.bak "s/<UrlBase><\/UrlBase>/<UrlBase>\/$1<\/UrlBase>/" "${CONFIG_ROOT:-.}"/"$container"/config.xml && rm "${CONFIG_ROOT:-.}"/"$container"/config.xml.bak
CONTAINER_NAME_UPPER=$(echo "$container" | tr '[:lower:]' '[:upper:]')
sed -i.bak 's/^'"${CONTAINER_NAME_UPPER}"'_API_KEY=.*/'"${CONTAINER_NAME_UPPER}"'_API_KEY='"$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/"$container"/config.xml)"'/' .env && rm .env.bak
echo "Update of ${container} configuration complete, restarting..."
docker compose restart "$container"
}
function update_qbittorrent_config {
echo "Updating ${container} configuration..."
docker compose stop "$container"
until [ -f "${CONFIG_ROOT:-.}"/"$container"/qBittorrent/qBittorrent.conf ]; do sleep 1; done
sed -i.bak '/WebUI\\ServerDomains=*/a WebUI\\Password_PBKDF2="@ByteArray(ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==)"' "${CONFIG_ROOT:-.}"/"$container"/qBittorrent/qBittorrent.conf && rm "${CONFIG_ROOT:-.}"/"$container"/qBittorrent/qBittorrent.conf.bak
echo "Update of ${container} configuration complete, restarting..."
docker compose start "$container"
}
function update_bazarr_config {
echo "Updating ${container} configuration..."
until [ -f "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml ]; do sleep 1; done
sed -i.bak "s/base_url: ''/base_url: '\/$container'/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
sed -i.bak "s/use_radarr: false/use_radarr: true/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
sed -i.bak "s/use_sonarr: false/use_sonarr: true/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
until [ -f "${CONFIG_ROOT:-.}"/sonarr/config.xml ]; do sleep 1; done
SONARR_API_KEY=$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/sonarr/config.xml)
sed -i.bak "/sonarr:/,/^radarr:/ { s/apikey: .*/apikey: $SONARR_API_KEY/; s/base_url: .*/base_url: \/sonarr/; s/ip: .*/ip: sonarr/ }" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
until [ -f "${CONFIG_ROOT:-.}"/radarr/config.xml ]; do sleep 1; done
RADARR_API_KEY=$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/radarr/config.xml)
sed -i.bak "/radarr:/,/^sonarr:/ { s/apikey: .*/apikey: $RADARR_API_KEY/; s/base_url: .*/base_url: \/radarr/; s/ip: .*/ip: radarr/ }" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
sed -i.bak 's/^BAZARR_API_KEY=.*/BAZARR_API_KEY='"$(sed -n 's/.*apikey: \(.*\)*/\1/p' "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml | head -n 1)"'/' .env && rm .env.bak
echo "Update of ${container} configuration complete, restarting..."
docker compose restart "$container"
}
for container in $(docker ps --format '{{.Names}}'); do
if [[ "$container" =~ ^(radarr|sonarr|lidarr|prowlarr)$ ]]; then
update_arr_config "$container"
elif [[ "$container" =~ ^(bazarr)$ ]]; then
update_bazarr_config "$container"
elif [[ "$container" =~ ^(qbittorrent)$ ]]; then
update_qbittorrent_config "$container"
fi
done

1370
update-setup.sh Executable file

File diff suppressed because it is too large Load Diff