Compare commits

...

29 Commits

Author SHA1 Message Date
aki
ba889f9c38 feat(update-setup): Update to support the new authelia/configuration.yml
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 15:20:06 +08:00
aki
026d24a3ae fix(authelia): Update access control rules and remove non-existing trusted proxies configuration 2025-04-26 15:19:33 +08:00
aki
2ae84f4481 feat(setup): Enhance update script and add user database example 2025-04-26 14:10:11 +08:00
aki
f3fab15ffb fix(traefik): Remove unnecessary middleware definitions and update HTTPS header configuration
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 12:33:11 +08:00
aki
db968ba5ca fix(traefik): Update middleware configuration for HTTPS and routing rules
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 12:27:09 +08:00
aki
191d25e281 fix(authelia): Add trusted proxies configuration
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 12:10:42 +08:00
aki
f07d0937d9 refactor(docker-compose): Move authelia to /auth, then bring back host checks
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 11:16:37 +08:00
aki
749aa6f1cf fix(auth): Refactor service authentication status check 2025-04-26 03:27:49 +08:00
aki
f4409eb258 feat(auth): Enhance authentication management with yq support for YAML parsing
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 03:17:22 +08:00
aki
a74707dc1f fix(authelia): Authentication management and middleware errors
Some checks failed
/ validate-docker-compose (push) Has been cancelled
- Fix middleware "true@docker" does not exist errors
- Integrate authentication management directly into update-setup.sh
- Add command-line support for managing service authentication
- Add backup file cleanup functionality
- Update README with new authentication management instructions
- Remove standalone manage-auth.sh script
2025-04-26 02:59:46 +08:00
aki
5d7a162647 feat(auth): Add authentication management script and update permissions for setup script
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 02:45:09 +08:00
aki
a0e63e2e2b feat(auth): Implement conditional authentication middleware for services in docker-compose.yml
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 01:58:42 +08:00
aki
2fadb08c72 feat(auth): Add additional service authentication settings in .env.example and update docker-compose.yml for conditional middlewares
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 01:32:13 +08:00
aki
6d2baa7300 fix(update-setup): Enhance password hash verification in Authelia account management
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 01:20:15 +08:00
aki
13b73671f8 feat(authelia): Add account management functionality to update users and passwords
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 01:11:16 +08:00
aki
2217377ae8 refactor(update-setup): Enhance configuration update process with error handling and summary reporting
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 00:51:17 +08:00
aki
ca4c3e92f0 fix(docker-compose): Update Redis command and healthcheck to use actual password variable 2025-04-26 00:51:12 +08:00
aki
3ce92b7394 fix(authelia): Add Redis password configuration to Authelia setup script
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 00:45:53 +08:00
aki
4ad7bf0a38 fix(authelia): Update configuration and setup script for Tailscale domain handling in Authelia v4.38+
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 00:41:41 +08:00
aki
6d9139408d refactor: Consolidate Authelia configuration management and update setup scripts
Some checks failed
/ validate-docker-compose (push) Has been cancelled
- Removed outdated configuration files and scripts.
- Introduced a new setup script to streamline environment and Authelia configuration updates.
- Enhanced .gitignore to exclude unnecessary files.
- Updated README to reflect new setup process and configuration details for Authelia v4.38+.
2025-04-26 00:32:24 +08:00
aki
6e17920cfd docs: Update README with required setup steps and configuration details for Authelia v4.38+
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 00:18:59 +08:00
aki
6b1a8b7d45 fix(authelia): Adjust configuration for Tailscale domain handling and simplify session settings 2025-04-26 00:18:57 +08:00
aki
09b20f71fc fix(authelia): Add user configuration for Authelia container
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 00:08:32 +08:00
aki
afbffb97e3 fix(authelia): Update configuration for v4.38+ with required variables and improved domain handling
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-26 00:04:49 +08:00
aki
1c5959cafb fix(env): Enhance environment variable tracking by adding current key presence check
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 23:52:33 +08:00
aki
73e40af91a feat: Add environment update script to manage .env variables and preserve existing values
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 23:48:09 +08:00
aki
461a0dc110 fix(authelia): Update configuration for v4.38+ compatibility and remove deprecated variables
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 23:40:25 +08:00
aki
91873062c9 fix(env): Rename HOSTNAME to APP_HOSTNAME to avoid conflicts and update related configurations
Some checks failed
/ validate-docker-compose (push) Has been cancelled
2025-04-25 21:52:38 +08:00
aki
8a52e6894f feat!: Add Authelia for authentication and Redis for session storage
Some checks failed
/ validate-docker-compose (push) Has been cancelled
- Introduced Redis service for session management with health checks.
- Added Authelia service for user authentication with necessary environment variables.
- Configured Traefik to use Authelia as middleware for various services.
- Created Authelia configuration file with session, storage, and access control settings.
- Added user database for Authelia with an example admin user.
2025-04-25 17:33:09 +08:00
8 changed files with 1657 additions and 306 deletions

View File

@@ -36,9 +36,10 @@ TAILSCALE_TAGS=tag:nas
# Enable Tailscale Funnel (public access) for HTTPS? Set to 'true' or 'false'. 'false' uses Serve (Tailnet only, recommended). # Enable Tailscale Funnel (public access) for HTTPS? Set to 'true' or 'false'. 'false' uses Serve (Tailnet only, recommended).
ENABLE_FUNNEL_HTTPS=false ENABLE_FUNNEL_HTTPS=false
# --- Primary Hostname --- # --- Primary Application Hostname ---
# Primary hostname used by Traefik for routing. Derived from Tailscale settings by default. # Primary hostname used by Traefik/Authelia. Derived from Tailscale settings by default.
HOSTNAME=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN} # Renamed from HOSTNAME to avoid collision with host system environment variable.
APP_HOSTNAME=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
# --- Application Credentials --- # --- Application Credentials ---
# qBittorrent Web UI Credentials (change default!) # qBittorrent Web UI Credentials (change default!)
@@ -62,18 +63,16 @@ HOMEPAGE_VAR_WEATHER_UNIT=metric
# --- Authelia Settings --- # --- Authelia Settings ---
# Generate strong random secrets for these using tools like `openssl rand -hex 32` # Generate strong random secrets for these using tools like `openssl rand -hex 32`
AUTHELIA_JWT_SECRET= # Example: your_strong_jwt_secret # These are all REQUIRED for Authelia to function properly
AUTHELIA_SESSION_SECRET= # Example: your_strong_session_secret AUTHELIA_JWT_SECRET= # Secret used for JWT tokens (password reset, etc)
AUTHELIA_STORAGE_ENCRYPTION_KEY= # Example: your_strong_storage_encryption_key AUTHELIA_SESSION_SECRET= # Secret for encrypting session cookies
AUTHELIA_REDIS_PASSWORD= # Example: your_strong_redis_password AUTHELIA_STORAGE_ENCRYPTION_KEY= # Secret for encrypting stored data
AUTHELIA_REDIS_PASSWORD= # Password for Redis session storage
# Google OIDC Provider Settings (Get from Google Cloud Console - https://console.cloud.google.com/apis/credentials) # Note: The following variables are no longer needed with Authelia 4.38+ and the updated configuration
AUTHELIA_GOOGLE_OIDC_CLIENT_ID= # Example: your-google-client-id.apps.googleusercontent.com # They are preserved for backward compatibility but will be automatically mapped to the new structure
AUTHELIA_GOOGLE_OIDC_CLIENT_SECRET= # Example: GOCSPX-your-google-client-secret # AUTHELIA_SESSION_DOMAIN=${APP_HOSTNAME}
# AUTHELIA_DEFAULT_REDIRECT_URL=https://${APP_HOSTNAME}/home
# Authelia Session Configuration
AUTHELIA_SESSION_DOMAIN=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
AUTHELIA_DEFAULT_REDIRECT_URL=https://${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/home
# --- API Keys & Integration Tokens (Optional - Mainly for Homepage Widgets) --- # --- API Keys & Integration Tokens (Optional - Mainly for Homepage Widgets) ---
# Find API keys within each application's settings (usually Settings > General or Security) # Find API keys within each application's settings (usually Settings > General or Security)

3
.gitignore vendored
View File

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

642
README.md
View File

@@ -4,228 +4,415 @@ This project provides a comprehensive, self-hosted media and utility server setu
The core idea is to manage media libraries (movies, TV shows, music), automate downloads securely, provide easy access via a dashboard, and enable remote access through Tailscale. The core idea is to manage media libraries (movies, TV shows, music), automate downloads securely, provide easy access via a dashboard, and enable remote access through Tailscale.
## Table of Contents
- [Docker Compose NAS](#docker-compose-nas)
- [Table of Contents](#table-of-contents)
- [Architecture Overview](#architecture-overview)
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Required Setup Steps](#required-setup-steps)
- [Quick Start Guide](#quick-start-guide)
- [Configuration (`.env` Variables)](#configuration-env-variables)
- [Core System \& Paths](#core-system--paths)
- [Networking \& Access (Tailscale)](#networking--access-tailscale)
- [Authentication (Authelia)](#authentication-authelia)
- [Service Credentials](#service-credentials)
- [Homepage Customization \& Widgets](#homepage-customization--widgets)
- [Optional Features \& Services](#optional-features--services)
- [Detailed Setup \& Usage](#detailed-setup--usage)
- [Authelia User Management](#authelia-user-management)
- [(Optional) VPN Configuration](#optional-vpn-configuration)
- [(Optional) Traefik DNS Challenge](#optional-traefik-dns-challenge)
- [Service Access](#service-access)
- [Setup Script Commands (`update-setup.sh`)](#setup-script-commands-update-setupsh)
- [Managing Service Authentication](#managing-service-authentication)
- [Optional Services](#optional-services)
- [Troubleshooting](#troubleshooting)
- [Middleware Not Found Errors](#middleware-not-found-errors)
- [SELinux Socket Permissions (Docker)](#selinux-socket-permissions-docker)
- [Authelia v4.38+ Configuration](#authelia-v438-configuration)
- [Tailscale Issues](#tailscale-issues)
- [File Permissions](#file-permissions)
- [Advanced Topics](#advanced-topics)
## Architecture Overview
This stack uses a combination of key services for routing, access, and security:
* **[Tailscale](https://tailscale.com):** Provides a secure overlay network (WireGuard-based VPN) connecting your devices. It allows access to the NAS services from anywhere without opening firewall ports and handles HTTPS termination via its built-in `tailscale serve` or `tailscale funnel` features. All other services run within Tailscale's network namespace.
* **[Traefik](https://traefik.io):** Acts as a reverse proxy *within* the Tailscale network. It discovers services via Docker labels and routes incoming requests (from Tailscale) to the appropriate container based on paths (e.g., `/sonarr`, `/radarr`).
* **[Authelia](https://www.authelia.com):** Serves as the authentication gateway. Traefik forwards requests to Authelia for verification. If a user isn't logged in, they are redirected to the Authelia portal (`/`). Once authenticated, Authelia sets a session cookie (stored in Redis), and Traefik allows access to the requested service. You can configure which services require authentication via environment variables.
## Features ## Features
This stack includes: This stack includes the following services, categorized for clarity:
* **Reverse Proxy & Service Discovery:** [Traefik](https://traefik.io) automatically routes traffic to services. **Core Infrastructure:**
* **Media Management:** * **Reverse Proxy:** [Traefik](https://traefik.io) - Manages internal routing and service discovery.
* [Sonarr](https://sonarr.tv): TV show management. * **Secure Remote Access:** [Tailscale](https://tailscale.com) - Provides VPN access and HTTPS.
* [Radarr](https://radarr.video): Movie management. * **Authentication:** [Authelia](https://www.authelia.com) & [Redis](https://redis.io) - Single sign-on portal and session management.
* [Lidarr](https://lidarr.audio) (Optional): Music management. * **Dashboard:** [Homepage](https://gethomepage.dev) - Centralized access point (at `/home`).
* [Bazarr](https://www.bazarr.media/): Subtitle management.
* **Indexers & Downloads:** **Media Management (\*Arr Suite):**
* [Prowlarr](https://github.com/Prowlarr/Prowlarr): Indexer management for *arr apps. * [Sonarr](https://sonarr.tv): TV show management.
* [qBittorrent](https://www.qbittorrent.org): Bittorrent client (can be configured to run through a VPN). * [Radarr](https://radarr.video): Movie management.
* [SABnzbd](https://sabnzbd.org/) (Optional): Usenet download client. * [Lidarr](https://lidarr.audio) (Optional): Music management.
* **Media Server:** [Jellyfin](https://jellyfin.org) organizes and streams your media. * [Bazarr](https://www.bazarr.media/): Subtitle management.
* **Request Management:** [Jellyseerr](https://github.com/FallenBagel/jellyseerr) allows users (including Jellyfin users) to request media. * [Prowlarr](https://github.com/Prowlarr/Prowlarr): Indexer management for *arr apps.
* **Dashboard:** [Homepage](https://gethomepage.dev) provides a central dashboard to access all services.
* **Remote Access:** [Tailscale](https://tailscale.com) provides secure access to your services from anywhere without opening firewall ports. It handles HTTPS termination. **Download Clients:**
* **Utilities:** * [qBittorrent](https://www.qbittorrent.org): Bittorrent client.
* [Watchtower](https://containrrr.dev/watchtower/): Automatically updates running containers to the latest image. * [SABnzbd](https://sabnzbd.org/) (Optional): Usenet download client.
* [Autoheal](https://github.com/willfarrell/docker-autoheal/): Monitors and restarts unhealthy containers.
* [Unpackerr](https://unpackerr.zip): Automatically extracts downloaded archives. **Media Serving & Requests:**
* **Other Optional Services:** AdGuard Home, Calibre-Web, Decluttarr, Tandoor Recipes, Joplin Server, Home Assistant, Immich Photos (enable via profiles). * [Jellyfin](https://jellyfin.org): Media server for streaming.
* [Jellyseerr](https://github.com/FallenBagel/jellyseerr): Media request management.
**Utilities:**
* [Watchtower](https://containrrr.dev/watchtower/): Automatic container updates.
* [Autoheal](https://github.com/willfarrell/docker-autoheal/): Automatic container restarts on failure.
* [Unpackerr](https://unpackerr.zip): Automated archive extraction.
**Optional Services (Enabled via Profiles):**
* [AdGuard Home](https://adguard.com/en/adguard-home/overview.html): Network-wide ad blocking.
* [Calibre-Web](https://github.com/janeczku/calibre-web): E-book library management.
* [Decluttarr](https://github.com/manimatter/decluttarr): Automated download cleanup.
* [Tandoor Recipes](https://docs.tandoor.dev/): Recipe management.
* [Joplin Server](https://joplinapp.org/): Note-taking synchronization server.
* [Home Assistant](https://www.home-assistant.io/): Home automation platform.
* [Immich](https://immich.app/): Self-hosted photo and video backup.
* [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr): Bypasses Cloudflare challenges (e.g., for Prowlarr).
## Prerequisites ## Prerequisites
* **Linux Host:** Any recent Linux distribution capable of running Docker. Tested on Ubuntu Server 22.04. * **Linux Host:** A system capable of running Docker (e.g., Ubuntu, Debian, Fedora).
* **Docker Engine:** Install the latest version of Docker Engine. [Official Installation Guide](https://docs.docker.com/engine/install/). * **Docker & Docker Compose:** Latest versions installed. See [Docker Engine Install](https://docs.docker.com/engine/install/) and [Docker Compose Install](https://docs.docker.com/compose/install/).
* **Docker Compose V2:** Ensure you have Docker Compose V2 (usually installed as a Docker plugin, invoked via `docker compose`). [Official Installation Guide](https://docs.docker.com/compose/install/). * **User Permissions:** Ability to run `docker` commands (user in `docker` group or use `sudo`).
* **User Permissions:** You'll need a user account that can run `docker` commands (usually by adding the user to the `docker` group) or run `docker compose` via `sudo`. * **Basic Linux Knowledge:** Familiarity with command line, text editors, and file permissions.
* **SELinux (If Enabled):** If your host uses SELinux (e.g., Fedora, CentOS, RHEL), you might need additional host configuration. See the [Troubleshooting](#selinux-socket-permissions) section. * **Tailscale Account:** Required for remote access. [Sign up here](https://tailscale.com/login).
* **(Optional) SELinux:** If enabled, see [Troubleshooting](#selinux-socket-permissions).
## Quick Start ## Required Setup Steps
1. **Clone the Repository:** These steps are **mandatory** for a working installation. Without properly completing these, the stack will not function correctly.
1. **⚠️ Required: System User Information**
- Set `USER_ID` and `GROUP_ID` in `.env` (run `id -u` and `id -g` to get yours)
- Incorrect values will cause permission errors with mounted volumes
2. **⚠️ Required: Directory Paths**
- Set `CONFIG_ROOT` (where service configurations will be stored)
- Set `DATA_ROOT` (where your media libraries will be stored)
- Set `DOWNLOAD_ROOT` (must be on same filesystem as DATA_ROOT for hardlinks)
3. **⚠️ Required: Tailscale Configuration**
- Create a Tailscale account at [tailscale.com](https://tailscale.com)
- Generate an Auth Key in the [Tailscale Admin Console](https://login.tailscale.com/admin/settings/keys)
- Set `TAILSCALE_AUTHKEY` in `.env`
- Set `TAILSCALE_TAILNET_DOMAIN` to your Tailnet domain (e.g., `your-name.ts.net`)
4. **⚠️ Required: Security Credentials**
- Generate four strong random secrets using `openssl rand -hex 32`:
```bash
echo "AUTHELIA_JWT_SECRET=$(openssl rand -hex 32)"
echo "AUTHELIA_SESSION_SECRET=$(openssl rand -hex 32)"
echo "AUTHELIA_STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32)"
echo "AUTHELIA_REDIS_PASSWORD=$(openssl rand -hex 32)"
```
- Add these to your `.env` file as shown
5. **⚠️ Required: Create Admin Password**
- Generate a password hash for Authelia:
```bash
docker run --rm authelia/authelia:latest authelia hash-password 'your_secure_password'
```
- Replace the example hash in `authelia/users_database.yml` with your generated hash
6. **⚠️ Optional: Authentication Configuration**
- Choose which services require authentication by setting the corresponding variables in your `.env` file:
```
AUTH_JELLYFIN=false # Example: Allow Jellyfin access without authentication
AUTH_SONARR=true # Example: Require authentication for Sonarr
```
- By default, all services require authentication if not specified otherwise
## Quick Start Guide
After completing all [Required Setup Steps](#required-setup-steps) above, follow these steps to get up and running:
1. **Clone Repository:**
```bash
git clone https://github.com/AdrienPoupa/docker-compose-nas.git
cd docker-compose-nas
```
2. **Configure Environment:**
```bash
# Create an .env file from the example
cp .env.example .env
# Edit the .env file and fill in ALL required values
nano .env
```
3. **Configure Authelia Admin:**
```bash
# Generate a password hash
docker run --rm authelia/authelia:latest authelia hash-password 'your_secure_password'
# Edit the users_database.yml with the generated hash
nano authelia/users_database.yml
```
4. **Run the Setup Script:**
```bash
# Make the script executable
chmod +x ./update-setup.sh
# Run the setup tool (use 'all' for initial setup)
./update-setup.sh all
```
This script will:
- Update your `.env` file while preserving existing values (`update-env`).
- Configure Authelia with your Tailscale domain settings (`update-authelia`).
- Set up service configurations and retrieve API keys (`update-services`).
You can also run individual commands like `./update-setup.sh update-authelia`. Run `./update-setup.sh help` for all options.
5. **Start the Stack:**
```bash
# Start containers
docker compose up -d
```
*(Wait for containers to download and start)*
6. **Access Your NAS:**
- Open `https://<TAILSCALE_HOSTNAME>.<TAILSCALE_TAILNET_DOMAIN>/`
- Log in with username `admin` and the password you set
- After login, you'll land on the Homepage dashboard at `/home`
> ⚠️ **IMPORTANT:** If the stack fails to start, check the [Troubleshooting](#troubleshooting) section and verify you've properly completed all [Required Setup Steps](#required-setup-steps).
## Configuration (`.env` Variables)
This file controls essential settings. Copy `.env.example` to `.env` and modify the values. **Bold variables** are critical for initial setup.
#### Core System & Paths
| Variable | Description | Default |
| :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- |
| **`USER_ID`** | Linux user ID for container permissions. Find with `id -u`. | `1000` |
| **`GROUP_ID`** | Linux group ID for container permissions. Find with `id -g`. | `1000` |
| **`TIMEZONE`** | Your local timezone (e.g., `America/New_York`, `Asia/Manila`). [List](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | `America/New_York` |
| `CONFIG_ROOT` | Host base directory for service configurations. `.` = project subdirs. | `.` |
| `DATA_ROOT` | Host directory for media libraries (movies, TV, music, etc.). | `/mnt/data` |
| `DOWNLOAD_ROOT` | Host directory for downloads (in progress/completed). **Must be on same filesystem as `DATA_ROOT` for hardlinks.** | `/mnt/data/torrents` |
| `IMMICH_UPLOAD_LOCATION` | Host path for Immich uploads (if `immich` profile enabled). | `/mnt/data/photos` |
#### Networking & Access (Tailscale)
| Variable | Description | Default |
| :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- |
| **`TAILSCALE_AUTHKEY`** | **Required.** Auth key from [Tailscale Admin Console](https://login.tailscale.com/admin/settings/keys). Use reusable or ephemeral. | *(None)* |
| **`TAILSCALE_TAILNET_DOMAIN`**| **Required.** Your unique Tailnet domain (e.g., `your-name.ts.net`). | `your-tailnet.ts.net` |
| `TAILSCALE_HOSTNAME` | Desired hostname for this NAS within Tailscale. | `tailscale-nas` |
| `TAILSCALE_TAGS` | Optional tags for the Tailscale node (e.g., `tag:nas`). | `tag:nas` |
| `ENABLE_FUNNEL_HTTPS` | Use Tailscale Funnel (`true` = public access via Tailscale domain) or Serve (`false` = Tailnet-only access, recommended). | `false` |
| `APP_HOSTNAME` | Primary hostname used by Traefik/Authelia. Defaults to Tailscale FQDN. Renamed from `HOSTNAME` to avoid host system conflicts. Can be overridden if using custom DNS. | `${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}` |
#### Authentication (Authelia)
| Variable | Description | Default |
| :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- |
| **`AUTHELIA_JWT_SECRET`** | **Required.** Random secret for Authelia (used for password reset JWT). **Generate your own!** | *(None - Example in file)* |
| **`AUTHELIA_SESSION_SECRET`** | **Required.** Random secret for session cookies. **Generate your own!** | *(None - Example in file)* |
| **`AUTHELIA_STORAGE_ENCRYPTION_KEY`** | **Required.** Random secret for encrypting data at rest (e.g., SQLite DB). **Generate your own!** | *(None - Example in file)* |
| **`AUTHELIA_REDIS_PASSWORD`** | **Required.** Password for the Redis database (used for session storage). **Generate your own!** | *(None - Example in file)* |
| `AUTH_SONARR` | Control whether Sonarr requires authentication (`true`/`false`). | `true` |
| `AUTH_RADARR` | Control whether Radarr requires authentication (`true`/`false`). | `true` |
| `AUTH_BAZARR` | Control whether Bazarr requires authentication (`true`/`false`). | `true` |
| `AUTH_PROWLARR` | Control whether Prowlarr requires authentication (`true`/`false`). | `true` |
| `AUTH_JELLYSEERR` | Control whether Jellyseerr requires authentication (`true`/`false`). | `true` |
| `AUTH_QBITTORRENT` | Control whether qBittorrent requires authentication (`true`/`false`). | `true` |
| `AUTH_LIDARR` | Control whether Lidarr requires authentication (`true`/`false`). | `true` |
| `AUTH_JELLYFIN` | Control whether Jellyfin requires authentication (`true`/`false`). | `false` |
| `AUTH_HOMEPAGE` | Control whether Homepage requires authentication (`true`/`false`). | `true` |
| `AUTH_FLARESOLVERR` | Control whether FlareSolverr requires authentication (`true`/`false`). | `true` |
| `AUTH_SABNZBD` | Control whether SABnzbd requires authentication (`true`/`false`). | `true` |
| `AUTH_CALIBRE` | Control whether Calibre-Web requires authentication (`true`/`false`). | `true` |
> **Note:** Authentication variables were introduced to give you fine-grained control over which services require login. Set to `false` for services you want to access without authentication.
#### Service Credentials
| Variable | Description | Default |
| :----------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- |
| `QBITTORRENT_USERNAME` | Username for qBittorrent Web UI. | `admin` |
| `QBITTORRENT_PASSWORD` | Password for qBittorrent Web UI. **Change default!** (May need to use temp password from logs on first run, then change in UI & `.env`). | `adminadmin` |
| `CALIBRE_USERNAME` | Username for Calibre-Web (if `calibre-web` profile enabled). | `admin` |
| `CALIBRE_PASSWORD` | Password for Calibre-Web (if `calibre-web` profile enabled). | `admin123` |
| `IMMICH_DB_PASSWORD` | Password for Immich's internal database (if `immich` profile enabled). | `postgres` |
| `ADGUARD_USERNAME` | Username for AdGuard Home (if `adguardhome` profile enabled). | *(None)* |
| `ADGUARD_PASSWORD` | Password for AdGuard Home (if `adguardhome` profile enabled). | *(None)* |
#### Homepage Customization & Widgets
| Variable | Description | Default |
| :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- |
| `HOMEPAGE_VAR_TITLE` | Title shown on the dashboard. | `Docker-Compose NAS` |
| `HOMEPAGE_VAR_SEARCH_PROVIDER` | Default search engine. [Options](https://gethomepage.dev/en/widgets/search/). | `google` |
| `HOMEPAGE_VAR_HEADER_STYLE` | Dashboard header style. [Options](https://gethomepage.dev/en/configs/settings/#header-style). | `boxed` |
| `HOMEPAGE_VAR_WEATHER_CITY` | City for weather widget. | *(None)* |
| `HOMEPAGE_VAR_WEATHER_LAT` | Latitude for weather widget. | *(None)* |
| `HOMEPAGE_VAR_WEATHER_LONG` | Longitude for weather widget. | *(None)* |
| `HOMEPAGE_VAR_WEATHER_UNIT` | Weather units (`metric` or `imperial`). | `metric` |
| `SONARR_API_KEY` | API Keys for various services, primarily used for Homepage widgets. Find keys in each app's settings. | *(None)* |
| `RADARR_API_KEY` | " | *(None)* |
| `LIDARR_API_KEY` | " (if `lidarr` profile enabled) | *(None)* |
| `BAZARR_API_KEY` | " | *(None)* |
| `PROWLARR_API_KEY` | " | *(None)* |
| `JELLYFIN_API_KEY` | " | *(None)* |
| `JELLYSEERR_API_KEY` | " | *(None)* |
| `SABNZBD_API_KEY` | " (if `sabnzbd` profile enabled) | *(None)* |
| `IMMICH_API_KEY` | " (if `immich` profile enabled) | *(None)* |
| `HOMEASSISTANT_ACCESS_TOKEN` | " (if `homeassistant` profile enabled) | *(None)* |
#### Optional Features & Services
| Variable | Description | Default |
| :---------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- |
| `COMPOSE_PROFILES` | Comma-separated list of optional service profiles to enable (e.g., `lidarr,sabnzbd`). See [Optional Services](#optional-services). | *(None)* |
| `COMPOSE_PATH_SEPARATOR` | Path separator for `COMPOSE_FILE` (use `;` for Windows). | `:` |
| `COMPOSE_FILE` | Colon-separated list of compose files to use. Allows extending base config. | `docker-compose.yml:...` (See `.env.example`) |
| `DECLUTTARR_TEST_RUN` | Run Decluttarr in test mode (`True`/`False`)? (if `decluttarr` profile enabled). | `True` |
| `DECLUTTARR_...` | Other Decluttarr settings (see `.env.example`). | *(Varies)* |
| `PIA_USER` / `PIA_PASS` | Credentials for PIA VPN (if using default VPN setup for qBittorrent). | *(None)* |
| `PIA_LOCATION` | PIA server location (if using default VPN). [List](https://serverlist.piaservers.net/vpninfo/servers/v6). | `ca` |
| `PIA_LOCAL_NETWORK` | Your local network CIDR (e.g., `192.168.1.0/24`) to allow local access to VPN'd containers. | `192.168.0.0/16` |
| `DNS_CHALLENGE` | Enable Traefik DNS challenge for Let's Encrypt (`true`/`false`). **Not needed if using Tailscale for HTTPS.** | `true` |
| `DNS_CHALLENGE_PROVIDER` | Your DNS provider (e.g., `cloudflare`). [Providers](https://doc.traefik.io/traefik/https/acme/#providers). | `cloudflare` |
| `LETS_ENCRYPT_EMAIL` | Email for Let's Encrypt (if using DNS challenge). | *(None)* |
| `LETS_ENCRYPT_CA_SERVER` | Let's Encrypt server URL (if using DNS challenge). | `https://acme-v02.api.letsencrypt.org/directory` |
| `CLOUDFLARE_...` / `PROVIDER_...` | DNS provider API credentials (if using DNS challenge). | *(None)* |
| `HOMEASSISTANT_HOSTNAME` | Specific hostname for Home Assistant (if `homeassistant` profile enabled). | *(None)* |
| `IMMICH_HOSTNAME` | Specific hostname for Immich (if `immich` profile enabled). | *(None)* |
| `ADGUARD_HOSTNAME` | Specific hostname for AdGuard Home (if `adguardhome` profile enabled). | *(None)* |
## Detailed Setup & Usage
### Authelia User Management
Authelia uses the `authelia/users_database.yml` file to manage users.
* **Setting the Initial Admin Password:**
1. As mentioned in the Quick Start, you **must** set a strong password for the default `admin` user.
2. Generate a hash using Docker (replace `'your_secure_password'`):
```bash
docker run --rm authelia/authelia:latest authelia hash-password 'your_secure_password'
```
3. Copy the **entire output hash** (starting with `$argon2id...`).
4. Open `authelia/users_database.yml` and replace the example `password:` value under `admin:` with your generated hash.
5. Ensure the `admin` user belongs to the `admins` and `users` groups as shown in the example.
* **Adding More Users:**
1. Generate a password hash for the new user as shown above.
2. Edit `authelia/users_database.yml`.
3. Add a new entry under `users:`, following the format of the `admin` user:
```yaml
users:
admin:
# ... (admin details) ...
newuser:
displayname: "New User Name"
password: "paste_generated_hash_here"
email: newuser@example.com
groups:
- users # Add to 'admins' group if needed
```
4. Save the file and restart Authelia: `docker compose restart authelia`.
* **Adding/Updating Users (Recommended Method):**
Use the setup script's interactive tool:
```bash ```bash
git clone https://github.com/AdrienPoupa/docker-compose-nas.git ./update-setup.sh manage-accounts
cd docker-compose-nas
``` ```
This script handles password hashing and file formatting, reducing the chance of errors. It will prompt you for the username, display name, email, and groups, then generate a secure password hash.
2. **Create Configuration File:** * **Enabling User Registration (Optional):**
Copy the example environment file: 1. Edit `authelia/configuration.yml`.
```bash 2. Find the commented-out `registration:` section near the bottom.
cp .env.example .env 3. Uncomment it and set `enable: true`.
``` 4. Save the file and restart Authelia (`docker compose restart authelia`).
5. A "Register" link will now appear on the Authelia login page.
3. **Edit `.env` File:** * **Approving Registered Users:**
Open the `.env` file with a text editor and configure it according to your system and preferences. **This is the most crucial step.** See the detailed [Configuration (`.env` File)](#configuration-env-file) section below for explanations of each variable. Minimally, you **must** set `USER_ID`, `GROUP_ID`, `TIMEZONE`, `HOSTNAME`, and `TAILSCALE_AUTHKEY`. 1. When a user registers (if enabled), their details are added to `authelia/users_database.yml` but marked as `disabled: true`.
2. To approve them, edit `authelia/users_database.yml`.
3. Find the new user's entry.
4. Change `disabled: true` to `disabled: false` (or simply remove the `disabled: true` line).
5. Save the file. The user should now be able to log in.
4. **Start the Stack:** ### (Optional) VPN Configuration
Run Docker Compose (use `sudo` if your user isn't in the `docker` group):
```bash
docker compose up -d
```
This will pull the necessary images and start all the core services in the background.
5. **Run Initial Configuration Script:** *(Details about configuring the PIA VPN or other VPN setups could go here if needed.)*
This script helps configure base URLs and API keys within the running *arr applications based on your `.env` file.
```bash
./update-config.sh
```
*(Note: You might need to make it executable first: `chmod +x ./update-config.sh`)*
6. **Access Services:** Once Tailscale is connected, you should be able to access your services via `https://<TAILSCALE_HOSTNAME>.<your-tailnet-name>.ts.net/<service_path>` or `https://<TAILSCALE_IP>/<service_path>`. If you set up DNS for your `HOSTNAME`, you can use `https://<HOSTNAME>/<service_path>`. The main dashboard is at `/`. ### (Optional) Traefik DNS Challenge
## Configuration (`.env` File) *(Details about setting up DNS provider credentials for Let's Encrypt could go here if needed.)*
This file controls all the essential settings for your Docker Compose stack. Copy `.env.example` to `.env` and edit the values.
---
### **Core Settings (Required)**
These are fundamental for basic operation and permissions.
* `USER_ID`: The Linux user ID that the containers will run as. Find yours with `id -u`.
* *Default:* `1000`
* `GROUP_ID`: The Linux group ID that the containers will run as. Find yours with `id -g`.
* *Default:* `1000`
* **Note:** Using the correct IDs is crucial for file permissions, especially for accessing media files on the host.
* `TIMEZONE`: Your local timezone (e.g., `America/New_York`, `Europe/London`, `Asia/Manila`). Find yours from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
* *Default:* `America/New_York`
* `HOSTNAME`: **(Deprecated - Now derived)** The primary hostname used by Traefik for routing. This is now automatically constructed from `TAILSCALE_HOSTNAME` and `TAILSCALE_TAILNET_DOMAIN`. You generally don't need to set this directly unless overriding the default behavior.
* *Default:* `${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`
---
### **Host Paths (Required)**
Define where container data and configuration are stored on your host machine.
* `CONFIG_ROOT`: The base directory on your host where configuration files for each service will be stored. Using `.` stores them in subdirectories within the project folder.
* *Default:* `.`
* `DATA_ROOT`: The main directory on your host containing your media libraries (movies, TV shows, music, books).
* *Default:* `/mnt/data`
* `DOWNLOAD_ROOT`: The directory on your host where the download client (qBittorrent/SABnzbd) will store downloads in progress and completed files *before* they are imported by *arr apps.
* *Default:* `/mnt/data/torrents`
* **Hardlink Note:** For efficient storage (avoiding duplicate files), it's highly recommended that `DOWNLOAD_ROOT` is on the **same filesystem** as `DATA_ROOT` (e.g., `/mnt/data/torrents` is inside `/mnt/data`). This allows instant moves via hardlinks instead of slow copies.
---
### **Tailscale Access (Required)**
Controls secure remote access via Tailscale.
* `TAILSCALE_AUTHKEY`: **Required.** An authentication key from your Tailscale account. Generate one in the Tailscale Admin Console under Settings > Keys. You can use a reusable key or an ephemeral key (recommended for containers).
* *Default:* (None - **Must be set**)
* `TAILSCALE_HOSTNAME`: The desired hostname for this NAS within your Tailscale network.
* *Default:* `tailscale-nas`
* `TAILSCALE_TAILNET_DOMAIN`: **Required.** The domain of your Tailnet, including your Tailnet's unique name
* *Default:* `your-tailnet.ts.net` (**Must be set**)
* `TAILSCALE_TAGS`: Optional tags to apply to the Tailscale node (e.g., `tag:nas`).
* *Default:* `tag:nas`
* `ENABLE_FUNNEL_HTTPS`: Controls Tailscale's public accessibility.
* `true`: Enables Tailscale Funnel, making services accessible publicly via the Tailscale domain (`<TAILSCALE_HOSTNAME>.<your-tailnet-name>.ts.net`). Use with caution.
* `false`: Uses Tailscale Serve, making services accessible *only* to devices logged into your Tailnet. (Recommended)
* *Default:* `false`
---
### **Homepage Widgets (Optional)**
API keys needed *only* if you want to display real-time information from these services on the Homepage dashboard. Find the API keys within each application's settings (usually under Settings > General or Settings > Security).
* `SONARR_API_KEY`
* `RADARR_API_KEY`
* `LIDARR_API_KEY` (If Lidarr profile is enabled)
* `BAZARR_API_KEY`
* `PROWLARR_API_KEY`
* `JELLYFIN_API_KEY`
* `JELLYSEERR_API_KEY`
* `SABNZBD_API_KEY` (If SABnzbd profile is enabled)
* `ADGUARD_USERNAME` / `ADGUARD_PASSWORD` (If AdGuard Home profile is enabled)
* `CALIBRE_USERNAME` / `CALIBRE_PASSWORD` (If Calibre-Web profile is enabled)
---
### **Homepage Customization (Optional)**
Control the appearance and behavior of the Homepage dashboard.
* `HOMEPAGE_VAR_TITLE`: Title shown on the dashboard.
* *Default:* `Docker-Compose NAS`
* `HOMEPAGE_VAR_SEARCH_PROVIDER`: Default search engine. [See options](https://gethomepage.dev/en/widgets/search/).
* *Default:* `google`
* `HOMEPAGE_VAR_HEADER_STYLE`: Dashboard header style. [See options](https://gethomepage.dev/en/configs/settings/#header-style).
* *Default:* `boxed`
* `HOMEPAGE_VAR_WEATHER_CITY`, `_LAT`, `_LONG`, `_UNIT`: Configure the weather widget.
---
### **Download Client Settings**
Credentials for included download clients.
* `QBITTORRENT_USERNAME`: Username for qBittorrent Web UI.
* *Default:* `admin`
* `QBITTORRENT_PASSWORD`: Password for qBittorrent Web UI.
* *Default:* `adminadmin`
* **Note:** On first run, qBittorrent might generate a temporary password shown in its logs (`docker compose logs qbittorrent`). Log in with that, change the password in qBittorrent settings, and update this `.env` variable accordingly.
---
### **VPN Configuration (Example: PIA - Optional)**
These variables are specific to the example `thrnz/docker-wireguard-pia` VPN container used for qBittorrent in the default setup. If you use a different VPN provider or container, you'll need different variables. **If you don't use the VPN, you can ignore these.**
* `PIA_USER`: Private Internet Access username.
* `PIA_PASS`: Private Internet Access password.
* `PIA_LOCATION`: PIA server location code (e.g., `ca_montreal`, `us_east`). [See list](https://serverlist.piaservers.net/vpninfo/servers/v6).
* *Default:* `ca`
* `PIA_LOCAL_NETWORK`: Your local network CIDR (e.g., `192.168.1.0/24`). Allows local access to the qBittorrent UI when the VPN is active.
* *Default:* `192.168.0.0/16`
---
### **Traefik DNS Challenge (Optional)**
These settings are for enabling automatic HTTPS certificate generation via Let's Encrypt using the DNS-01 challenge method. **This is generally NOT needed** because Tailscale handles HTTPS termination by default in this setup. Only configure this if you have a specific reason to manage your own certificates via Traefik (e.g., accessing services without Tailscale).
* `DNS_CHALLENGE`: Set to `true` to enable DNS challenge.
* *Default:* `true` (Consider setting to `false` if using Tailscale for HTTPS)
* `DNS_CHALLENGE_PROVIDER`: Your DNS provider supported by Traefik/Lego (e.g., `cloudflare`, `godaddy`). [See providers](https://doc.traefik.io/traefik/https/acme/#providers).
* *Default:* `cloudflare`
* `LETS_ENCRYPT_EMAIL`: Your email address for Let's Encrypt notifications.
* `LETS_ENCRYPT_CA_SERVER`: Let's Encrypt server URL (use staging for testing).
* *Default:* `https://acme-v02.api.letsencrypt.org/directory` (Production)
* Provider-Specific Variables (e.g., `CLOUDFLARE_EMAIL`, `CLOUDFLARE_DNS_API_TOKEN`, `CLOUDFLARE_ZONE_API_TOKEN`): Credentials required by your chosen `DNS_CHALLENGE_PROVIDER`. Refer to Traefik documentation.
---
### **Compose Profiles & Files (Advanced)**
* `COMPOSE_PROFILES`: Comma-separated list of optional service profiles to enable (e.g., `lidarr,sabnzbd,adguardhome`). See [Optional Services](#optional-services).
* `COMPOSE_FILE`: Colon-separated list of compose files to use. Allows extending the base configuration.
* *Default:* `docker-compose.yml`
## Service Access ## Service Access
With the default Tailscale setup, services are securely accessible via HTTPS using your Tailscale node's name or IP, followed by the service path. Replace `<TAILSCALE_NODE>` with your Tailscale device name (e.g., `tailscale-nas.your-tailnet.ts.net`) or its Tailscale IP address. With the default Tailscale setup and Authelia enabled, services are securely accessible via HTTPS using your Tailscale node's name or IP. Authentication is controlled by the included `update-setup.sh` script.
* **Homepage:** `https://<TAILSCALE_NODE>/home` * **Login Portal:** `https://<TAILSCALE_NODE>/` (Redirects unauthenticated users here for secured services)
* **Sonarr:** `https://<TAILSCALE_NODE>/sonarr` * **Homepage Dashboard:** `https://<TAILSCALE_NODE>/home` (Requires login by default)
* **Radarr:** `https://<TAILSCALE_NODE>/radarr` * **Sonarr:** `https://<TAILSCALE_NODE>/sonarr` (Requires login by default)
* **Lidarr:** `https://<TAILSCALE_NODE>/lidarr` (If profile enabled) * **Radarr:** `https://<TAILSCALE_NODE>/radarr` (Requires login by default)
* **Bazarr:** `https://<TAILSCALE_NODE>/bazarr` * **qBittorrent:** `https://<TAILSCALE_NODE>/qbittorrent` (Requires login by default)
* **Jellyseerr:** `https://<TAILSCALE_NODE>/jellyseerr` * **Jellyfin:** `https://<TAILSCALE_NODE>/jellyfin` (Requires login by default)
* **Prowlarr:** `https://<TAILSCALE_NODE>/prowlarr` * ...and so on.
* **qBittorrent:** `https://<TAILSCALE_NODE>/qbittorrent`
* **SABnzbd:** `https://<TAILSCALE_NODE>/sabnzbd` (If profile enabled)
* **Jellyfin:** `https://<TAILSCALE_NODE>/jellyfin`
* **Calibre-Web:** `https://<TAILSCALE_NODE>/calibre` (If profile enabled)
* **AdGuard Home:** `http://<TAILSCALE_NODE_IP>:3000` (If profile enabled, access via IP/port initially)
* **Tandoor Recipes:** `https://<TAILSCALE_NODE>/recipes` (If profile enabled)
* **Joplin Server:** `https://<TAILSCALE_NODE>/joplin` (If profile enabled)
* **Home Assistant:** `http://<TAILSCALE_NODE_IP>:8123` (If profile enabled, access via IP/port initially)
* **Immich:** `http://<TAILSCALE_NODE_IP>:2283` (If profile enabled, access via IP/port initially)
**Note:** Replace `<TAILSCALE_NODE>` with your Tailscale device name (e.g., `tailscale-nas.your-tailnet.ts.net`) or its Tailscale IP address.
* `<TAILSCALE_NODE>` refers to the full Tailscale name (e.g., `tailscale-nas.your-tailnet.ts.net`).
* `<TAILSCALE_NODE_IP>` refers to the Tailscale IP address of the NAS. If you configure DNS for your `APP_HOSTNAME` variable to point to the Tailscale IP, you can use `https://<APP_HOSTNAME>/<service_path>`.
* Some services (AdGuard, HA, Immich) might require initial setup via their direct IP and port before Tailscale/Traefik routing is fully effective or configured within the application. Authentication for most services will be handled by Authelia (configured later).
### Setup Script Commands (`update-setup.sh`)
The `update-setup.sh` script provides various commands to manage your configuration. Run `./update-setup.sh help` to see all options.
**Core Setup & Updates:**
* `./update-setup.sh update-env`: Updates `.env` from `.env.example`, preserving existing values and highlighting new/deprecated keys.
* `./update-setup.sh update-authelia`: Updates `authelia/configuration.yml` from the example, applying domain settings from `.env` and attempting to preserve secrets (uses `yq` if available).
* `./update-setup.sh update-services`: Updates configurations for running *arr/qBittorrent/Bazarr containers (sets URL base, extracts API keys to `.env`). Restarts affected containers.
* `./update-setup.sh all`: Runs `update-env`, `update-authelia`, and `update-services` sequentially. Recommended for initial setup and major updates.
**Authelia Policy Management:**
* `./update-setup.sh manage-policies`: Starts an interactive menu to list or set Authelia access policies (`one_factor`, `two_factor`, `bypass`, `deny`) for specific services defined in `authelia/configuration.yml`.
* `./update-setup.sh list-policies`: Lists services defined in `authelia/configuration.yml` and their current access policy.
* `./update-setup.sh set-policy <service> <policy>`: Directly sets the Authelia access policy for the specified `<service>` to the given `<policy>` (e.g., `one_factor`, `two_factor`, `bypass`, `deny`).
> **Important:** After changing Authelia policies using `manage-policies` or `set-policy`, you **must** restart Authelia for the changes to take effect:
> ```bash
> docker compose restart authelia
> ```
**User & File Management:**
* `./update-setup.sh manage-accounts`: Starts an interactive tool to add or update users in `authelia/users_database.yml`. It generates password hashes and prompts for user details.
* `./update-setup.sh cleanup`: Interactively finds and deletes old backup files (`.bak`) created by the script. Allows keeping the most recent backup of each type.
**Help:**
* `./update-setup.sh help`: Displays the full list of commands and usage instructions.
### Managing Service Authentication (Authelia Policies)
Use the `update-setup.sh` script to easily control which services require Authelia login and what level of authentication is needed. This is done by managing *access control rules* within Authelia's configuration (`authelia/configuration.yml`).
See the `Authelia Policy Management` commands in the [Setup Script Commands](#setup-script-commands-update-setupsh) section above for details on how to list and set policies like `one_factor`, `two_factor`, `bypass`, or `deny` for each service.
## Optional Services ## Optional Services
@@ -250,6 +437,20 @@ Available Profiles:
## Troubleshooting ## Troubleshooting
### Middleware Not Found Errors
If you see error messages like `middleware "authelia-auth@docker" does not exist` in the Traefik logs, it could be due to one of these issues:
1. **Docker Network Issue:** The Traefik configuration has been updated to fix network discovery issues when running in Tailscale's network namespace. If you're still seeing this error, try restarting the stack with:
```bash
docker compose down
docker compose up -d
```
2. **Authentication Variable Missing:** Ensure you have properly configured the `AUTH_*` variables in your `.env` file for the services you want to control. If not specified, most services default to requiring authentication.
3. **Docker Socket Permissions:** Make sure Traefik can access the Docker socket. See the [SELinux Socket Permissions](#selinux-socket-permissions-docker) section below for more details.
### SELinux Socket Permissions (Docker) ### SELinux Socket Permissions (Docker)
If you are running Docker on a host with SELinux enabled (like Fedora, CentOS, RHEL) and services like Traefik, Watchtower, or Autoheal fail with "permission denied" errors when trying to access `/var/run/docker.sock`: If you are running Docker on a host with SELinux enabled (like Fedora, CentOS, RHEL) and services like Traefik, Watchtower, or Autoheal fail with "permission denied" errors when trying to access `/var/run/docker.sock`:
@@ -270,6 +471,45 @@ If you are running Docker on a host with SELinux enabled (like Fedora, CentOS, R
``` ```
This allows the specific actions that were being denied. You might need to repeat this if different denials appear after applying the first policy. This allows the specific actions that were being denied. You might need to repeat this if different denials appear after applying the first policy.
### Authelia v4.38+ Configuration
Authelia v4.38+ introduces significant changes to its configuration structure, particularly for session domains and authentication flows. The setup in this repository has been carefully configured to work with these changes:
1. **Domain Configuration**:
- You must use your specific Tailnet domain (e.g., `example.ts.net`) for cookies, not just `ts.net`
- The domain `ts.net` is part of the [Public Suffix List](https://publicsuffix.org/), which means browsers restrict cookies on it for security reasons
- Authelia will refuse to start if you try to use a domain from this list
2. **Required Secret Variables**: You must set these four variables in your `.env` file:
- `AUTHELIA_JWT_SECRET`: Used for password reset tokens
- `AUTHELIA_SESSION_SECRET`: Used for session cookie encryption
- `AUTHELIA_STORAGE_ENCRYPTION_KEY`: Used for database encryption
- `AUTHELIA_REDIS_PASSWORD`: Used for Redis authentication
Generate strong random values for these with: `openssl rand -hex 32`
3. **Automatic Domain Setup**: The `update-setup.sh` script automatically:
- Uses your specific Tailnet domain (e.g., `example.ts.net`) from your `.env` file
- Configures cookie domains properly to avoid Public Suffix List issues
- Sets up proper access control rules for both your domain and its subdomains
4. **File Permissions**: The Authelia container runs with your user ID and group ID, preventing permission issues when managing the configuration files with git or other tools.
If you encounter any of these common errors, running the setup script should resolve them:
```
error: option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list
error: option 'authelia_url' does not share a cookie scope with domain
error: can't be specified at the same time: option 'domain' and option 'cookies'
configuration key 'jwt_secret' is deprecated in 4.38.0
```
After making changes to the configuration, restart Authelia with:
```bash
docker compose restart authelia
```
See the [Authelia documentation](https://www.authelia.com/configuration/session/introduction/) for more details on the v4.38+ configuration structure.
### Tailscale Issues ### Tailscale Issues
* **Authentication:** Ensure your `TAILSCALE_AUTHKEY` in `.env` is valid and hasn't expired (especially if using ephemeral keys). Check the `tailscale` container logs (`docker compose logs tailscale`) for authentication errors. * **Authentication:** Ensure your `TAILSCALE_AUTHKEY` in `.env` is valid and hasn't expired (especially if using ephemeral keys). Check the `tailscale` container logs (`docker compose logs tailscale`) for authentication errors.

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

@@ -13,18 +13,57 @@ services:
- --experimental.plugins.rewrite-body.version=v1.2.0 - --experimental.plugins.rewrite-body.version=v1.2.0
- --experimental.plugins.rewriteHeaders.modulename=github.com/XciD/traefik-plugin-rewrite-headers - --experimental.plugins.rewriteHeaders.modulename=github.com/XciD/traefik-plugin-rewrite-headers
- --experimental.plugins.rewriteHeaders.version=v0.0.3 - --experimental.plugins.rewriteHeaders.version=v0.0.3
network_mode: service:tailscale # Add this line - --providers.docker.network=docker-compose-nas
# ports: # Remove this section - --providers.docker.endpoint=unix:///var/run/docker.sock
# - "80:80" network_mode: service:tailscale
# - "443:443"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
# extra_hosts: # Remove this section
# - host.docker.internal:172.17.0.1
healthcheck: healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"] test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s interval: 30s
retries: 10 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: sonarr:
image: lscr.io/linuxserver/sonarr image: lscr.io/linuxserver/sonarr
container_name: sonarr container_name: sonarr
@@ -42,8 +81,9 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`) - traefik.http.routers.sonarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/sonarr`) # Added Host check
- traefik.http.routers.sonarr.entrypoints=web - 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 - traefik.http.services.sonarr.loadbalancer.server.port=8989
- homepage.group=Media - homepage.group=Media
- homepage.name=Sonarr - homepage.name=Sonarr
@@ -71,8 +111,9 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.radarr.rule=PathPrefix(`/radarr`) - traefik.http.routers.radarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/radarr`) # Added Host check
- traefik.http.routers.radarr.entrypoints=web - 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 - traefik.http.services.radarr.loadbalancer.server.port=7878
- homepage.group=Media - homepage.group=Media
- homepage.name=Radarr - homepage.name=Radarr
@@ -100,8 +141,9 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.lidarr.rule=PathPrefix(`/lidarr`) - traefik.http.routers.lidarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/lidarr`) # Added Host check
- traefik.http.routers.lidarr.entrypoints=web - 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 - traefik.http.services.lidarr.loadbalancer.server.port=8686
- homepage.group=Media - homepage.group=Media
- homepage.name=Lidarr - homepage.name=Lidarr
@@ -131,8 +173,9 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.bazarr.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/bazarr`) - traefik.http.routers.bazarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/bazarr`) # Added Host check
- traefik.http.routers.bazarr.entrypoints=web - 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 - traefik.http.services.bazarr.loadbalancer.server.port=6767
- homepage.group=Download - homepage.group=Download
- homepage.name=Bazarr - homepage.name=Bazarr
@@ -165,10 +208,10 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.jellyseerr.rule=PathPrefix(`/jellyseerr`) - traefik.http.routers.jellyseerr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/jellyseerr`) # Added Host check
- traefik.http.routers.jellyseerr.entrypoints=web - traefik.http.routers.jellyseerr.entrypoints=web
- traefik.http.services.jellyseerr.loadbalancer.server.port=5055 - 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-stripprefix.stripPrefix.prefixes=/jellyseerr
- traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].header=location - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].header=location
- traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].regex=^/(.+)$ - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].regex=^/(.+)$
@@ -239,8 +282,9 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.prowlarr.rule=PathPrefix(`/prowlarr`) - traefik.http.routers.prowlarr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/prowlarr`) # Added Host check
- traefik.http.routers.prowlarr.entrypoints=web - 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 - traefik.http.services.prowlarr.loadbalancer.server.port=9696
- homepage.group=Download - homepage.group=Download
- homepage.name=Prowlarr - homepage.name=Prowlarr
@@ -262,8 +306,9 @@ services:
- TZ=${TIMEZONE} - TZ=${TIMEZONE}
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.flaresolverr.rule=PathPrefix(`/flaresolverr`) - traefik.http.routers.flaresolverr.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/flaresolverr`) # Added Host check
- traefik.http.routers.flaresolverr.entrypoints=web - 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 - traefik.http.services.flaresolverr.loadbalancer.server.port=8191
profiles: profiles:
- flaresolverr - flaresolverr
@@ -281,25 +326,20 @@ services:
- ${DOWNLOAD_ROOT}:/data/torrents:Z - ${DOWNLOAD_ROOT}:/data/torrents:Z
restart: always restart: always
healthcheck: 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: test:
["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"] ["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"]
interval: 30s interval: 30s
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.qbittorrent.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/qbittorrent`) - traefik.http.routers.qbittorrent.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/qbittorrent`) # Added Host check
- traefik.http.routers.qbittorrent.entrypoints=web - traefik.http.routers.qbittorrent.entrypoints=web
- traefik.http.services.qbittorrent.loadbalancer.server.port=8080 - traefik.http.services.qbittorrent.loadbalancer.server.port=8080
- traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix - traefik.http.routers.qbittorrent.middlewares=https-proto@docker,qbittorrent-strip-slash,qbittorrent-stripprefix,authelia-auth@docker
# https://github.com/qbittorrent/qBittorrent/issues/5693#issuecomment-552146296
- traefik.http.middlewares.qbittorrent-stripprefix.stripPrefix.prefixes=/qbittorrent - 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.regex=(^.*\/qbittorrent$$)
- traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/ - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/
- traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.permanent=false - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.permanent=false
#- com.centurylinklabs.watchtower.depends-on=/vpn
- homepage.group=Download - homepage.group=Download
- homepage.name=qBittorrent - homepage.name=qBittorrent
- homepage.icon=qbittorrent.png - homepage.icon=qbittorrent.png
@@ -338,8 +378,9 @@ services:
restart: always restart: always
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.sabnzbd.rule=PathPrefix(`/sabnzbd`) # Simplified rule - traefik.http.routers.sabnzbd.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/sabnzbd`) # Added Host check
- traefik.http.routers.sabnzbd.entrypoints=web - 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 - traefik.http.services.sabnzbd.loadbalancer.server.port=8080
- homepage.group=Download - homepage.group=Download
- homepage.name=Sabnzbd - homepage.name=Sabnzbd
@@ -359,7 +400,7 @@ services:
- PUID=${USER_ID} - PUID=${USER_ID}
- PGID=${GROUP_ID} - PGID=${GROUP_ID}
- TZ=${TIMEZONE} - TZ=${TIMEZONE}
- JELLYFIN_PublishedServerUrl=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/jellyfin - JELLYFIN_PublishedServerUrl=https://${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/jellyfin
volumes: volumes:
- ${CONFIG_ROOT:-.}/jellyfin:/config:Z - ${CONFIG_ROOT:-.}/jellyfin:/config:Z
- ${DATA_ROOT}:/data:Z - ${DATA_ROOT}:/data:Z
@@ -373,8 +414,9 @@ services:
retries: 10 retries: 10
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.jellyfin.rule=PathPrefix(`/jellyfin`) - traefik.http.routers.jellyfin.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/jellyfin`) # Added Host check
- traefik.http.routers.jellyfin.entrypoints=web - 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 - traefik.http.services.jellyfin.loadbalancer.server.port=8096
- homepage.group=Media - homepage.group=Media
- homepage.name=Jellyfin - homepage.name=Jellyfin
@@ -403,8 +445,8 @@ services:
- traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Scheme=https - 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-headers.headers.customRequestHeaders.X-Script-Name=/calibre
- traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre - traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre
- traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex - traefik.http.routers.calibre.middlewares=https-proto@docker,calibre-headers,calibre-stripprefixregex,authelia-auth@docker
- traefik.http.routers.calibre.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/calibre`) - traefik.http.routers.calibre.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/calibre`) # Added Host check
- traefik.http.routers.calibre.entrypoints=web - traefik.http.routers.calibre.entrypoints=web
- traefik.http.services.calibre.loadbalancer.server.port=8083 - traefik.http.services.calibre.loadbalancer.server.port=8083
- homepage.group=Media - homepage.group=Media
@@ -467,7 +509,6 @@ services:
- HOMEPAGE_VAR_WEATHER_LONG=${HOMEPAGE_VAR_WEATHER_LONG} - HOMEPAGE_VAR_WEATHER_LONG=${HOMEPAGE_VAR_WEATHER_LONG}
- HOMEPAGE_VAR_WEATHER_TIME=${TIMEZONE} - HOMEPAGE_VAR_WEATHER_TIME=${TIMEZONE}
- HOMEPAGE_VAR_WEATHER_UNIT=${HOMEPAGE_VAR_WEATHER_UNIT} - HOMEPAGE_VAR_WEATHER_UNIT=${HOMEPAGE_VAR_WEATHER_UNIT}
# Explicitly allow the hostname constructed from Tailscale variables
- HOMEPAGE_ALLOWED_HOSTS=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN} - HOMEPAGE_ALLOWED_HOSTS=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}
volumes: volumes:
- ${CONFIG_ROOT:-.}/homepage:/app/config:Z - ${CONFIG_ROOT:-.}/homepage:/app/config:Z
@@ -478,11 +519,17 @@ services:
[sh, -c, "cp -n /app/config/tpl/*.yaml /app/config && node server.js"] [sh, -c, "cp -n /app/config/tpl/*.yaml /app/config && node server.js"]
labels: labels:
- traefik.enable=true - traefik.enable=true
# Change path to /home and use specific Tailscale host - traefik.http.routers.homepage.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/`) # Changed rule to root
- traefik.http.routers.homepage.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/home`)
- traefik.http.routers.homepage.entrypoints=web - traefik.http.routers.homepage.entrypoints=web
# Authelia middleware will be added in a later commit # - traefik.http.routers.homepage.priority=10 # Removed priority
- traefik.http.services.homepage.loadbalancer.server.port=3000 # 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: watchtower:
image: ghcr.io/containrrr/watchtower:latest image: ghcr.io/containrrr/watchtower:latest
container_name: watchtower container_name: watchtower
@@ -502,23 +549,22 @@ services:
tailscale: tailscale:
image: tailscale/tailscale:latest image: tailscale/tailscale:latest
container_name: tailscale container_name: tailscale
hostname: ${TAILSCALE_HOSTNAME:-tailscale-nas} # Hostname for Tailscale access hostname: ${TAILSCALE_HOSTNAME:-tailscale-nas}
environment: environment:
TS_AUTHKEY: ${TAILSCALE_AUTHKEY} # Needs to be set in .env TS_AUTHKEY: ${TAILSCALE_AUTHKEY}
TS_EXTRA_ARGS: "--advertise-tags=${TAILSCALE_TAGS:-tag:nas}" # Keep tags if desired TS_EXTRA_ARGS: "--advertise-tags=${TAILSCALE_TAGS:-tag:nas}"
TS_STATE_DIR: "/var/lib/tailscale" TS_STATE_DIR: "/var/lib/tailscale"
TS_USERSPACE: "false" TS_USERSPACE: "false"
# Switch to enable Funnel (public access) or Serve (Tailnet only)
ENABLE_FUNNEL_HTTPS: ${ENABLE_FUNNEL_HTTPS:-false} ENABLE_FUNNEL_HTTPS: ${ENABLE_FUNNEL_HTTPS:-false}
volumes: volumes:
- ${CONFIG_ROOT:-.}/tailscale/state:/var/lib/tailscale:Z # Persist state - ${CONFIG_ROOT:-.}/tailscale/state:/var/lib/tailscale:Z
- /var/run/docker.sock:/var/run/docker.sock # Optional, keep if needed - /var/run/docker.sock:/var/run/docker.sock
devices: devices:
- /dev/net/tun:/dev/net/tun - /dev/net/tun:/dev/net/tun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
- NET_RAW - NET_RAW
extra_hosts: # Add this section extra_hosts:
- host.docker.internal:172.17.0.1 - host.docker.internal:172.17.0.1
restart: always restart: always
command: command:
@@ -542,8 +588,6 @@ services:
done done
echo " Tailscaled is running." echo " Tailscaled is running."
# --- Start Tailscale Funnel/Serve ---
# Check the ENABLE_FUNNEL_HTTPS variable
if [ "${ENABLE_FUNNEL_HTTPS}" = "true" ]; then if [ "${ENABLE_FUNNEL_HTTPS}" = "true" ]; then
echo "ENABLE_FUNNEL_HTTPS is true. Setting up Funnel -> http://localhost:80..." echo "ENABLE_FUNNEL_HTTPS is true. Setting up Funnel -> http://localhost:80..."
tailscale funnel --bg http://localhost:80 tailscale funnel --bg http://localhost:80
@@ -553,10 +597,9 @@ services:
tailscale serve --bg http://localhost:80 tailscale serve --bg http://localhost:80
echo "Tailscale Serve configured." echo "Tailscale Serve configured."
fi fi
# --- End Tailscale Funnel/Serve ---
echo "Tailscale forwarding configured. Container will remain running." echo "Tailscale forwarding configured. Container will remain running."
wait # Wait indefinitely for background processes wait
networks: networks:
default: 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

936
update-setup.sh Executable file
View File

@@ -0,0 +1,936 @@
#!/bin/bash
# Combined update script for docker-compose-nas
# - Updates .env file from .env.example while preserving values
# - Updates Authelia configuration from example file
# - Configures services with correct paths and API keys
# - Manages Authelia accounts
# - Controls service authentication requirements
# Created: April 26, 2025
set -e
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color
# Files
ENV_FILE=".env"
ENV_EXAMPLE=".env.example"
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
ENV_BACKUP=".env.${TIMESTAMP}.bak"
AUTHELIA_CONFIG="authelia/configuration.yml"
AUTHELIA_CONFIG_EXAMPLE="authelia/configuration.example.yml"
AUTHELIA_CONFIG_BACKUP="authelia/configuration.${TIMESTAMP}.bak"
COMPOSE_FILE="docker-compose.yml"
COMPOSE_BACKUP="docker-compose.${TIMESTAMP}.bak"
# Check if yq is installed
check_yq() {
if ! command -v yq &> /dev/null; then
echo -e "${YELLOW}Warning: 'yq' is not installed. While not required, it provides better YAML handling.${NC}"
echo -e "${YELLOW}Installation instructions: https://github.com/mikefarah/yq#install${NC}"
return 1
fi
return 0
}
# Print section header
print_header() {
echo -e "\n${CYAN}${BOLD}$1${NC}"
echo -e "${CYAN}$(printf '=%.0s' $(seq 1 ${#1}))${NC}"
}
# Check if a file exists
check_file() {
if [ ! -f "$1" ]; then
echo -e "${RED}Error: $1 doesn't exist${NC}"
return 1
fi
return 0
}
# Function to create a backup
create_backup() {
echo -e "${BLUE}Creating backup of $1 as $2...${NC}"
cp "$1" "$2"
}
##################################################
# PART 1: Update .env file from .env.example
##################################################
update_env_file() {
print_header "Environment File Update Tool"
# Check if files exist
if [ ! -f "$ENV_FILE" ]; then
echo -e "${RED}Error: $ENV_FILE doesn't exist${NC}"
echo -e "Creating a new $ENV_FILE from $ENV_EXAMPLE"
cp "$ENV_EXAMPLE" "$ENV_FILE"
echo -e "${GREEN}Done! Please review and fill in required values in $ENV_FILE${NC}"
return 0
fi
if ! check_file "$ENV_EXAMPLE"; then
return 1
fi
echo -e "${BLUE}This will update your $ENV_FILE based on the structure in $ENV_EXAMPLE${NC}"
echo -e "${BLUE}Your existing values will be preserved where possible${NC}"
echo -e "${BLUE}Backup will be created as: $ENV_BACKUP${NC}"
echo -e "${YELLOW}Continue? [y/N]:${NC}"
read -r answer
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
echo -e "${RED}Environment update cancelled.${NC}"
return 0
fi
# Create backup of current .env
create_backup "$ENV_FILE" "$ENV_BACKUP"
# Store current env values
echo -e "${BLUE}Reading current environment values...${NC}"
declare -A current_values
declare -A current_keys_present
while IFS='=' read -r key value; do
# Skip comments and empty lines
if [[ ! "$key" =~ ^#.*$ ]] && [[ ! -z "$key" ]]; then
# Clean up any comments after the value
value=$(echo "$value" | sed 's/[[:space:]]*#.*$//')
# Trim leading/trailing whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
# Store in associative array if key is not empty
if [[ ! -z "$key" ]]; then
current_values["$key"]="$value"
# Track that this key existed in original file, regardless of value
current_keys_present["$key"]=1
fi
fi
done < "$ENV_FILE"
# Create new env file from example
echo -e "${BLUE}Creating new $ENV_FILE from $ENV_EXAMPLE...${NC}"
cp "$ENV_EXAMPLE" "$ENV_FILE.new"
# Track which keys from the current env have been used
declare -A used_keys
# Track new keys that need attention
new_keys=()
# Track keys with special warnings
special_keys=()
# Process the template and fill in values from current env
while IFS= read -r line; do
if [[ "$line" =~ ^([A-Za-z0-9_]+)=(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
default_value="${BASH_REMATCH[2]}"
# Mark the key as used if it exists in the original file
if [[ -n "${current_keys_present[$key]}" ]]; then
used_keys["$key"]=1
# Replace the line with the current value if one exists
if [[ -n "${current_values[$key]}" ]]; then
sed -i "s|^$key=.*$|$key=${current_values[$key]}|" "$ENV_FILE.new"
fi
# If key doesn't exist in original file and has empty/placeholder value
elif [[ -z "$default_value" ]] || [[ "$default_value" == '""' ]] || [[ "$default_value" == "''" ]]; then
new_keys+=("$key")
# Special attention for Authelia keys
if [[ "$key" == AUTHELIA_*_SECRET* ]] || [[ "$key" == AUTHELIA_*_KEY* ]]; then
special_keys+=("$key")
fi
fi
fi
done < "$ENV_FILE.new"
# Create section for unused/deprecated keys at the bottom of the file
echo -e "\n\n# --- DEPRECATED OR UNUSED KEYS (Kept for Reference) ---" >> "$ENV_FILE.new"
echo -e "# Keys below were in your original .env but aren't in the current .env.example" >> "$ENV_FILE.new"
echo -e "# They may be deprecated or renamed. Review and remove if no longer needed\n" >> "$ENV_FILE.new"
unused_keys_count=0
for key in "${!current_values[@]}"; do
if [[ -z "${used_keys[$key]}" ]]; then
echo "$key=${current_values[$key]} # DEPRECATED/UNUSED - Review" >> "$ENV_FILE.new"
unused_keys_count=$((unused_keys_count + 1))
fi
done
# Replace the old file with the new one
mv "$ENV_FILE.new" "$ENV_FILE"
# Generate summary
echo -e "\n${GREEN}${BOLD}Environment Update Complete!${NC}"
echo -e "${BLUE}Summary:${NC}"
echo -e " - ${CYAN}Original config backed up to: $ENV_BACKUP${NC}"
echo -e " - ${CYAN}Updated .env structure to match .env.example${NC}"
echo -e " - ${CYAN}Preserved ${#used_keys[@]} existing values${NC}"
if [[ $unused_keys_count -gt 0 ]]; then
echo -e " - ${YELLOW}Found $unused_keys_count deprecated/unused keys${NC}"
echo -e " ${YELLOW}These have been moved to the bottom of the file with warnings${NC}"
fi
if [[ ${#new_keys[@]} -gt 0 ]]; then
echo -e "\n${YELLOW}${BOLD}NEW KEYS NEEDING ATTENTION:${NC}"
echo -e "${YELLOW}The following keys are new and may need values set:${NC}"
for key in "${new_keys[@]}"; do
echo -e " - ${MAGENTA}$key${NC}"
done
fi
if [[ ${#special_keys[@]} -gt 0 ]]; then
echo -e "\n${RED}${BOLD}IMPORTANT SECURITY KEYS:${NC}"
echo -e "${RED}The following keys require secure values:${NC}"
for key in "${special_keys[@]}"; do
echo -e " - ${MAGENTA}$key${NC}"
# Specific advice for Authelia keys
if [[ "$key" == AUTHELIA_*_SECRET* ]] || [[ "$key" == AUTHELIA_*_KEY* ]]; then
echo -e " ${CYAN}Generate with: ${GREEN}openssl rand -hex 32${NC}"
fi
done
fi
echo -e "\n${BLUE}Review your updated $ENV_FILE file and adjust any values as needed.${NC}"
}
##################################################
# PART 2: Update Authelia configuration
##################################################
update_authelia_config() {
print_header "Authelia Configuration Update Tool"
# Check if files exist
if ! check_file "$AUTHELIA_CONFIG_EXAMPLE"; then
echo -e "${RED}Error: Example configuration file '$AUTHELIA_CONFIG_EXAMPLE' doesn't exist${NC}"
return 1
fi
if ! check_file "$ENV_FILE"; then
echo -e "${RED}Error: Environment file '$ENV_FILE' doesn't exist. Cannot retrieve domain settings.${NC}"
return 1
fi
# Get the tailnet domain and hostname from .env
local TAILNET_DOMAIN=$(grep -oP "^TAILSCALE_TAILNET_DOMAIN=\K.*" "$ENV_FILE" | tr -d '"' | tr -d "'")
local TAILSCALE_HOSTNAME=$(grep -oP "^TAILSCALE_HOSTNAME=\K.*" "$ENV_FILE" | tr -d '"' | tr -d "'")
local FULL_HOSTNAME="${TAILSCALE_HOSTNAME}.${TAILNET_DOMAIN}"
local WILDCARD_DOMAIN="*.${TAILNET_DOMAIN}"
if [ -z "$TAILNET_DOMAIN" ] || [ -z "$TAILSCALE_HOSTNAME" ]; then
echo -e "${RED}Error: Could not read TAILSCALE_TAILNET_DOMAIN or TAILSCALE_HOSTNAME from $ENV_FILE${NC}"
return 1
fi
# If config file doesn't exist, create it from example
if [ ! -f "$AUTHELIA_CONFIG" ]; then
echo -e "${YELLOW}Authelia configuration file '$AUTHELIA_CONFIG' doesn't exist, creating from example...${NC}"
cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG"
echo -e "${GREEN}Created new Authelia configuration file.${NC}"
# Proceed to update the newly created file
else
echo -e "${BLUE}This will update your Authelia configuration '$AUTHELIA_CONFIG' based on '$AUTHELIA_CONFIG_EXAMPLE'${NC}"
echo -e "${BLUE}Your Tailscale domain settings from '$ENV_FILE' will be applied.${NC}"
echo -e "${BLUE}Backup will be created as: $AUTHELIA_CONFIG_BACKUP${NC}"
echo -e "${YELLOW}Continue? [y/N]:${NC}"
read -r answer
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
echo -e "${RED}Authelia config update cancelled.${NC}"
return 0
fi
# Create backup of current config only if it exists
create_backup "$AUTHELIA_CONFIG" "$AUTHELIA_CONFIG_BACKUP"
fi
# Check for yq
if check_yq; then
echo -e "${BLUE}Using 'yq' to update Authelia configuration...${NC}"
# Create a temporary file from the example
local TEMP_CONFIG="${AUTHELIA_CONFIG}.tmp"
cp "$AUTHELIA_CONFIG_EXAMPLE" "$TEMP_CONFIG"
# Preserve specific existing values if the original config exists
if [ -f "$AUTHELIA_CONFIG_BACKUP" ]; then # Use backup as source of truth for existing values
echo -e "${BLUE}Attempting to preserve existing secrets and notifier settings...${NC}"
local existing_jwt_secret=$(yq e '.identity_validation.reset_password.jwt_secret // ""' "$AUTHELIA_CONFIG_BACKUP")
local existing_session_secret=$(yq e '.session.secret // ""' "$AUTHELIA_CONFIG_BACKUP")
local existing_storage_key=$(yq e '.storage.encryption_key // ""' "$AUTHELIA_CONFIG_BACKUP")
local existing_redis_pass=$(yq e '.session.redis.password // ""' "$AUTHELIA_CONFIG_BACKUP")
local existing_notifier=$(yq e '.notifier // ""' "$AUTHELIA_CONFIG_BACKUP")
# Update secrets in temp file if they existed in the backup
if [[ -n "$existing_jwt_secret" && "$existing_jwt_secret" != '""' && "$existing_jwt_secret" != "null" ]]; then
yq e -i '.identity_validation.reset_password.jwt_secret = strenv(existing_jwt_secret)' --env existing_jwt_secret="$existing_jwt_secret" "$TEMP_CONFIG"
##################################################
# PART 5: Authelia Account Management
##################################################
# PART 4: Authelia Policy Management
##################################################
# Get the current policy for a service from Authelia config
# Usage: get_authelia_policy <service_name> <config_file>
# Output: policy (e.g., one_factor, bypass), not_found, or error
get_authelia_policy() {
local service=$1
local config_file=$2
if ! check_file "$config_file"; then
echo "error: config file not found"
return 1
fi
# Use yq if available for more reliable YAML parsing
if check_yq; then
# Find the rule matching the path_regex for the service
# Note: This assumes path_regex is unique enough for the service (e.g., ^/service.*)
local policy=$(yq e ".access_control.rules[] | select(.path_regex == \"^/${service}.*\") | .policy" "$config_file" 2>/dev/null)
if [ -n "$policy" ] && [ "$policy" != "null" ]; then
echo "$policy"
else
# Check if a rule exists for the service path at all
local rule_exists=$(yq e ".access_control.rules[] | select(.path_regex == \"^/${service}.*\")" "$config_file" 2>/dev/null)
if [ -n "$rule_exists" ] && [ "$rule_exists" != "null" ]; then
echo "error: policy not found in rule" # Rule exists but policy couldn't be read
else
echo "not_found" # No rule found for this service path
fi
fi
else
# Fall back to grep if yq isn't available (less reliable)
# This is fragile and depends heavily on formatting
local policy_line=$(grep -A 1 "path_regex: '^/${service}.*'" "$config_file" | grep "policy:")
if [ -n "$policy_line" ]; then
echo "$policy_line" | awk '{print $2}'
else
echo "not_found"
fi
fi
return 0
}
# Set the policy for a service in Authelia config
# Usage: set_authelia_policy <service_name> <policy> <config_file>
set_authelia_policy() {
local service=$1
local policy=$2
local config_file=$3
local backup_file="${config_file}.${TIMESTAMP}.bak"
if ! check_file "$config_file"; then
echo -e "${RED}Error: Authelia configuration file '$config_file' not found.${NC}"
return 1
fi
if [[ "$policy" != "one_factor" && "$policy" != "two_factor" && "$policy" != "deny" && "$policy" != "bypass" ]]; then
echo -e "${RED}Error: Invalid policy '$policy'. Must be one of: one_factor, two_factor, deny, bypass.${NC}"
return 1
fi
echo -e "${BLUE}Setting policy for service '$service' to '$policy' in '$config_file'...${NC}"
# Create backup if it doesn't exist for this run
if [ ! -f "$backup_file" ]; then
create_backup "$config_file" "$backup_file"
fi
# Use yq if available
if check_yq; then
# Check if the rule exists first using the path_regex
local rule_index=$(yq e ".access_control.rules | map(.path_regex == \"^/${service}.*\") | indexOf(true)" "$config_file" 2>/dev/null)
if [ "$rule_index" == "-1" ] || [ -z "$rule_index" ]; then
echo -e "${YELLOW}Warning: No rule found for service path '^/${service}.*' in '$config_file'.${NC}"
echo -e "${YELLOW}Attempting to add a new rule...${NC}"
# Get the tailnet domain from .env for the new rule
local TAILNET_DOMAIN=$(grep -oP "^TAILSCALE_TAILNET_DOMAIN=\K.*" "$ENV_FILE" | tr -d '"' | tr -d "'")
if [ -z "$TAILNET_DOMAIN" ]; then
echo -e "${RED}Error: Could not read TAILSCALE_TAILNET_DOMAIN from $ENV_FILE. Cannot add rule.${NC}"
return 1
fi
local WILDCARD_DOMAIN="*.${TAILNET_DOMAIN}"
# Add the new rule to the access_control.rules array
# Places it before the generic domain rule if it exists, otherwise at the end
# This assumes a generic rule like "- domain: '*.domain.tld'" exists near the end
yq e -i ".access_control.rules |= select(.domain != \"${WILDCARD_DOMAIN}\" or .path_regex != null) + [{\"domain\": \"${WILDCARD_DOMAIN}\", \"path_regex\": \"^/${service}.*\", \"policy\": \"${policy}\"}] + select(.domain == \"${WILDCARD_DOMAIN}\" and .path_regex == null)" "$config_file"
if [ $? -eq 0 ]; then
echo -e "${GREEN}Added new rule for '$service' with policy '$policy'.${NC}"
return 0
else
echo -e "${RED}Error: Failed to add new rule for '$service' using yq.${NC}"
return 1
fi
else
# Rule exists, update the policy at the found index
yq e -i "(.access_control.rules[$rule_index].policy) = \"$policy\"" "$config_file"
if [ $? -eq 0 ]; then
echo -e "${GREEN}Policy for '$service' updated to '$policy'.${NC}"
return 0
else
echo -e "${RED}Error: Failed to update policy for '$service' using yq.${NC}"
return 1
fi
fi
else
# Fallback to sed (much less reliable, especially for adding rules)
echo -e "${YELLOW}Warning: 'yq' not found. Using 'sed' which is less reliable for YAML manipulation.${NC}"
# Check if rule exists (simple grep)
if grep -q "path_regex: '^/${service}.*'" "$config_file"; then
# Attempt to find the line number of path_regex and update the policy line below it
local line_num=$(grep -n "path_regex: '^/${service}.*'" "$config_file" | head -n 1 | cut -d: -f1) # Use head -n 1 just in case
if [ -n "$line_num" ]; then
# Assuming policy is the next line (fragile!)
local policy_line_num=$((line_num + 1))
# Check if the next line actually contains 'policy:'
if sed -n "${policy_line_num}p" "$config_file" | grep -q "policy:"; then
sed -i "${policy_line_num}s/policy:.*/policy: $policy/" "$config_file"
if [ $? -eq 0 ]; then
echo -e "${GREEN}Policy for '$service' updated to '$policy' (using sed).${NC}"
return 0
else
echo -e "${RED}Error: Failed to update policy line using sed.${NC}"
return 1
fi
else
echo -e "${RED}Error: Could not reliably find policy line for '$service' using sed (expected on line $policy_line_num).${NC}"
return 1
fi
else
echo -e "${RED}Error: Could not find line number for service '$service' using sed.${NC}"
return 1
fi
else
echo -e "${RED}Error: Rule for service '$service' not found. Cannot add rule using sed.${NC}"
return 1
fi
fi
}
# List services and their Authelia policy status
list_authelia_services() {
print_header "Authelia Service Policy Status"
if ! check_file "$AUTHELIA_CONFIG"; then
echo -e "${RED}Error: Authelia configuration file '$AUTHELIA_CONFIG' not found.${NC}"
return 1
fi
echo -e "${BLUE}Checking service policies in $AUTHELIA_CONFIG...${NC}"
echo -e "${CYAN}SERVICE\t\tPOLICY${NC}"
echo -e "${CYAN}-------\t\t------${NC}"
local service_count=0
local processed_services="" # Track processed services
# Use yq to get all rules with path_regex if available
if check_yq; then
# Extract path_regex and policy for rules that have path_regex
local rules_data=$(yq e '.access_control.rules[] | select(has("path_regex")) | {"path": .path_regex, "policy": .policy}' "$AUTHELIA_CONFIG")
# Process each rule found
while IFS= read -r line; do
# Extract service name from path_regex (e.g., "^/sonarr.*" -> "sonarr")
local path_regex=$(echo "$line" | grep -oP 'path: \K.*' | tr -d '"' | tr -d "'")
local policy=$(echo "$line" | grep -oP 'policy: \K.*' | tr -d '"' | tr -d "'")
if [[ "$path_regex" =~ ^\^/([a-zA-Z0-9_-]+)\.\* ]]; then
local service="${BASH_REMATCH[1]}"
# Skip duplicates if multiple rules somehow match the same pattern start
if [[ "$processed_services" == *"$service"* ]]; then
continue
fi
processed_services="$processed_services $service"
printf "${BOLD}%-20s${NC}" "$service"
case "$policy" in
"one_factor"|"two_factor")
echo -e "${GREEN}${policy}${NC}"
;;
"bypass")
echo -e "${YELLOW}${policy}${NC}"
;;
"deny")
echo -e "${RED}${policy}${NC}"
;;
*)
echo -e "${MAGENTA}Unknown ($policy)${NC}"
;;
esac
service_count=$((service_count + 1))
fi
# Use yq -N to prevent splitting lines with spaces
done <<< "$(echo "$rules_data" | yq -N e '.' -)"
else
# Fallback to grep (less reliable)
echo -e "${YELLOW}Warning: yq not found, using grep (may miss services or show duplicates).${NC}"
# Find lines with path_regex, then try to get the policy line after
grep -n "path_regex: '^/.*" "$AUTHELIA_CONFIG" | while IFS=: read -r line_num line_content; do
if [[ "$line_content" =~ path_regex:\ \'^\/([a-zA-Z0-9_-]+)\.\*\' ]]; then
local service="${BASH_REMATCH[1]}"
# Skip duplicates
if [[ "$processed_services" == *"$service"* ]]; then
continue
fi
processed_services="$processed_services $service"
# Try to get policy from the next line (very fragile)
local policy_line_num=$((line_num + 1))
local policy=$(sed -n "${policy_line_num}p" "$AUTHELIA_CONFIG" | grep "policy:" | awk '{print $2}')
printf "${BOLD}%-20s${NC}" "$service"
if [ -n "$policy" ]; then
case "$policy" in
"one_factor"|"two_factor") echo -e "${GREEN}${policy}${NC}" ;;
"bypass") echo -e "${YELLOW}${policy}${NC}" ;;
"deny") echo -e "${RED}${policy}${NC}" ;;
*) echo -e "${MAGENTA}Unknown ($policy)${NC}" ;;
esac
else
echo -e "${RED}Unknown (could not read policy)${NC}"
fi
service_count=$((service_count + 1))
fi
done
fi
if [ $service_count -eq 0 ]; then
echo -e "${YELLOW}No services with path_regex rules found in $AUTHELIA_CONFIG.${NC}"
echo -e "${YELLOW}Only services explicitly defined with path_regex rules are listed.${NC}"
fi
return 0
}
cleanup_backups() {
print_header "Backup Files Cleanup"
echo -e "${BLUE}Searching for backup files...${NC}"
local env_backups=$(find . -maxdepth 1 -name ".env.*.bak" | sort)
local compose_backups=$(find . -maxdepth 1 -name "docker-compose.*.bak" | sort)
local authelia_backups=$(find ./authelia -maxdepth 1 -name "configuration.*.bak" 2>/dev/null | sort)
local env_count=$(echo "$env_backups" | grep -c "^")
local compose_count=$(echo "$compose_backups" | grep -c "^")
local authelia_count=$(echo "$authelia_backups" | grep -c "^")
echo -e "${CYAN}Found:${NC}"
echo -e " - ${CYAN}$env_count .env backup files${NC}"
echo -e " - ${CYAN}$compose_count docker-compose.yml backup files${NC}"
echo -e " - ${CYAN}$authelia_count Authelia configuration backup files${NC}"
local total_count=$((env_count + compose_count + authelia_count))
if [ $total_count -eq 0 ]; then
echo -e "${GREEN}No backup files found.${NC}"
return 0
fi
echo -e "${YELLOW}Do you want to delete all backup files? [y/N]:${NC}"
read -r answer
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Would you like to keep the most recent backup of each type? [Y/n]:${NC}"
read -r keep_recent
if [[ "$keep_recent" =~ ^[Nn]$ ]]; then
echo -e "${RED}Backup cleanup cancelled.${NC}"
return 0
fi
if [ $env_count -gt 1 ]; then
local keep_env=$(echo "$env_backups" | tail -1)
local del_env=$(echo "$env_backups" | grep -v "$keep_env")
echo -e "${BLUE}Keeping most recent .env backup: ${CYAN}$keep_env${NC}"
echo -e "${BLUE}Deleting ${CYAN}$((env_count - 1))${BLUE} older .env backups${NC}"
for file in $del_env; do
rm "$file"
done
fi
if [ $compose_count -gt 1 ]; then
local keep_compose=$(echo "$compose_backups" | tail -1)
local del_compose=$(echo "$compose_backups" | grep -v "$keep_compose")
echo -e "${BLUE}Keeping most recent docker-compose.yml backup: ${CYAN}$keep_compose${NC}"
echo -e "${BLUE}Deleting ${CYAN}$((compose_count - 1))${BLUE} older docker-compose.yml backups${NC}"
for file in $del_compose; do
rm "$file"
done
fi
if [ $authelia_count -gt 1 ]; then
local keep_authelia=$(echo "$authelia_backups" | tail -1)
local del_authelia=$(echo "$authelia_backups" | grep -v "$keep_authelia")
echo -e "${BLUE}Keeping most recent Authelia config backup: ${CYAN}$keep_authelia${NC}"
echo -e "${BLUE}Deleting ${CYAN}$((authelia_count - 1))${BLUE} older Authelia config backups${NC}"
for file in $del_authelia; do
rm "$file"
done
fi
echo -e "${GREEN}Cleanup completed with most recent backups retained.${NC}"
else
for file in $env_backups $compose_backups $authelia_backups; do
rm "$file"
done
echo -e "${GREEN}All $total_count backup files have been deleted.${NC}"
fi
}
# Interactive menu for managing Authelia policies
manage_authelia_policies() {
print_header "Authelia Policy Management"
if ! check_file "$AUTHELIA_CONFIG"; then
echo -e "${RED}Error: Authelia configuration file '$AUTHELIA_CONFIG' not found.${NC}"
return 1
fi
while true; do
echo -e "\n${BLUE}Choose an option:${NC}"
echo -e " ${CYAN}1. ${NC}List services and their current Authelia policy"
echo -e " ${CYAN}2. ${NC}Set Authelia policy for a service"
echo -e " ${CYAN}3. ${NC}Return to main menu"
echo
local choice
echo -e "${YELLOW}Enter your choice [1-3]: ${NC}"
read -r choice
case "$choice" in
1)
list_authelia_services
;;
2)
echo -e "${BLUE}Enter the service name to set the policy for (e.g., sonarr, radarr):${NC}"
read -r service
if [ -z "$service" ]; then
echo -e "${RED}No service name provided.${NC}"
continue
fi
echo -e "${BLUE}Select the desired policy for '$service':${NC}"
echo -e " ${CYAN}1. ${GREEN}one_factor${NC} (Requires login)"
echo -e " ${CYAN}2. ${GREEN}two_factor${NC} (Requires login + 2FA)"
echo -e " ${CYAN}3. ${YELLOW}bypass${NC} (No login required)"
echo -e " ${CYAN}4. ${RED}deny${NC} (Access denied)"
echo -e " ${CYAN}5. ${NC}Cancel"
local policy_choice
local policy=""
while true; do
echo -e "${YELLOW}Enter policy choice [1-5]: ${NC}"
read -r policy_choice
case "$policy_choice" in
1) policy="one_factor"; break ;;
2) policy="two_factor"; break ;;
3) policy="bypass"; break ;;
4) policy="deny"; break ;;
5) policy=""; break ;; # Cancel
*) echo -e "${RED}Invalid choice.${NC}" ;;
esac
done
if [ -z "$policy" ]; then
echo -e "${YELLOW}Policy change cancelled.${NC}"
continue
fi
# Call the function to set the policy
set_authelia_policy "$service" "$policy" "$AUTHELIA_CONFIG"
# Check the return status of set_authelia_policy
if [ $? -eq 0 ]; then
echo -e "\n${YELLOW}Remember to restart Authelia for the policy change to take effect:${NC}"
echo -e " ${CYAN}docker compose restart authelia${NC}"
else
echo -e "${RED}Failed to set policy. Please check errors above.${NC}"
# Optionally offer to restore backup here
fi
;;
3)
return 0
;;
*)
echo -e "${RED}Invalid choice. Please try again.${NC}"
;;
esac
echo # Add a newline for better readability between menu iterations
done
}
##################################################
# PART 5: Authelia Account Management
##################################################
manage_authelia_accounts() {
print_header "Authelia Account Management"
local users_file="${CONFIG_ROOT:-.}/authelia/users_database.yml"
if [ ! -f "$users_file" ]; then
echo -e "${RED}Error: users_database.yml not found at $users_file${NC}"
echo -e "${YELLOW}Would you like to create a new users database file? [y/N]:${NC}"
read -r answer
if [[ "$answer" =~ ^[Yy]$ ]]; then
cat > "$users_file" <<EOL
# Authelia User Database
# Documentation: https://www.authelia.com/configuration/security/authentication/file/
users:
admin:
displayname: "Admin User"
password: "$argon2id$v=19$m=102400,t=1,p=8$PBf/L9l3s7LwN6jX/B3tVg$9+q3kL8VAbpWj9Gv9Z6uA5bA4zT1fB2fH3aD5c6b7e8"
email: admin@example.com
groups:
- admins
- users
EOL
echo -e "${GREEN}Created new users_database.yml file${NC}"
else
echo -e "${RED}Account management cancelled.${NC}"
return 1
fi
fi
local backup_file="${users_file}.${TIMESTAMP}.bak"
cp "$users_file" "$backup_file"
echo -e "${BLUE}Backed up users database to ${backup_file}${NC}"
while true; do
echo -e "\n${CYAN}${BOLD}Add Authelia User${NC}"
echo -e "${BLUE}Enter a username (or press Enter to finish):${NC}"
read -r username
if [ -z "$username" ]; then
break
fi
if grep -q "^[[:space:]]*${username}:" "$users_file"; then
echo -e "${YELLOW}Warning: User '${username}' already exists.${NC}"
echo -e "${YELLOW}Would you like to update this user? [y/N]:${NC}"
read -r answer
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
echo -e "${RED}Skipping user '${username}'.${NC}"
continue
fi
fi
echo -e "${BLUE}Enter display name for ${username}:${NC}"
read -r displayname
if [ -z "$displayname" ]; then
displayname="$username"
fi
echo -e "${BLUE}Enter email for ${username}:${NC}"
read -r email
if [ -z "$email" ]; then
email="${username}@example.com"
fi
echo -e "${BLUE}Select group(s) for ${username} (comma-separated, default: users):${NC}"
echo -e "${CYAN}Available groups: admins, users${NC}"
read -r groups
if [ -z "$groups" ]; then
groups="users"
fi
IFS=',' read -ra group_array <<< "$groups"
formatted_groups=""
for group in "${group_array[@]}"; do
formatted_groups+=" - $(echo $group | xargs)\n"
done
generated_passphrase=$(generate_passphrase)
echo -e "${GREEN}Generated passphrase: ${BOLD}${generated_passphrase}${NC}"
echo -e "${YELLOW}Do you want to use this passphrase? [Y/n]:${NC}"
read -r use_generated
if [[ "$use_generated" =~ ^[Nn]$ ]]; then
echo -e "${BLUE}Enter a custom password for ${username}:${NC}"
read -rs password
if [ -z "$password" ]; then
echo -e "${RED}Error: Password cannot be empty.${NC}"
continue
fi
else
password="$generated_passphrase"
fi
echo -e "${CYAN}Generating password hash...${NC}"
password_hash=$(docker compose run --rm authelia authelia crypto hash generate argon2 --password "$password" 2>/dev/null)
if [ -z "$password_hash" ]; then
echo -e "${RED}Error: Failed to generate password hash. Is the authelia container available?${NC}"
echo -e "${YELLOW}Trying direct docker run method...${NC}"
password_hash=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$password" 2>/dev/null)
if [ -z "$password_hash" ]; then
echo -e "${RED}Error: Both methods failed to generate a password hash. Skipping user.${NC}"
continue
fi
fi
password_hash=$(echo "$password_hash" | sed 's/^Digest: //')
if [[ ! "$password_hash" =~ ^\$argon2id.*$ ]]; then
echo -e "${RED}Error: Generated hash does not have the expected format. Actual value:${NC}"
echo -e "${YELLOW}$password_hash${NC}"
echo -e "${RED}Skipping user creation.${NC}"
continue
fi
echo -e "${GREEN}Password hash generated successfully.${NC}"
if grep -q "^[[:space:]]*${username}:" "$users_file"; then
sed -i "/^[[:space:]]*${username}:/,/^[[:space:]]*[a-zA-Z0-9_-]\+:/ s/^/# UPDATED: /" "$users_file"
sed -i "0,/^# UPDATED: [[:space:]]*[a-zA-Z0-9_-]\+:/ s/^# UPDATED: //" "$users_file"
fi
cat >> "$users_file" <<EOL
${username}:
displayname: "${displayname}"
password: "${password_hash}"
email: ${email}
groups:
$(echo -e "$formatted_groups")
EOL
echo -e "${GREEN}User '${username}' added successfully!${NC}"
echo -e "${CYAN}Password: ${BOLD}${password}${NC}"
echo -e "${YELLOW}Please save this password securely.${NC}"
done
echo -e "\n${GREEN}${BOLD}Account Management Complete!${NC}"
echo -e "${BLUE}Users file updated: ${users_file}${NC}"
echo -e "${BLUE}Backup saved as: ${backup_file}${NC}"
echo -e "${YELLOW}Would you like to restart Authelia to apply changes? [y/N]:${NC}"
read -r restart
if [[ "$restart" =~ ^[Yy]$ ]]; then
if docker compose restart authelia; then
echo -e "${GREEN}Authelia restarted successfully!${NC}"
else
echo -e "${RED}Failed to restart Authelia. Please restart manually.${NC}"
fi
else
echo -e "${YELLOW}Remember to restart Authelia to apply these changes:${NC}"
echo -e "${CYAN} docker compose restart authelia${NC}"
fi
}
##################################################
# MAIN SCRIPT
##################################################
# Function to display help message
show_help() {
print_header "Docker Compose NAS - Setup Tool"
echo -e "${BLUE}Usage: $0 [command] [arguments]${NC}"
echo -e ""
echo -e "${BLUE}Available Commands:${NC}"
echo -e " ${CYAN}update-env${NC} Update .env file from .env.example, preserving values."
echo -e " ${CYAN}update-authelia${NC} Update Authelia configuration (configuration.yml) from example,"
echo -e " applying domain settings from .env and preserving secrets."
echo -e " ${CYAN}update-services${NC} Update configurations for running *arr/qBittorrent/Bazarr containers"
echo -e " (sets URL base, extracts API keys to .env)."
echo -e " ${CYAN}manage-accounts${NC} Interactively add/update Authelia users in users_database.yml."
echo -e " ${CYAN}manage-policies${NC} Interactively manage Authelia access policies for services."
echo -e " ${CYAN}list-policies${NC} List services and their current Authelia policy."
echo -e " ${CYAN}set-policy <svc> <pol>${NC} Set Authelia policy for a service (e.g., 'one_factor', 'bypass')."
echo -e " ${CYAN}cleanup${NC} Interactively clean up old backup files (.bak)."
echo -e " ${CYAN}all${NC} Run 'update-env', 'update-authelia', and 'update-services'."
echo -e " ${CYAN}help${NC} Show this help message."
echo -e ""
echo -e "${BLUE}Examples:${NC}"
echo -e " $0 update-authelia"
echo -e " $0 set-policy sonarr one_factor"
echo -e " $0 set-policy radarr bypass"
echo -e " $0 manage-policies"
echo -e " $0 all"
echo -e ""
echo -e "${YELLOW}Note:${NC} Some commands require Docker to be running and may restart containers."
echo -e "${YELLOW}Policy changes require an Authelia restart ('docker compose restart authelia').${NC}"
}
# Check if any arguments were provided
if [ $# -eq 0 ]; then
show_help
exit 0
fi
# Process command line arguments
case "$1" in
update-env)
update_env_file
;;
update-authelia)
update_authelia_config
;;
update-services)
update_service_configs
;;
manage-accounts)
manage_authelia_accounts # Interactive
;;
manage-policies)
manage_authelia_policies # Interactive
;;
list-policies)
list_authelia_services
;;
set-policy)
if [ -z "$2" ] || [ -z "$3" ]; then
echo -e "${RED}Error: Service name and policy are required.${NC}" >&2
echo -e "Usage: $0 set-policy <service_name> <policy>${NC}" >&2
echo -e "Valid policies: one_factor, two_factor, bypass, deny" >&2
exit 1
fi
if ! check_file "$AUTHELIA_CONFIG"; then exit 1; fi
# Backup is handled within set_authelia_policy if needed
set_authelia_policy "$2" "$3" "$AUTHELIA_CONFIG"
if [ $? -eq 0 ]; then
echo -e "\n${YELLOW}Remember to restart Authelia for the policy change to take effect:${NC}"
echo -e " ${CYAN}docker compose restart authelia${NC}"
fi
;;
cleanup)
cleanup_backups
;;
all)
print_header "Running All Updates"
update_env_file
update_authelia_config
update_service_configs
echo -e "\n${GREEN}${BOLD}All core updates completed!${NC}"
echo -e "${BLUE}Review output for any required actions (e.g., setting new .env variables).${NC}"
echo -e "${BLUE}Consider running 'manage-accounts' if needed.${NC}"
echo -e "${YELLOW}Remember to restart relevant services or the full stack if necessary.${NC}"
;;
help|-h|--help)
show_help
;;
*)
echo -e "${RED}Unknown command: $1${NC}" >&2
echo -e "${BLUE}Run '$0 help' for usage information.${NC}" >&2
exit 1
;;
esac
exit 0