diff --git a/.env.example b/.env.example index b03ff47..b9d30f2 100644 --- a/.env.example +++ b/.env.example @@ -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_FUNNEL_HTTPS=false -# --- Primary Hostname --- -# Primary hostname used by Traefik for routing. Derived from Tailscale settings by default. -HOSTNAME=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN} +# --- Primary Application Hostname --- +# Primary hostname used by Traefik/Authelia. Derived from Tailscale settings by default. +# Renamed from HOSTNAME to avoid collision with host system environment variable. +APP_HOSTNAME=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN} # --- Application Credentials --- # qBittorrent Web UI Credentials (change default!) @@ -62,18 +63,16 @@ HOMEPAGE_VAR_WEATHER_UNIT=metric # --- Authelia Settings --- # Generate strong random secrets for these using tools like `openssl rand -hex 32` -AUTHELIA_JWT_SECRET= # Example: your_strong_jwt_secret -AUTHELIA_SESSION_SECRET= # Example: your_strong_session_secret -AUTHELIA_STORAGE_ENCRYPTION_KEY= # Example: your_strong_storage_encryption_key -AUTHELIA_REDIS_PASSWORD= # Example: your_strong_redis_password +# These are all REQUIRED for Authelia to function properly +AUTHELIA_JWT_SECRET= # Secret used for JWT tokens (password reset, etc) +AUTHELIA_SESSION_SECRET= # Secret for encrypting session cookies +AUTHELIA_STORAGE_ENCRYPTION_KEY= # Secret for encrypting stored data +AUTHELIA_REDIS_PASSWORD= # Password for Redis session storage -# Google OIDC Provider Settings (Get from Google Cloud Console - https://console.cloud.google.com/apis/credentials) -AUTHELIA_GOOGLE_OIDC_CLIENT_ID= # Example: your-google-client-id.apps.googleusercontent.com -AUTHELIA_GOOGLE_OIDC_CLIENT_SECRET= # Example: GOCSPX-your-google-client-secret - -# Authelia Session Configuration -AUTHELIA_SESSION_DOMAIN=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN} -AUTHELIA_DEFAULT_REDIRECT_URL=https://${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/home +# Note: The following variables are no longer needed with Authelia 4.38+ and the updated configuration +# They are preserved for backward compatibility but will be automatically mapped to the new structure +# AUTHELIA_SESSION_DOMAIN=${APP_HOSTNAME} +# AUTHELIA_DEFAULT_REDIRECT_URL=https://${APP_HOSTNAME}/home # --- API Keys & Integration Tokens (Optional - Mainly for Homepage Widgets) --- # Find API keys within each application's settings (usually Settings > General or Security) diff --git a/.gitignore b/.gitignore index fa2394e..487b297 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.env +*.bak .idea docker-compose.override.yml +/authelia/*.yml +!/authelia/*.example.yml /homepage/logs /homepage/*.yaml /homepage/*.css diff --git a/authelia/configuration.example.yml b/authelia/configuration.example.yml new file mode 100644 index 0000000..753d5af --- /dev/null +++ b/authelia/configuration.example.yml @@ -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: '^/.*' + # 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 diff --git a/authelia/users_database.example.yml b/authelia/users_database.example.yml new file mode 100644 index 0000000..461f16d --- /dev/null +++ b/authelia/users_database.example.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 2f3263f..1aa9001 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,18 +13,57 @@ services: - --experimental.plugins.rewrite-body.version=v1.2.0 - --experimental.plugins.rewriteHeaders.modulename=github.com/XciD/traefik-plugin-rewrite-headers - --experimental.plugins.rewriteHeaders.version=v0.0.3 - network_mode: service:tailscale # Add this line - # ports: # Remove this section - # - "80:80" - # - "443:443" + - --providers.docker.network=docker-compose-nas + - --providers.docker.endpoint=unix:///var/run/docker.sock + network_mode: service:tailscale volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - # extra_hosts: # Remove this section - # - host.docker.internal:172.17.0.1 healthcheck: test: ["CMD", "traefik", "healthcheck", "--ping"] interval: 30s retries: 10 + redis: + image: redis:alpine + container_name: redis + restart: always + environment: + - REDIS_PASSWORD=${AUTHELIA_REDIS_PASSWORD} + command: ["redis-server", "--requirepass", "${AUTHELIA_REDIS_PASSWORD}"] + volumes: + - ${CONFIG_ROOT:-.}/redis:/data:Z + healthcheck: + test: ["CMD", "redis-cli", "-a", "${AUTHELIA_REDIS_PASSWORD}", "ping"] + interval: 5s + timeout: 3s + retries: 5 + authelia: + image: authelia/authelia:latest + container_name: authelia + restart: always + user: ${USER_ID}:${GROUP_ID} + volumes: + - ${CONFIG_ROOT:-.}/authelia:/config:Z + environment: + - AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET} + - AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY} + - AUTHELIA_SESSION_REDIS_PASSWORD=${AUTHELIA_REDIS_PASSWORD} + - AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET=${AUTHELIA_JWT_SECRET} + - TZ=${TIMEZONE} + labels: + - traefik.enable=true + - traefik.http.routers.authelia.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/auth`) # Changed rule + - traefik.http.routers.authelia.entrypoints=web + # - traefik.http.routers.authelia.priority=100 # Removed priority + - traefik.http.services.authelia.loadbalancer.server.port=9091 + - traefik.http.middlewares.authelia-auth.forwardAuth.address=http://authelia:9091/api/verify # Simplified forwardAuth address + - traefik.http.routers.authelia.middlewares=https-proto@docker + - traefik.http.middlewares.authelia-auth.forwardAuth.trustForwardHeader=true + - traefik.http.middlewares.authelia-auth.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email + - homepage.group=Security + - homepage.name=Authelia + - homepage.icon=authelia.png + - homepage.href=/auth # Updated href + - homepage.description=Authentication Portal sonarr: image: lscr.io/linuxserver/sonarr container_name: sonarr @@ -42,8 +81,9 @@ services: retries: 10 labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.sonarr.loadbalancer.server.port=8989 - homepage.group=Media - homepage.name=Sonarr @@ -71,8 +111,9 @@ services: retries: 10 labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.radarr.loadbalancer.server.port=7878 - homepage.group=Media - homepage.name=Radarr @@ -100,8 +141,9 @@ services: retries: 10 labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.lidarr.loadbalancer.server.port=8686 - homepage.group=Media - homepage.name=Lidarr @@ -131,8 +173,9 @@ services: retries: 10 labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.bazarr.loadbalancer.server.port=6767 - homepage.group=Download - homepage.name=Bazarr @@ -165,10 +208,10 @@ services: retries: 10 labels: - 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.services.jellyseerr.loadbalancer.server.port=5055 - - traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders + - traefik.http.routers.jellyseerr.middlewares=https-proto@docker,jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders,authelia-auth@docker - traefik.http.middlewares.jellyseerr-stripprefix.stripPrefix.prefixes=/jellyseerr - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].header=location - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].regex=^/(.+)$ @@ -239,8 +282,9 @@ services: retries: 10 labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.prowlarr.loadbalancer.server.port=9696 - homepage.group=Download - homepage.name=Prowlarr @@ -262,8 +306,9 @@ services: - TZ=${TIMEZONE} labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.flaresolverr.loadbalancer.server.port=8191 profiles: - flaresolverr @@ -281,25 +326,20 @@ services: - ${DOWNLOAD_ROOT}:/data/torrents:Z restart: always healthcheck: - # Container may fail if the PIA's token expired, so mark as unhealthy when there is no internet connection - # see: https://github.com/qdm12/gluetun/issues/641#issuecomment-933856220 test: ["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"] interval: 30s retries: 10 labels: - 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.services.qbittorrent.loadbalancer.server.port=8080 - - traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix - # https://github.com/qbittorrent/qBittorrent/issues/5693#issuecomment-552146296 + - traefik.http.routers.qbittorrent.middlewares=https-proto@docker,qbittorrent-strip-slash,qbittorrent-stripprefix,authelia-auth@docker - traefik.http.middlewares.qbittorrent-stripprefix.stripPrefix.prefixes=/qbittorrent - # https://community.traefik.io/t/middleware-to-add-the-if-needed/1895/19 - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.regex=(^.*\/qbittorrent$$) - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/ - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.permanent=false - #- com.centurylinklabs.watchtower.depends-on=/vpn - homepage.group=Download - homepage.name=qBittorrent - homepage.icon=qbittorrent.png @@ -338,8 +378,9 @@ services: restart: always labels: - 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.middlewares=https-proto@docker,authelia-auth@docker - traefik.http.services.sabnzbd.loadbalancer.server.port=8080 - homepage.group=Download - homepage.name=Sabnzbd @@ -359,7 +400,7 @@ services: - PUID=${USER_ID} - PGID=${GROUP_ID} - TZ=${TIMEZONE} - - JELLYFIN_PublishedServerUrl=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/jellyfin + - JELLYFIN_PublishedServerUrl=https://${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}/jellyfin volumes: - ${CONFIG_ROOT:-.}/jellyfin:/config:Z - ${DATA_ROOT}:/data:Z @@ -373,8 +414,9 @@ services: retries: 10 labels: - 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.middlewares=https-proto@docker # Only HTTPS, no auth - traefik.http.services.jellyfin.loadbalancer.server.port=8096 - homepage.group=Media - 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-Script-Name=/calibre - traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre - - traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex - - traefik.http.routers.calibre.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/calibre`) + - traefik.http.routers.calibre.middlewares=https-proto@docker,calibre-headers,calibre-stripprefixregex,authelia-auth@docker + - traefik.http.routers.calibre.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/calibre`) # Added Host check - traefik.http.routers.calibre.entrypoints=web - traefik.http.services.calibre.loadbalancer.server.port=8083 - homepage.group=Media @@ -467,7 +509,6 @@ services: - HOMEPAGE_VAR_WEATHER_LONG=${HOMEPAGE_VAR_WEATHER_LONG} - HOMEPAGE_VAR_WEATHER_TIME=${TIMEZONE} - HOMEPAGE_VAR_WEATHER_UNIT=${HOMEPAGE_VAR_WEATHER_UNIT} - # Explicitly allow the hostname constructed from Tailscale variables - HOMEPAGE_ALLOWED_HOSTS=${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN} volumes: - ${CONFIG_ROOT:-.}/homepage:/app/config:Z @@ -478,11 +519,17 @@ services: [sh, -c, "cp -n /app/config/tpl/*.yaml /app/config && node server.js"] labels: - traefik.enable=true - # Change path to /home and use specific Tailscale host - - traefik.http.routers.homepage.rule=Host(`${TAILSCALE_HOSTNAME}.${TAILSCALE_TAILNET_DOMAIN}`) && PathPrefix(`/home`) + - traefik.http.routers.homepage.rule=Host(`${APP_HOSTNAME}`) && PathPrefix(`/`) # Changed rule to root - traefik.http.routers.homepage.entrypoints=web - # Authelia middleware will be added in a later commit - - traefik.http.services.homepage.loadbalancer.server.port=3000 + # - traefik.http.routers.homepage.priority=10 # Removed priority + # Global middleware for setting HTTPS header + - traefik.http.middlewares.https-proto.headers.customrequestheaders.X-Forwarded-Proto=https + - traefik.http.routers.homepage.middlewares=https-proto@docker,authelia-auth@docker + - homepage.group=Dashboard + - homepage.name=Homepage + - homepage.icon=homepage.png + - homepage.href=/ # Updated href + - homepage.description=Service Dashboard watchtower: image: ghcr.io/containrrr/watchtower:latest container_name: watchtower @@ -502,23 +549,22 @@ services: tailscale: image: tailscale/tailscale:latest container_name: tailscale - hostname: ${TAILSCALE_HOSTNAME:-tailscale-nas} # Hostname for Tailscale access + hostname: ${TAILSCALE_HOSTNAME:-tailscale-nas} environment: - TS_AUTHKEY: ${TAILSCALE_AUTHKEY} # Needs to be set in .env - TS_EXTRA_ARGS: "--advertise-tags=${TAILSCALE_TAGS:-tag:nas}" # Keep tags if desired + TS_AUTHKEY: ${TAILSCALE_AUTHKEY} + TS_EXTRA_ARGS: "--advertise-tags=${TAILSCALE_TAGS:-tag:nas}" TS_STATE_DIR: "/var/lib/tailscale" TS_USERSPACE: "false" - # Switch to enable Funnel (public access) or Serve (Tailnet only) ENABLE_FUNNEL_HTTPS: ${ENABLE_FUNNEL_HTTPS:-false} volumes: - - ${CONFIG_ROOT:-.}/tailscale/state:/var/lib/tailscale:Z # Persist state - - /var/run/docker.sock:/var/run/docker.sock # Optional, keep if needed + - ${CONFIG_ROOT:-.}/tailscale/state:/var/lib/tailscale:Z + - /var/run/docker.sock:/var/run/docker.sock devices: - /dev/net/tun:/dev/net/tun cap_add: - NET_ADMIN - NET_RAW - extra_hosts: # Add this section + extra_hosts: - host.docker.internal:172.17.0.1 restart: always command: @@ -542,8 +588,6 @@ services: done echo " Tailscaled is running." - # --- Start Tailscale Funnel/Serve --- - # Check the ENABLE_FUNNEL_HTTPS variable if [ "${ENABLE_FUNNEL_HTTPS}" = "true" ]; then echo "ENABLE_FUNNEL_HTTPS is true. Setting up Funnel -> http://localhost:80..." tailscale funnel --bg http://localhost:80 @@ -553,10 +597,9 @@ services: tailscale serve --bg http://localhost:80 echo "Tailscale Serve configured." fi - # --- End Tailscale Funnel/Serve --- echo "Tailscale forwarding configured. Container will remain running." - wait # Wait indefinitely for background processes + wait networks: default: