11 Commits

Author SHA1 Message Date
kelin
44b529a7cb feat: Add Option 3 - Deploy Additional Server with multi-server support
Major features:
- Automated SSH key setup between remote and core servers
- Docker TLS configuration with shared CA certificates
- Automatic deployment of Dockge, Traefik, Sablier, and Infrastructure stacks
- Copy all stacks (except core) to remote server for on-demand deployment
- New standalone Traefik stack for remote server container discovery
- Locale-aware SSH/SCP commands to handle Raspberry Pi warnings
- Variable expansion support in .env files (${VAR} references)
- Comprehensive error handling and verbose deployment logging

Technical improvements:
- setup_ssh_key_to_core() - Automated RSA 4096-bit key generation and installation
- setup_multi_server_tls() - Fetch shared CA from core server via SSH
- copy_all_stacks_for_remote() - Deploy all stacks except core
- deploy_traefik_stack() - Local Traefik for container discovery
- Enhanced localization with envsubst support
- Docker network creation (traefik-network, homelab-network)
- Password authentication with special character handling

Fixes:
- Fixed SSH key path handling for non-root users
- Fixed SCP exit code checking (was checking grep instead of scp)
- Fixed CA file detection with proper test commands
- Removed unnecessary prepare_deployment() function call
- Added ACTUAL_USER variable initialization for remote deployments
2026-02-06 22:00:25 -05:00
kelinfoxy
5b3c4a2c5b Chage homarr image & add SECRET_ENCRYPTION_KEY 2026-02-06 17:17:17 -05:00
kelinfoxy
5cbb106160 Add multi-server support and update docs
Introduce multi-server architecture documentation and reorganize README content. Top-level README now documents Core vs Remote server roles, links to local docs instead of wiki pages, and highlights Traefik/Sablier multi-server behavior. docker-compose/README.md was rewritten to be a template-style reference with single- and multi-server deployment guidance, Traefik label examples, and sablier usage; dockge README was moved into docker-compose/dockge/. docker-compose/core/README.md was updated to describe core responsibilities, shared CA artifacts, and startup order for multi-server deployments. Several obsolete/duplicated docs and action reports were removed and a new multi-server deployment doc was added to centralize on-demand/remote service guidance. Overall this cleans up legacy docs and documents the multi-server workflow and TLS/shared-CA requirements.
2026-02-05 22:30:52 -05:00
Kelin
84b2cabacc Merge pull request #2 from kelinfoxy/copilot/scan-and-replace-personal-data
Sanitize personal data from repository history
2026-02-05 14:09:23 -05:00
copilot-swe-agent[bot]
0052fa4ddc Complete personal data replacement in Traefik configs
- Replace remaining domain references in Traefik dynamic configs
- Replace kelinreij.duckdns.org with yourdomain.duckdns.org in:
  - external-host-homeassistant.yml
  - local-host-production.yml
  - sablier.yml
  - markup.yml service URLs

Co-authored-by: kelinfoxy <67766943+kelinfoxy@users.noreply.github.com>
2026-02-05 18:41:42 +00:00
copilot-swe-agent[bot]
300d870a2b Replace personal data with variables and placeholders
- Replace hardcoded password in code-server config with ${CODE_SERVER_PASSWORD}
- Replace domain kelin-hass.duckdns.org with yourdomain.duckdns.org in docs
- Replace domain kelinreij.duckdns.org with yourdomain.duckdns.org in homepage config
- Replace personal emails with example addresses
- Replace DuckDNS token and credentials in markup.yml with placeholders
- Replace Let's Encrypt account numbers with placeholders

Co-authored-by: kelinfoxy <67766943+kelinfoxy@users.noreply.github.com>
2026-02-05 18:40:44 +00:00
copilot-swe-agent[bot]
94b4906a6a Initial plan 2026-02-05 18:35:18 +00:00
kelin
10aee3b3b1 Fix: Correct Dozzle port and remove unnecessary template files
- Changed Dozzle service port from 8085 to 8080 (correct port)
- Removed authelia config template files (no longer needed)
- Removed db.sqlite3 runtime data file
2026-02-05 13:13:44 -05:00
kelin
4803d2c856 Fix: Add multi-line secret sanitization to deployment script
Added Python script to automatically merge multi-line Authelia secrets
(JWT_SECRET, SESSION_SECRET, STORAGE_ENCRYPTION_KEY) during core
deployment. This prevents envsubst from reading truncated values when
the .env file contains accidental line breaks.

Fixes Authelia startup errors:
- 'encryption key does not appear to be valid for this database'
- Invalid URL parsing due to incomplete variable expansion
2026-02-05 12:36:19 -05:00
kelin
d4c9516e00 Fix: Generate users_database.yml in secrets folder with proper variables
- Move users_database.yml template from config/ to secrets/ folder
- Authelia configuration expects users file at /secrets/users_database.yml
- Replace hardcoded values in configuration.yml with variables:
  - jwt_secret, session secret, storage encryption_key
  - Domain references (kelinreij.duckdns.org → ${DOMAIN})
- Update deploy script to not move config files (already in subdirectories)
- Fix sed command path to target config/configuration.yml
- Remove unnecessary mkdir and mv commands from deploy_core()

This ensures Authelia uses the correctly generated file with user credentials.
2026-02-05 12:12:33 -05:00
kelin
a554d00fd3 Fix: Use variables instead of placeholders in docker-compose files
- Replace hardcoded placeholders with Docker Compose variables
- docker-compose.yml: Use ${DUCKDNS_SUBDOMAINS}, ${DUCKDNS_TOKEN}, ${DOMAIN}, ${SERVER_IP}
- traefik.yml: Use ${DEFAULT_EMAIL} for ACME email
- users_database.yml: Use ${AUTHELIA_ADMIN_USER}, ${AUTHELIA_ADMIN_EMAIL}, ${AUTHELIA_ADMIN_PASSWORD_HASH}
- sablier/docker-compose.yml: Fix to use ${DOMAIN} instead of {{DUCKDNS_DOMAIN}}

This aligns with the correct strategy:
- Docker-compose files use variables (${VAR})
- .env files use placeholders or actual values
2026-02-05 11:57:42 -05:00
47 changed files with 1402 additions and 2927 deletions

View File

@@ -12,13 +12,6 @@ TZ=America/New_York
SERVER_IP=192.168.1.100 # This server SERVER_IP=192.168.1.100 # This server
SERVER_HOSTNAME=debian SERVER_HOSTNAME=debian
# If deploying with option 3: Remote Core Server
# the REMOTE_SERVER is where the Core Stack (Traefik) is running
REMOTE_SERVER_IP=your.remote.ip.address
REMOTE_SERVER_HOSTNAME=your-remote-server
REMOTE_SERVER_USER=${DEFAULT_USER}
REMOTE_SERVER_PASSWORD=${DEFAULT_PASSWORD}
# Domain Configuration # Domain Configuration
DUCKDNS_SUBDOMAINS=yourdomain # Without .duckdns.org DUCKDNS_SUBDOMAINS=yourdomain # Without .duckdns.org
DUCKDNS_TOKEN=your-duckdns-token DUCKDNS_TOKEN=your-duckdns-token
@@ -35,6 +28,13 @@ MEDIADIR=/mnt/media # Large media files on separate drive
DOWNLOADDIR=/mnt/downloads # Downloads on separate drive DOWNLOADDIR=/mnt/downloads # Downloads on separate drive
PROJECTDIR=~/projects # User's projects folder PROJECTDIR=~/projects # User's projects folder
# If selecting option 3: Deploy Additional Server
# the CORE_SERVER is where the Core Traefik is running
CORE_SERVER_IP=192.168.1.101
CORE_SERVER_HOSTNAME=debian2
CORE_SERVER_USER=${DEFAULT_USER}
CORE_SERVER_PASSWORD=${DEFAULT_PASSWORD}
# ########################################## # ##########################################
# #### NOTEABLE OPTIONAL CONFIGURATIONS #### # #### NOTEABLE OPTIONAL CONFIGURATIONS ####

View File

@@ -31,6 +31,12 @@ cd EZ-Homelab
./scripts/ez-homelab.sh ./scripts/ez-homelab.sh
``` ```
**Multi-Server Support:**
- **Core Server**: Full deployment with ports 80/443 forwarded from router
- **Remote Servers**: Infrastructure-only setup (option 3 in script)
- Each server runs its own Traefik and Sablier for local container management
- Core server Traefik routes to all servers via Docker TLS providers
**What the script does:** **What the script does:**
- Installs Docker and required system packages - Installs Docker and required system packages
- Guides you through configuration (domain, admin credentials, etc.) - Guides you through configuration (domain, admin credentials, etc.)
@@ -44,41 +50,56 @@ cd EZ-Homelab
## 📚 Documentation ## 📚 Documentation
For comprehensive documentation, see the [GitHub Wiki](https://github.com/kelinfoxy/EZ-Homelab/wiki): - **[Getting Started Guide](docs/getting-started.md)** - Step-by-step deployment and configuration
- **[Automated Setup](docs/automated-setup.md)** - Guided installation with ez-homelab.sh script
- **[Getting Started Guide](https://github.com/kelinfoxy/EZ-Homelab/wiki/Getting-Started-Guide)** - Step-by-step deployment and configuration - **[Manual Setup](docs/manual-setup.md)** - Step-by-step manual installation
- **[Docker Guidelines](https://github.com/kelinfoxy/EZ-Homelab/wiki/Docker-Guidelines)** - Service management patterns and best practices - **[Docker Guidelines](docs/docker-guidelines.md)** - Service management patterns and best practices
- **[Quick Reference](https://github.com/kelinfoxy/EZ-Homelab/wiki/Quick-Reference)** - Command cheat sheet and troubleshooting - **[Services Reference](docs/services-overview.md)** - All 50+ available services
- **[Services Reference](https://github.com/kelinfoxy/EZ-Homelab/wiki/Services-Overview)** - All 70+ available services - **[Quick Reference](docs/quick-reference.md)** - Command cheat sheet and troubleshooting
- **[Proxying External Hosts](https://github.com/kelinfoxy/EZ-Homelab/wiki/Proxying-External-Hosts)** - Connect non-Docker services (Raspberry Pi, NAS, etc.) - **[Proxying External Hosts](docs/proxying-external-hosts.md)** - Connect non-Docker services (Raspberry Pi, NAS, etc.)
- **[Multi-Server Setup](docs/Ondemand-Remote-Services.md)** - Deploy services across multiple servers
## 🚀 Quick Navigation ## 🚀 Quick Navigation
**New to EZ-Homelab?** → [Getting Started Guide](https://github.com/kelinfoxy/EZ-Homelab/wiki/Getting-Started-Guide) **New to EZ-Homelab?** → [Getting Started Guide](docs/getting-started.md)
**Need Help Deploying?** → [Automated Setup](https://github.com/kelinfoxy/EZ-Homelab/wiki/Getting-Started-Guide#automated-setup) **Need Help Deploying?** → [Automated Setup](docs/automated-setup.md)
**Want to Add Services?** → [Service Creation Guide](https://github.com/kelinfoxy/EZ-Homelab/wiki/Docker-Guidelines#service-creation-guidelines) **Want to Add Services?** → [Service Creation Guide](docs/docker-guidelines.md)
**Having Issues?** → [Troubleshooting](https://github.com/kelinfoxy/EZ-Homelab/wiki/Quick-Reference#troubleshooting) **Having Issues?** → [Troubleshooting](docs/quick-reference.md)
**Managing Services?** → [Dockge Dashboard](https://dockge.yourdomain.duckdns.org) **Multi-Server Setup?** → [Remote Services Guide](docs/Ondemand-Remote-Services.md)
**Managing Services?** → Dockge Dashboard at `https://dockge.yourdomain.duckdns.org`
### Service Documentation ### Service Documentation
Individual service documentation is available in the [GitHub Wiki](https://github.com/kelinfoxy/EZ-Homelab/wiki): Individual service documentation is available in [docs/service-docs/](docs/service-docs/):
- [Authelia](https://github.com/kelinfoxy/EZ-Homelab/wiki/Authelia) - SSO authentication - [Authelia](docs/service-docs/authelia.md) - SSO authentication
- [Traefik](https://github.com/kelinfoxy/EZ-Homelab/wiki/Traefik) - Reverse proxy and SSL - [Traefik](docs/service-docs/traefik.md) - Reverse proxy and SSL
- [Dockge](https://github.com/kelinfoxy/EZ-Homelab/wiki/Dockge) - Stack management - [Sablier](docs/service-docs/sablier.md) - Lazy loading for on-demand containers
- [Homepage](https://github.com/kelinfoxy/EZ-Homelab/wiki/Homepage) - Service dashboard - [DuckDNS](docs/service-docs/duckdns.md) - Dynamic DNS
- And 50+ more services... - [Dockge](docs/service-docs/dockge.md) - Stack management
- [Homepage](docs/service-docs/homepage.md) - Service dashboard
- And 50+ more services in the docs/service-docs/ folder
## 🏗️ Architecture ## 🏗️ Architecture
### Core Infrastructure ### Core Infrastructure (Deploy on Main Server)
- **Traefik** - Reverse proxy with automatic HTTPS termination
- **Authelia** - Single sign-on (SSO) authentication
- **DuckDNS** - Dynamic DNS with wildcard SSL certificates - **DuckDNS** - Dynamic DNS with wildcard SSL certificates
- **Sablier** - Lazy loading service for on-demand containers - **Traefik** - Reverse proxy with automatic HTTPS termination and multi-server routing
- **Authelia** - Single sign-on (SSO) authentication
### Per-Server Infrastructure (Deploy on Each Server)
- **Traefik** - Local reverse proxy instance for container discovery
- **Sablier** - Lazy loading service for on-demand local container startup
### Multi-Server Architecture
- **Core Server**: Only server with ports 80/443 forwarded from router
- **Remote Servers**: Connect to core via Docker TLS (port 2376)
- **Unified Access**: All services accessible through core server's domain
- **Automatic Routing**: Core Traefik discovers services on all servers
- **Lazy Loading**: Each server's Sablier manages local containers only
### VPN Services ### VPN Services
- **Gluetun** - VPN client for secure downloads - **Gluetun** - VPN client for secure downloads
@@ -94,11 +115,13 @@ Individual service documentation is available in the [GitHub Wiki](https://githu
### Key Features ### Key Features
- **File-based configuration** - AI-manageable YAML files - **File-based configuration** - AI-manageable YAML files
- **Multi-server support** - Scale across multiple machines with unified access
- **Automated SSL** - Wildcard certificates via Let's Encrypt - **Automated SSL** - Wildcard certificates via Let's Encrypt
- **Automatic routing** - Traefik discovers services across all servers
- **VPN routing** - Secure download clients through Gluetun - **VPN routing** - Secure download clients through Gluetun
- **Resource limits** - Prevent resource exhaustion - **Resource limits** - Prevent resource exhaustion
- **SSO protection** - Authelia integration with bypass options - **SSO protection** - Authelia integration with bypass options
- **Lazy loading** - Sablier enables on-demand container startup - **Lazy loading** - Per-server Sablier enables on-demand container startup
- **Automated backups** - Restic + Backrest for comprehensive data protection - **Automated backups** - Restic + Backrest for comprehensive data protection
## 🤖 AI Management ## 🤖 AI Management
@@ -121,8 +144,8 @@ This homelab is designed to be managed by AI agents through VS Code with GitHub
## 🔧 Manual Setup ## 🔧 Manual Setup
If automated scripts fail, see: If automated scripts fail, see:
- **[Manual Setup Guide](https://github.com/kelinfoxy/EZ-Homelab/wiki/Manual-Setup)** - Step-by-step manual installation - **[Manual Setup Guide](docs/manual-setup.md)** - Step-by-step manual installation
- **[Troubleshooting](https://github.com/kelinfoxy/EZ-Homelab/wiki/Troubleshooting)** - Common issues and solutions - **[Troubleshooting](docs/quick-reference.md)** - Common issues and solutions
## 🤝 Contributing ## 🤝 Contributing

View File

@@ -1,152 +1,108 @@
# Docker Compose Stacks # Docker Compose Stacks
This directory contains Docker Compose files for managing your homelab services. Each stack is organized in its own folder for better organization and maintainability. This directory contains Docker Compose templates for managing your homelab services. Each stack is organized in its own folder for better organization and maintainability.
## Structure ## Structure
``` ```
docker-compose/ docker-compose/
├── core/ # Core infrastructure (Traefik, Authelia, DuckDNS) ├── core/ # Core infrastructure (MUST DEPLOY FIRST)
├── infrastructure/ # Additional infrastructure (Pi-hole, Dockge, etc.) │ ├── docker-compose.yml
│ ├── authelia/ # SSO configuration
│ ├── duckdns/ # DNS configuration
│ └── traefik/ # Reverse proxy configuration
│ └── dynamic/ # External routing YAML files (multi-server)
├── sablier/ # Lazy loading service (per-server)
├── dockge/ # Docker management web UI
├── infrastructure/ # Additional infrastructure (Pi-hole, etc.)
├── dashboards/ # Dashboard services (Homepage, Homarr) ├── dashboards/ # Dashboard services (Homepage, Homarr)
├── vpn/ # VPN services (Gluetun, qBittorrent)
├── media/ # Media services (Plex, Jellyfin, etc.) ├── media/ # Media services (Plex, Jellyfin, etc.)
├── media-management/ # *arr services (Sonarr, Radarr, etc.)
├── monitoring/ # Observability stack (Prometheus, Grafana, etc.) ├── monitoring/ # Observability stack (Prometheus, Grafana, etc.)
├── alternatives/ # Alternative services (Authentik, etc.)
├── homeassistant/ # Home Assistant stack ├── homeassistant/ # Home Assistant stack
├── nextcloud/ # Nextcloud stack ├── productivity/ # Productivity tools (Nextcloud, Gitea, etc.)
├── productivity/ # Productivity tools ├── utilities/ # Utility services (Duplicati, FreshRSS, etc.)
├── utilities/ # Utility services ├── wikis/ # Mediawiki, Dokuwiki, Bookstacks
## ⚠️ Important: Core Services First └── vpn/ # VPN services (Gluetun, qBittorrent)
**Before deploying any other stacks, ensure the `core/` services are running:**
- **Traefik**: Reverse proxy and SSL termination
- **Authelia**: Single sign-on authentication
- **DuckDNS**: Dynamic DNS for domain resolution
These services provide the foundation for all other services. Most stacks depend on Traefik for routing and Authelia for authentication.
### Quick Start Core Services
```bash
cd core
cp .env.template .env # Edit with your values
cp docker-compose.yml.template docker-compose.yml # Or use the pre-configured version
docker compose up -d
``` ```
### Starting Services ## Multi-Server Architecture
Start all services in a stack: EZ-Homelab supports two deployment models:
### **Single Server:**
- Core + all other stacks on one machine
- Simplest setup for beginners
### **Multi-Server:**
- **Core Server**: DuckDNS, Traefik (multi-provider), Authelia
- **Remote Servers**: Traefik (local-only), Sablier (local-only), application services
- All services accessed through unified domain
See [docs/Ondemand-Remote-Services.md](../docs/Ondemand-Remote-Services.md) for multi-server setup.
## Deployment
Use the unified setup script:
```bash ```bash
cd docker-compose/core && docker compose up -d cd ~/EZ-Homelab
./scripts/ez-homelab.sh
``` ```
Start a specific service: ## Single Server Traefik service labels
```bash
cd docker-compose/vpn && docker compose up -d gluetun
```
### Stopping Services
Stop all services in a stack:
```bash
cd docker-compose/core && docker compose down
```
Stop a specific service:
```bash
cd docker-compose/vpn && docker compose stop qbittorrent
```
### Viewing Status
Check running services:
```bash
docker compose -f docker-compose/media.yml ps
```
View logs:
```bash
docker compose -f docker-compose/media.yml logs -f plex
```
### Updating Services
Pull latest images:
```bash
docker compose -f docker-compose/media.yml pull
```
Update a specific service:
```bash
docker compose -f docker-compose/media.yml pull plex
docker compose -f docker-compose/media.yml up -d plex
```
## Networks
All services connect to a shared bridge network called `homelab-network`. Create it once:
```bash
docker network create homelab-network
```
Some services may use additional networks for security isolation:
- `monitoring-network` - For monitoring stack
- `database-network` - For database isolation
- `media-network` - For media services
Create them as needed:
```bash
docker network create monitoring-network
docker network create database-network
docker network create media-network
```
## Environment Variables
Create a `.env` file in the root of your homelab directory with common variables:
```bash
# .env
PUID=1000
PGID=1000
TZ=America/New_York
USERDIR=/home/username/homelab
DATADIR=/mnt/data
```
Never commit `.env` files to git! Use `.env.example` as a template instead.
## Labels
### To enable Authelia SSO
```yaml
```
### Traefik routing labels
If Traekif is on the same server add these labels.
```yaml
```
>If Traefik is on a seperate server, don't use traekfik labels in compose files, use an external host yaml file.
### Sablier middleware labels
Add these labels to enable ondemand functionality.
```yaml ```yaml
services:
myservice:
labels: labels:
- sablier.enable=true # TRAEFIK CONFIGURATION
- sablier.group=<server>-<service name> # ==========================================
- sablier.start-on-demand=true # Service metadata
- "com.centurylinklabs.watchtower.enable=true"
- "homelab.category=category-name"
- "homelab.description=Brief service description"
# Traefik labels
- "traefik.enable=true"
# Router configuration
- "traefik.http.routers.myservice.rule=Host(`myservice.${DOMAIN}`)"
- "traefik.http.routers.myservice.entrypoints=websecure"
- "traefik.http.routers.myservice.tls.certresolver=letsencrypt"
- "traefik.http.routers.myservice.middlewares=authelia@docker" # SSO (remove to disable)
# Service configuration
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
# Sablier configuration (lazy loading)
- "sablier.enable=true"
- "sablier.group=${SERVER_HOSTNAME}-myservice"
- "sablier.start-on-demand=true"
```
## Multi-Server Traefik
### On Core Server
## On Remote Server
### Disabling SSO (Media Servers)
Remove or comment the authelia middleware line:
```yaml
# SSO enabled (default):
- "traefik.http.routers.myservice.middlewares=authelia@docker"
# SSO disabled (for Plex, Jellyfin, etc.):
# - "traefik.http.routers.myservice.middlewares=authelia@docker"
```
### Disabling Lazy Loading (Always-On Services)
Remove Sablier labels and use `restart: unless-stopped`:
```yaml
services:
myservice:
restart: unless-stopped # Always running
# No sablier labels
``` ```
## Best Practices ## Best Practices
@@ -171,6 +127,7 @@ services:
restart: unless-stopped restart: unless-stopped
networks: networks:
- homelab-network - homelab-network
- traefik-network
ports: ports:
- "host_port:container_port" - "host_port:container_port"
volumes: volumes:
@@ -197,77 +154,3 @@ networks:
homelab-network: homelab-network:
external: true external: true
``` ```
## Troubleshooting
### Service won't start
1. Check logs: `docker compose -f file.yml logs service-name`
2. Validate config: `docker compose -f file.yml config`
3. Check for port conflicts: `sudo netstat -tlnp | grep PORT`
4. Verify volumes exist and have correct permissions
### Permission errors
1. Ensure PUID and PGID match your user: `id -u` and `id -g`
2. Fix directory ownership: `sudo chown -R 1000:1000 ./config/service-name`
### Network issues
1. Verify network exists: `docker network ls`
2. Check service is connected: `docker network inspect homelab-network`
3. Test connectivity: `docker compose exec service1 ping service2`
## Migration from Docker Run
If you have services running via `docker run`, migrate them to compose:
1. Get current configuration:
```bash
docker inspect container-name > container-config.json
```
2. Convert to compose format (extract image, ports, volumes, environment)
3. Test the compose configuration
4. Stop old container:
```bash
docker stop container-name
docker rm container-name
```
5. Start with compose:
```bash
docker compose -f file.yml up -d
```
## Backup Strategy
Regular backups are essential:
```bash
# Backup compose files (already in git)
git add docker-compose/*.yml
git commit -m "Update compose configurations"
# Backup volumes
docker run --rm \
-v volume-name:/data \
-v $(pwd)/backups:/backup \
busybox tar czf /backup/volume-name-$(date +%Y%m%d).tar.gz /data
# Backup config directories
tar czf backups/config-$(date +%Y%m%d).tar.gz config/
```
## Getting Help
- Check the [Docker Guidelines](../docs/docker-guidelines.md) for detailed documentation
- Review the [GitHub Copilot Instructions](../.github/copilot-instructions.md) for AI assistance
- Consult service-specific documentation in `config/service-name/README.md`
## Examples
See the example compose files in this directory:
- `infrastructure.yml` - Essential services like reverse proxy
- `media.yml` - Media server stack
- `monitoring.yml` - Observability and monitoring
- `development.yml` - Development environments and tools

View File

@@ -1,27 +1,65 @@
# Core Infrastructure Services # Core Infrastructure Services
This directory contains the core infrastructure services that form the foundation of the homelab. These services should always be running and are critical for the operation of other services. This directory contains the core infrastructure services that form the foundation of the homelab. These services should be deployed **on the main server only** and are critical for the operation of all other services across all servers.
## Services ## Services
### DuckDNS
- **Purpose**: Dynamic DNS service for domain resolution and wildcard SSL certificates
- **Subdomain**: Configurable via environment variables
- **Token**: Configured in environment variables
- **SSL Certificates**: Generates wildcard cert used by all services on all servers
- **Deploy**: Core server only
### Traefik (v3) ### Traefik (v3)
- **Purpose**: Reverse proxy and SSL termination - **Purpose**: Reverse proxy and SSL termination with multi-server routing
- **Ports**: 80 (HTTP), 443 (HTTPS), 8080 (Dashboard) - **Ports**: 80 (HTTP), 443 (HTTPS), 8080 (Dashboard)
- **Configuration**: Located in `traefik/config/traefik.yml` - **Configuration**: Located in `traefik/config/traefik.yml`
- **SSL**: Let's Encrypt with DNS-01 challenge (configurable provider) - **Multi-Server**: Discovers services on all servers via Docker providers
- **SSL**: Let's Encrypt with DNS-01 challenge (wildcard certificate)
- **Dashboard**: Available at configured domain - **Dashboard**: Available at configured domain
- **Deploy**: Core server (multi-provider), Remote servers (local-only)
**Note**: Sablier has been moved to its own stack (`/opt/stacks/sablier/`) and should be deployed on each server individually. See [Sablier documentation](../../docs/service-docs/sablier.md) for details.
### Authelia (v4.37.5) ### Authelia (v4.37.5)
- **Purpose**: Single sign-on authentication service - **Purpose**: Single sign-on authentication service for all services across all servers
- **Port**: 9091 (internal) - **Port**: 9091 (internal)
- **Access**: Configured authentication domain - **Access**: Configured authentication domain
- **Configuration**: Located in `authelia/config/` - **Configuration**: Located in `authelia/config/`
- **Database**: SQLite database in `authelia/config/db.sqlite3` - **Database**: SQLite database in `authelia/config/db.sqlite3`
- **Deploy**: Core server only
### DuckDNS ### DuckDNS
- **Purpose**: Dynamic DNS service for domain resolution - **Purpose**: Dynamic DNS service for domain resolution and wildcard SSL certificates
- **Subdomain**: Configurable via environment variables - **Subdomain**: Configurable via environment variables
- **Token**: Configured in environment variables - **Token**: Configured in environment variables
- **SSL Certificates**: Generates wildcard cert used by all services on all servers
- **Deploy**: Core server only
## Multi-Server Architecture
The core stack on the main server provides centralized services for the entire homelab:
**Core Server Responsibilities:**
- Receives all external traffic (ports 80/443 forwarded from router)
- Runs DuckDNS for domain management and SSL certificates
- Runs Authelia for centralized authentication
- Runs multi-provider Traefik that discovers services on all servers
- Generates shared CA for Docker TLS communication
**Remote Server Setup:**
- Remote servers run their own Traefik instance (local Docker provider only)
- Remote servers run their own Sablier instance (local container management)
- Remote servers expose Docker API on port 2376 with TLS
- Core server Traefik connects to remote Docker APIs to discover services
- No port forwarding needed on remote servers
**Service Access:**
- All services accessible via: `https://service.yourdomain.duckdns.org`
- Core Traefik routes to appropriate server (local or remote)
- Single wildcard SSL certificate used for all services
- Authelia provides SSO for all protected services
## ⚠️ Version Pinning & Breaking Changes ## ⚠️ Version Pinning & Breaking Changes
@@ -64,15 +102,22 @@ core/
│ ├── configuration.yml # Authelia main config │ ├── configuration.yml # Authelia main config
│ ├── users_database.yml # User credentials │ ├── users_database.yml # User credentials
│ └── db.sqlite3 # SQLite database │ └── db.sqlite3 # SQLite database
── traefik/ ── duckdns/
── config/ ── config/ # DuckDNS configuration
│ └── traefik.yml # Traefik static config ── traefik/
├── dynamic/ # Dynamic configurations ├── config/
── routes.yml ── traefik.yml # Traefik static config
│ ├── sablier.yml │ ├── dynamic/ # Dynamic configurations
── external-host-*.yml ── routes.yml
└── letsencrypt/ │ ├── sablier.yml
└── acme.json # SSL certificates └── external-host-*.yml # Remote server routing
│ └── letsencrypt/
│ └── acme.json # SSL certificates
└── shared-ca/ # TLS certificates for multi-server
├── ca.pem # Certificate Authority
├── ca-key.pem # CA private key
├── cert.pem # Client certificate
└── key.pem # Client key
``` ```
### Environment Variables (.env) ### Environment Variables (.env)
@@ -100,10 +145,12 @@ PGID=1000
4. Domain configured in DuckDNS 4. Domain configured in DuckDNS
### Startup Order ### Startup Order
1. `duckdns` - For DNS updates 1. `duckdns` - For DNS updates and SSL certificate generation
2. `traefik` - Reverse proxy 2. `traefik` - Reverse proxy (waits for SSL certificates)
3. `authelia` - Authentication service 3. `authelia` - Authentication service
**Note**: Sablier is now deployed separately in `/opt/stacks/sablier/` after core stack is running.
### Commands ### Commands
```bash ```bash
# Start all services # Start all services

View File

@@ -11,12 +11,12 @@ log:
theme: dark theme: dark
jwt_secret: 4f263cdfa9929d007551fd5a5a6b552f7e17127cc4bb425b375a8532631d527b6b591a560a784552a33767699391973799e7472b679e7f94fcf4aca2ce5b2efc jwt_secret: ${AUTHELIA_JWT_SECRET}
default_redirection_url: https://auth.kelinreij.duckdns.org default_redirection_url: https://auth.${DOMAIN}
totp: totp:
issuer: kelinreij.duckdns.org issuer: ${DOMAIN}
period: 30 period: 30
skew: 1 skew: 1
@@ -36,34 +36,34 @@ access_control:
rules: rules:
# Bypass Authelia for Jellyfin (allow app access) # Bypass Authelia for Jellyfin (allow app access)
- domain: jellyfin.kelinreij.duckdns.org - domain: jellyfin.${DOMAIN}
policy: bypass policy: bypass
# Bypass for Plex (allow app access) # Bypass for Plex (allow app access)
- domain: plex.kelinreij.duckdns.org - domain: plex.${DOMAIN}
policy: bypass policy: bypass
# Bypass for Home Assistant (has its own auth) # Bypass for Home Assistant (has its own auth)
- domain: ha.kelinreij.duckdns.org - domain: ha.${DOMAIN}
policy: bypass policy: bypass
# Protected: All other services require authentication # Protected: All other services require authentication
- domain: "*.kelinreij.duckdns.org" - domain: "*.${DOMAIN}"
policy: one_factor policy: one_factor
# Two-factor for admin services (optional) # Two-factor for admin services (optional)
# - domain: # - domain:
# - "admin.kelinreij.duckdns.org" # - "admin.${DOMAIN}"
# - "portainer.kelinreij.duckdns.org" # - "portainer.${DOMAIN}"
# policy: two_factor # policy: two_factor
session: session:
name: authelia_session name: authelia_session
secret: 3ba018547a24dfd49ae55f23b5b75377ec93f5957707e2a669b0a49966df745a5b062eee3f7356e0abae21452915bdd30a32f404ec0a2a7a957c93a2fa2a94c8 secret: ${AUTHELIA_SESSION_SECRET}
expiration: 24h # Session expires after 24 hours expiration: 24h # Session expires after 24 hours
inactivity: 24h # Session expires after 24 hours of inactivity inactivity: 24h # Session expires after 24 hours of inactivity
remember_me_duration: 1M remember_me_duration: 1M
domain: kelinreij.duckdns.org domain: ${DOMAIN}
regulation: regulation:
max_retries: 3 max_retries: 3
@@ -71,7 +71,7 @@ regulation:
ban_time: 5m ban_time: 5m
storage: storage:
encryption_key: dd23db430500eb630e469d5cf0f77dd597649bd4d1a90c02ad673286d8eb9aa8f55435655435d40033751003fc764a173944dbc3ad89d57330e185269792a4b7 encryption_key: ${AUTHELIA_STORAGE_ENCRYPTION_KEY}
local: local:
path: /config/db.sqlite3 path: /config/db.sqlite3

View File

@@ -1,87 +0,0 @@
# Authelia Configuration
# Copy to /opt/stacks/authelia/configuration.yml
# IMPORTANT: Replace '${DOMAIN}' with your actual DuckDNS domain
server:
host: 0.0.0.0
port: 9091
log:
level: info
theme: dark
jwt_secret: ${AUTHELIA_JWT_SECRET}
default_redirection_url: https://auth.${DOMAIN}
totp:
issuer: ${DOMAIN}
period: 30
skew: 1
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 1
key_length: 32
salt_length: 16
memory: 1024
parallelism: 8
access_control:
default_policy: deny
rules:
# Bypass Authelia for Jellyfin (allow app access)
- domain: jellyfin.${DOMAIN}
policy: bypass
# Bypass for Plex (allow app access)
- domain: plex.${DOMAIN}
policy: bypass
# Bypass for Home Assistant (has its own auth)
- domain: ha.${DOMAIN}
policy: bypass
# Bypass for development services (they have their own auth or setup)
- domain: pgadmin.${DOMAIN}
policy: bypass
- domain: gitlab.${DOMAIN}
policy: bypass
# Protected: All other services require authentication
- domain: "*.${DOMAIN}"
policy: one_factor
# Two-factor for admin services (optional)
# - domain:
# - "admin.${DOMAIN}"
# - "portainer.${DOMAIN}"
# policy: two_factor
session:
name: authelia_session
secret: ${AUTHELIA_SESSION_SECRET}
expiration: 24h # Session expires after 24 hours
inactivity: 24h # Session expires after 24 hours of inactivity
remember_me_duration: 1M
domain: ${DOMAIN}
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
storage:
encryption_key: ${AUTHELIA_STORAGE_ENCRYPTION_KEY}
local:
path: /data/db.sqlite3
notifier:
# File-based notifications (for development/testing)
filesystem:
filename: /data/notification.txt

View File

@@ -1,12 +0,0 @@
###############################################################
# Users Database #
###############################################################
users:
admin:
displayname: "admin"
password: "generate-with-openssl-rand-hex-64"
email: admin@example.com
groups:
- admins
- users

View File

@@ -1,12 +0,0 @@
###############################################################
# Users Database #
###############################################################
users:
${AUTHELIA_ADMIN_USER}:
displayname: "${AUTHELIA_ADMIN_USER}"
password: "${AUTHELIA_ADMIN_PASSWORD_HASH}"
email: ${AUTHELIA_ADMIN_EMAIL}
groups:
- admins
- users

View File

@@ -1,19 +1,12 @@
# yamllint disable rule:line-length
---
############################################################### ###############################################################
# Users Database # # Users Database #
############################################################### ###############################################################
# This file can be used if you do not have an LDAP set up.
users: users:
authelia: ${AUTHELIA_ADMIN_USER}:
disabled: false displayname: "${AUTHELIA_ADMIN_USER}"
displayname: "Test User" password: "${AUTHELIA_ADMIN_PASSWORD_HASH}"
password: "$argon2id$v=19$m=32768,t=1,p=8$eUhVT1dQa082YVk2VUhDMQ$E8QI4jHbUBt3EdsU1NFDu4Bq5jObKNx7nBKSn1EYQxk" # Password is 'authelia' email: ${AUTHELIA_ADMIN_EMAIL}
email: authelia@authelia.com
groups: groups:
- admins - admins
- dev - users
...
# yamllint enable rule:line-length

View File

@@ -15,8 +15,8 @@ services:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- TZ=America/New_York - TZ=America/New_York
- SUBDOMAINS=yourdomain - SUBDOMAINS=${DUCKDNS_SUBDOMAINS}
- TOKEN=your-duckdns-token - TOKEN=${DUCKDNS_TOKEN}
volumes: volumes:
- ./duckdns/config:/config - ./duckdns/config:/config
networks: networks:
@@ -29,7 +29,7 @@ services:
restart: unless-stopped restart: unless-stopped
command: ['--configFile=/config/traefik.yml'] command: ['--configFile=/config/traefik.yml']
environment: environment:
- DUCKDNS_TOKEN=your-duckdns-token - DUCKDNS_TOKEN=${DUCKDNS_TOKEN}
ports: ports:
- 80:80 - 80:80
- 443:443 - 443:443
@@ -48,7 +48,7 @@ services:
- 'homelab.category=core' - 'homelab.category=core'
- 'homelab.description=Reverse proxy and SSL termination' - 'homelab.description=Reverse proxy and SSL termination'
- 'traefik.enable=true' - 'traefik.enable=true'
- 'traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.duckdns.org`)' - 'traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)'
- 'traefik.http.routers.traefik.entrypoints=websecure' - 'traefik.http.routers.traefik.entrypoints=websecure'
- 'traefik.http.routers.traefik.tls.certresolver=letsencrypt' - 'traefik.http.routers.traefik.tls.certresolver=letsencrypt'
- 'traefik.http.routers.traefik.middlewares=authelia@docker' - 'traefik.http.routers.traefik.middlewares=authelia@docker'
@@ -80,13 +80,13 @@ services:
# If Traefik is on a remote server: these labels are NOT USED; # If Traefik is on a remote server: these labels are NOT USED;
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- 'traefik.enable=true' - 'traefik.enable=true'
- 'traefik.http.routers.authelia.rule=Host(`auth.yourdomain.duckdns.org`)' - 'traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)'
- 'traefik.http.routers.authelia.entrypoints=websecure' - 'traefik.http.routers.authelia.entrypoints=websecure'
- 'traefik.http.routers.authelia.tls.certresolver=letsencrypt' - 'traefik.http.routers.authelia.tls.certresolver=letsencrypt'
- 'traefik.http.routers.authelia.service=authelia' - 'traefik.http.routers.authelia.service=authelia'
- 'traefik.http.services.authelia.loadbalancer.server.port=9091' - 'traefik.http.services.authelia.loadbalancer.server.port=9091'
# Authelia forward auth middleware configuration # Authelia forward auth middleware configuration
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.yourdomain.duckdns.org/' - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.${DOMAIN}/'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=X-Secret' - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=X-Secret'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
@@ -96,7 +96,7 @@ networks:
x-dockge: x-dockge:
urls: urls:
- https://auth.yourdomain.duckdns.org - https://auth.${DOMAIN}
- http://192.168.1.100:9091 - http://${SERVER_IP}:9091
- https://traefik.yourdomain.duckdns.org - https://traefik.${DOMAIN}
- http://192.168.1.100:8080 - http://${SERVER_IP}:8080

View File

@@ -2,7 +2,7 @@ http:
routers: routers:
# Individual Services # Individual Services
homeassistant: homeassistant:
rule: "Host(`hass.kelinreij.duckdns.org`)" rule: "Host(`hass.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: homeassistant service: homeassistant

View File

@@ -2,7 +2,7 @@ http:
routers: routers:
# Remote Server Services (your-remote-server) # Remote Server Services (your-remote-server)
dockge-your-remote-server: dockge-your-remote-server:
rule: "Host(`dockge.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`dockge.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: dockge-your-remote-server service: dockge-your-remote-server
@@ -12,7 +12,7 @@ http:
- authelia@docker - authelia@docker
dozzle-your-remote-server: dozzle-your-remote-server:
rule: "Host(`dozzle.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`dozzle.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: dozzle-your-remote-server service: dozzle-your-remote-server
@@ -22,7 +22,7 @@ http:
- authelia@docker - authelia@docker
glances-your-remote-server: glances-your-remote-server:
rule: "Host(`glances.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`glances.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: glances-your-remote-server service: glances-your-remote-server
@@ -32,7 +32,7 @@ http:
- authelia@docker - authelia@docker
backrest-your-remote-server: backrest-your-remote-server:
rule: "Host(`backrest.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`backrest.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: backrest-your-remote-server service: backrest-your-remote-server
@@ -42,7 +42,7 @@ http:
- authelia@docker - authelia@docker
duplicati-your-remote-server: duplicati-your-remote-server:
rule: "Host(`duplicati.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`duplicati.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: duplicati-your-remote-server service: duplicati-your-remote-server
@@ -52,7 +52,7 @@ http:
- authelia@docker - authelia@docker
homepage-your-remote-server: homepage-your-remote-server:
rule: "Host(`homepage.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`homepage.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: homepage-your-remote-server service: homepage-your-remote-server
@@ -62,7 +62,7 @@ http:
- authelia@docker - authelia@docker
homarr-your-remote-server: homarr-your-remote-server:
rule: "Host(`homarr.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`homarr.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: homarr-your-remote-server service: homarr-your-remote-server
@@ -72,7 +72,7 @@ http:
- authelia@docker - authelia@docker
grafana-your-remote-server: grafana-your-remote-server:
rule: "Host(`grafana.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`grafana.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: grafana-your-remote-server service: grafana-your-remote-server
@@ -82,7 +82,7 @@ http:
- authelia@docker - authelia@docker
prometheus-your-remote-server: prometheus-your-remote-server:
rule: "Host(`prometheus.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`prometheus.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: prometheus-your-remote-server service: prometheus-your-remote-server
@@ -92,7 +92,7 @@ http:
- authelia@docker - authelia@docker
uptime-kuma-your-remote-server: uptime-kuma-your-remote-server:
rule: "Host(`status.your-remote-server.kelinreij.duckdns.org`)" rule: "Host(`status.your-remote-server.yourdomain.duckdns.org`)"
entryPoints: entryPoints:
- websecure - websecure
service: uptime-kuma-your-remote-server service: uptime-kuma-your-remote-server

View File

@@ -3,7 +3,7 @@ http:
middlewares: middlewares:
authelia: authelia:
forwardauth: forwardauth:
address: http://authelia:9091/api/verify?rd=https://auth.kelinreij.duckdns.org/ address: http://authelia:9091/api/verify?rd=https://auth.yourdomain.duckdns.org/
authResponseHeaders: authResponseHeaders:
- X-Secret - X-Secret
trustForwardHeader: true trustForwardHeader: true

View File

@@ -27,7 +27,7 @@ entryPoints:
certificatesResolvers: certificatesResolvers:
letsencrypt: letsencrypt:
acme: acme:
email: admin@example.com # Your email for Let's Encrypt notifications email: ${DEFAULT_EMAIL} # Your email for Let's Encrypt notifications
caServer: https://acme-v02.api.letsencrypt.org/directory # Use staging for testing caServer: https://acme-v02.api.letsencrypt.org/directory # Use staging for testing
storage: /letsencrypt/acme.json storage: /letsencrypt/acme.json
# DNS challenge - For wildcard certificates (*.yourdomain.duckdns.org) # DNS challenge - For wildcard certificates (*.yourdomain.duckdns.org)

View File

@@ -60,7 +60,7 @@ services:
# Homarr - Modern dashboard # Homarr - Modern dashboard
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
homarr: homarr:
image: ghcr.io/ajnart/homarr:latest image: ghcr.io/homarr-labs/homarr:latest
deploy: deploy:
resources: resources:
limits: limits:
@@ -84,6 +84,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
- TZ=America/New_York - TZ=America/New_York
- SECRET_ENCRYPTION_KEY=8830c9434b05ebfe3e31340c685fea63446ab3f635c4fad68370006949ed30df
healthcheck: healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:7575/'] test: ['CMD', 'curl', '-f', 'http://localhost:7575/']
interval: 30s interval: 30s

View File

@@ -4,253 +4,253 @@
- Dashboards: - Dashboards:
- Homepage: - Homepage:
icon: homepage.png icon: homepage.png
href: https://homepage.kelinreij.duckdns.org href: https://homepage.yourdomain.duckdns.org
description: Hosted on Raspberry Pi description: Hosted on Raspberry Pi
- Homarr: - Homarr:
icon: homarr.png icon: homarr.png
href: https://homarr.kelinreij.duckdns.org href: https://homarr.yourdomain.duckdns.org
description: Alternative Dashboard description: Alternative Dashboard
- Dockge - jasper: - Dockge - jasper:
icon: dockge.png icon: dockge.png
href: https://jasper.kelinreij.duckdns.org href: https://jasper.yourdomain.duckdns.org
description: Main Server description: Main Server
- Dockge - your-remote-server : - Dockge - your-remote-server :
icon: dockge.png icon: dockge.png
href: https://your-remote-server .kelinreij.duckdns.org href: https://your-remote-server .yourdomain.duckdns.org
description: Raspberry Pi Authentication Server description: Raspberry Pi Authentication Server
- Core: - Core:
- Traefik: - Traefik:
icon: traefik.png icon: traefik.png
href: https://traefik.kelinreij.duckdns.org href: https://traefik.yourdomain.duckdns.org
description: Reverse Proxy & SSL description: Reverse Proxy & SSL
- Authelia: - Authelia:
icon: authelia.png icon: authelia.png
href: https://auth.kelinreij.duckdns.org href: https://auth.yourdomain.duckdns.org
description: Authentication SSO Portal description: Authentication SSO Portal
- Pi-hole: - Pi-hole:
icon: pi-hole.png icon: pi-hole.png
href: https://pihole.kelinreij.duckdns.org href: https://pihole.yourdomain.duckdns.org
description: Network-wide Ad Blocking description: Network-wide Ad Blocking
- Monitoring Stack: - Monitoring Stack:
- Dozzle: - Dozzle:
icon: dozzle.png icon: dozzle.png
href: https://dozzle.jasper.kelinreij.duckdns.org href: https://dozzle.jasper.yourdomain.duckdns.org
description: jasper - Real-time Log Viewer description: jasper - Real-time Log Viewer
- Dozzle: - Dozzle:
icon: dozzle.png icon: dozzle.png
href: https://dozzle.your-remote-server .kelinreij.duckdns.org href: https://dozzle.your-remote-server .yourdomain.duckdns.org
description: your-remote-server - Real-time Log Viewer description: your-remote-server - Real-time Log Viewer
- Glances - jasper: - Glances - jasper:
icon: glances.png icon: glances.png
href: https://glances.jasper.kelinreij.duckdns.org href: https://glances.jasper.yourdomain.duckdns.org
description: jasper - System Monitoring description: jasper - System Monitoring
- Glances - your-remote-server : - Glances - your-remote-server :
icon: glances.png icon: glances.png
href: https://glances.your-remote-server .kelinreij.duckdns.org href: https://glances.your-remote-server .yourdomain.duckdns.org
description: your-remote-server - System Monitoring description: your-remote-server - System Monitoring
- Uptime Kuma: - Uptime Kuma:
icon: uptime-kuma.png icon: uptime-kuma.png
href: https://uptime-kuma.kelinreij.duckdns.org href: https://uptime-kuma.yourdomain.duckdns.org
description: Uptime Monitoring description: Uptime Monitoring
- Media: - Media:
- Jellyfin: - Jellyfin:
icon: jellyfin.png icon: jellyfin.png
href: https://jellyfin.kelinreij.duckdns.org href: https://jellyfin.yourdomain.duckdns.org
description: Open Source Media Server description: Open Source Media Server
- Jellyseerr: - Jellyseerr:
icon: jellyseerr.png icon: jellyseerr.png
href: https://jellyseerr.kelinreij.duckdns.org href: https://jellyseerr.yourdomain.duckdns.org
description: Media Request Manager description: Media Request Manager
- Calibre-Web: - Calibre-Web:
icon: calibre-web.png icon: calibre-web.png
href: https://calibre.kelinreij.duckdns.org href: https://calibre.yourdomain.duckdns.org
description: Ebook Library description: Ebook Library
- Media Management: - Media Management:
- Sonarr: - Sonarr:
icon: sonarr.png icon: sonarr.png
href: https://sonarr.kelinreij.duckdns.org href: https://sonarr.yourdomain.duckdns.org
description: TV Shows Automation description: TV Shows Automation
- Radarr: - Radarr:
icon: radarr.png icon: radarr.png
href: https://radarr.kelinreij.duckdns.org href: https://radarr.yourdomain.duckdns.org
description: Movies Automation description: Movies Automation
- Prowlarr: - Prowlarr:
icon: prowlarr.png icon: prowlarr.png
href: https://prowlarr.kelinreij.duckdns.org href: https://prowlarr.yourdomain.duckdns.org
description: Indexer Manager description: Indexer Manager
- Readarr: - Readarr:
icon: readarr.png icon: readarr.png
href: https://readarr.kelinreij.duckdns.org href: https://readarr.yourdomain.duckdns.org
description: Books Automation description: Books Automation
- Lidarr: - Lidarr:
icon: lidarr.png icon: lidarr.png
href: https://lidarr.kelinreij.duckdns.org href: https://lidarr.yourdomain.duckdns.org
description: Music Automation description: Music Automation
- Mylar3: - Mylar3:
icon: mylar.png icon: mylar.png
href: https://mylar.kelinreij.duckdns.org href: https://mylar.yourdomain.duckdns.org
description: Comics Manager description: Comics Manager
- Home Automation: - Home Automation:
- Home Assistant: - Home Assistant:
icon: home-assistant.png icon: home-assistant.png
href: https://hass.kelinreij.duckdns.org href: https://hass.yourdomain.duckdns.org
description: Home Automation Platform description: Home Automation Platform
- ESPHome: - ESPHome:
icon: esphome.png icon: esphome.png
href: https://esphome.kelinreij.duckdns.org href: https://esphome.yourdomain.duckdns.org
description: ESP Device Manager description: ESP Device Manager
- Node-RED: - Node-RED:
icon: node-red.png icon: node-red.png
href: https://nodered.kelinreij.duckdns.org href: https://nodered.yourdomain.duckdns.org
description: Flow-based Automation description: Flow-based Automation
- Zigbee2MQTT: - Zigbee2MQTT:
icon: zigbee2mqtt.png icon: zigbee2mqtt.png
href: https://zigbee.kelinreij.duckdns.org href: https://zigbee.yourdomain.duckdns.org
description: Zigbee Bridge description: Zigbee Bridge
- Mosquitto: - Mosquitto:
icon: mosquitto.png icon: mosquitto.png
href: https://mqtt.kelinreij.duckdns.org href: https://mqtt.yourdomain.duckdns.org
description: MQTT Broker description: MQTT Broker
- Productivity: - Productivity:
- Nextcloud: - Nextcloud:
icon: nextcloud.png icon: nextcloud.png
href: https://nextcloud.kelinreij.duckdns.org href: https://nextcloud.yourdomain.duckdns.org
description: Cloud Storage & Collaboration description: Cloud Storage & Collaboration
- Gitea: - Gitea:
icon: gitea.png icon: gitea.png
href: https://gitea.kelinreij.duckdns.org href: https://gitea.yourdomain.duckdns.org
description: Git Repository description: Git Repository
- Mealie: - Mealie:
icon: mealie.png icon: mealie.png
href: https://mealie.kelinreij.duckdns.org href: https://mealie.yourdomain.duckdns.org
description: Recipe Manager description: Recipe Manager
- WordPress: - WordPress:
icon: wordpress.png icon: wordpress.png
href: https://wordpress.kelinreij.duckdns.org href: https://wordpress.yourdomain.duckdns.org
description: CMS Platform description: CMS Platform
- Wikis: - Wikis:
- BookStack: - BookStack:
icon: bookstack.png icon: bookstack.png
href: https://bookstack.kelinreij.duckdns.org href: https://bookstack.yourdomain.duckdns.org
description: Wiki Platform description: Wiki Platform
- DokuWiki: - DokuWiki:
icon: dokuwiki.png icon: dokuwiki.png
href: https://dokuwiki.kelinreij.duckdns.org href: https://dokuwiki.yourdomain.duckdns.org
description: Simple Wiki description: Simple Wiki
- Mediawiki: - Mediawiki:
icon: mediawiki.png icon: mediawiki.png
href: https://mediawiki.kelinreij.duckdns.org href: https://mediawiki.yourdomain.duckdns.org
description: Collaborative Wiki description: Collaborative Wiki
- Development: - Development:
- VS Code Server: - VS Code Server:
icon: vscode.png icon: vscode.png
href: https://code.kelinreij.duckdns.org href: https://code.yourdomain.duckdns.org
description: Browser-based IDE description: Browser-based IDE
- Jupyter: - Jupyter:
icon: jupyter.png icon: jupyter.png
href: https://jupyter.kelinreij.duckdns.org href: https://jupyter.yourdomain.duckdns.org
description: Data Science Notebooks description: Data Science Notebooks
- Downloaders: - Downloaders:
- qBittorrent: - qBittorrent:
icon: qbittorrent.png icon: qbittorrent.png
href: https://qbit.kelinreij.duckdns.org href: https://qbit.yourdomain.duckdns.org
description: Torrent Client description: Torrent Client
- Transcoders: - Transcoders:
- Tdarr: - Tdarr:
icon: tdarr.png icon: tdarr.png
href: https://tdarr.kelinreij.duckdns.org href: https://tdarr.yourdomain.duckdns.org
description: Media Transcoding description: Media Transcoding
- Unmanic: - Unmanic:
icon: unmanic.png icon: unmanic.png
href: https://unmanic.kelinreij.duckdns.org href: https://unmanic.yourdomain.duckdns.org
description: Media Transcoder description: Media Transcoder
- Utilities: - Utilities:
- Vaultwarden: - Vaultwarden:
icon: vaultwarden.png icon: vaultwarden.png
href: https://vault.kelinreij.duckdns.org href: https://vault.yourdomain.duckdns.org
description: Password Manager description: Password Manager
- Formio: - Formio:
icon: mdi-form-select icon: mdi-form-select
href: https://formio.kelinreij.duckdns.org href: https://formio.yourdomain.duckdns.org
description: Form Builder description: Form Builder
- Backup: - Backup:
- Backrest: - Backrest:
icon: mdi-backup-restore icon: mdi-backup-restore
href: https://backrest.kelinreij.duckdns.org href: https://backrest.yourdomain.duckdns.org
description: Backup Solution description: Backup Solution
- Duplicati: - Duplicati:
icon: duplicati.png icon: duplicati.png
href: https://duplicati.kelinreij.duckdns.org href: https://duplicati.yourdomain.duckdns.org
description: Backup Software description: Backup Software
- Metrics: - Metrics:
- Grafana: - Grafana:
icon: grafana.png icon: grafana.png
href: https://grafana.kelinreij.duckdns.org href: https://grafana.yourdomain.duckdns.org
description: Metrics Dashboard description: Metrics Dashboard
- Prometheus: - Prometheus:
icon: prometheus.png icon: prometheus.png
href: https://prometheus.kelinreij.duckdns.org href: https://prometheus.yourdomain.duckdns.org
description: Metrics Collection description: Metrics Collection
- cAdvisor: - cAdvisor:
icon: cadvisor.png icon: cadvisor.png
href: https://cadvisor.kelinreij.duckdns.org href: https://cadvisor.yourdomain.duckdns.org
description: Container Metrics description: Container Metrics
- Alternatives: - Alternatives:
- Portainer: - Portainer:
icon: portainer.png icon: portainer.png
href: https://portainer.kelinreij.duckdns.org href: https://portainer.yourdomain.duckdns.org
description: Container Management UI description: Container Management UI
- Authentik: - Authentik:
icon: authentik.png icon: authentik.png
href: https://authentik.kelinreij.duckdns.org href: https://authentik.yourdomain.duckdns.org
description: Alternative Auth Provider description: Alternative Auth Provider
- Plex: - Plex:
icon: plex.png icon: plex.png
href: https://plex.kelinreij.duckdns.org href: https://plex.yourdomain.duckdns.org
description: Media Server description: Media Server

View File

@@ -1,4 +1,4 @@
bind-addr: 127.0.0.1:8080 bind-addr: 127.0.0.1:8080
auth: password auth: password
password: 4d6c2b20e8d2c62be2512281 password: ${CODE_SERVER_PASSWORD}
cert: false cert: false

View File

@@ -154,7 +154,7 @@ services:
- 'traefik.http.routers.dozzle.tls=true' - 'traefik.http.routers.dozzle.tls=true'
- 'traefik.http.routers.dozzle.middlewares=authelia@docker' - 'traefik.http.routers.dozzle.middlewares=authelia@docker'
# Service configuration # Service configuration
- 'traefik.http.services.dozzle.loadbalancer.server.port=8085' - 'traefik.http.services.dozzle.loadbalancer.server.port=8080'
# Sablier configuration # Sablier configuration
- 'sablier.enable=true' - 'sablier.enable=true'
- 'sablier.group=jasper-dozzle' - 'sablier.group=jasper-dozzle'

View File

@@ -9,7 +9,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.sablier.rule=Host(`sablier.{{DUCKDNS_DOMAIN}}`)" - "traefik.http.routers.sablier.rule=Host(`sablier.${DOMAIN}`)"
- "traefik.http.routers.sablier.entrypoints=websecure" - "traefik.http.routers.sablier.entrypoints=websecure"
- "traefik.http.routers.sablier.tls=true" - "traefik.http.routers.sablier.tls=true"
- "traefik.http.routers.sablier.tls.certresolver=letsencrypt" - "traefik.http.routers.sablier.tls.certresolver=letsencrypt"

View File

@@ -0,0 +1,40 @@
# Traefik Service for Remote Servers
# This standalone Traefik instance runs on remote servers to discover local containers
# and communicate with the core Traefik on the core server via Docker TLS
services:
traefik:
# Local Traefik instance for container discovery on this remote server
image: traefik:v3
container_name: traefik
restart: unless-stopped
command:
- '--api.dashboard=true'
- '--api.insecure=false'
- '--providers.docker=true'
- '--providers.docker.exposedbydefault=false'
- '--providers.docker.network=traefik-network'
- '--providers.file.directory=/dynamic'
- '--providers.file.watch=true'
- '--log.level=INFO'
- '--accesslog=true'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
environment:
- TZ=America/New_York
ports:
- '8080:8080' # Dashboard (optional, for debugging)
volumes:
- ./config:/config
- ./dynamic:/dynamic
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- traefik-network
labels:
- 'homelab.category=infrastructure'
- 'homelab.description=Local reverse proxy for container discovery'
- 'traefik.enable=false' # This Traefik doesn't route itself
networks:
traefik-network:
external: true

View File

@@ -1,20 +0,0 @@
# EZ-Homelab Configuration Audit
## Purpose
Validate the configuration for the entire homelab.
For each server:
* Folder structure
* File permissions
* List all stacks in /opt
* List all compose files with relevant .env file
For standalone server:
* All of the above plus
*

View File

@@ -1,458 +0,0 @@
# On Demand Remote Services with Authelia, Sablier & Traefik
## Overview
This guide explains how to set up lazy-loading services on remote servers (like Raspberry Pi) that start automatically when accessed via Traefik. The core server runs Sablier, which connects to remote Docker daemons via TLS to manage container lifecycle.
## Prerequisites
- Core server with Traefik, Authelia, and Sablier deployed
- Remote server with Docker installed
- Shared TLS CA configured between core and remote servers
## Automated Setup
For new remote servers, use the automated script:
1. On the remote server, run `ez-homelab.sh` and select option 3 (Infrastructure Only)
2. When prompted, enter the core server IP for shared TLS CA
3. The script will automatically:
- Copy shared CA from core server via SSH
- Configure Docker TLS with shared certificates
- Generate server certificates signed by shared CA
- Set up Docker daemon for TLS on port 2376
**Important**: The script will fail if it cannot copy the shared CA from the core server. Ensure SSH access is configured between servers before running option 3.
## Manual Setup (if automated fails)
If the automated setup fails, manually configure TLS:
### On Core Server:
```bash
# Generate server certificates for remote server
cd /opt/stacks/core/shared-ca
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=<REMOTE_IP>" -new -key server-key.pem -out server.csr
echo "subjectAltName = DNS:<REMOTE_IP>,IP:<REMOTE_IP>,IP:127.0.0.1" > extfile.cnf
openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
```
### On Remote Server:
```bash
# Copy certificates
scp user@core-server:/opt/stacks/core/shared-ca/ca.pem /opt/stacks/core/shared-ca/
scp user@core-server:/opt/stacks/core/shared-ca/server-cert.pem /opt/stacks/core/shared-ca/
scp user@core-server:/opt/stacks/core/shared-ca/server-key.pem /opt/stacks/core/shared-ca/
# Update Docker daemon
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"tls": true,
"tlsverify": true,
"tlscacert": "/opt/stacks/core/shared-ca/ca.pem",
"tlscert": "/opt/stacks/core/shared-ca/server-cert.pem",
"tlskey": "/opt/stacks/core/shared-ca/server-key.pem"
}
EOF
sudo systemctl restart docker
```
## 4 Step Process for Adding Services
1. Add route & service in Traefik external hosts file
2. Add middleware in Sablier config file (sablier.yml)
3. Add labels to compose files on Remote Host
4. Restart services
## Required Information
```bash
<server> - the hostname of the remote server
<service> - the application/container name
<full domain> - the base url for your proxy host (my-subdomain.duckdns.org)
<ip address> - the ip address of the remote server
<port> - the external port exposed by the service
<service display name> - how it will appear on the now loading page
<group name> - use <service name> for a single service, or something descriptive for the group of services that will start together.
```
## Step 1: Add route & service in Traefik external hosts file
### In /opt/stacks/core/traefik/dynamic/external-host-server_name.yml
```yaml
http:
routers:
# Add a section under routers for each Route (Proxy Host)
<service>-<server>:
rule: "Host(`<service>.<full domain>`)"
entryPoints:
- websecure
service: <service>-<server>
tls:
certResolver: letsencrypt
middlewares:
- sablier-<server>-<service>@file
- authelia@docker # comment this line to disable SSO login
# next route goes here
# All Routes go above this line
# Services section defines each service used above
services:
<service>-<server>:
loadBalancer:
servers:
- url: "http://<ip address>:<port>"
passHostHeader: true
# next service goes here
```
## Step 2: Add middlware to sablier config file
### In /opt/stacks/core/traefik/dynamic/sablier.yml
```yaml
http:
middlwares:
# Add a section under middlewares for each Route (Proxy Host)
sablier-<server>-<service>:
plugin:
sablier:
sablierUrl: http://sablier-service:10000
group: <server>-<group name>
sessionDuration: 2m # Increase this for convience
ignoreUserAgent: curl # Don't wake the service for a curl command
dynamic:
displayName: <service display name>
theme: ghost # This can be changed
show-details-by-default: true # May want to disable for production
# Next middleware goes here
```
## Step 3: Add labels to compose files on Remote Host
## On the Remote Server
### Apply lables to the services in the compose files
```yaml
labels:
- sablier.enable=true
- sablier.group=<server>-<group name>
- sablier.start-on-demand=true
```
>**Note**:
Traefik & Authelia labels are not used in the compose file for Remote Hosts
## Step 4: Restart services
### On host system
```bash
docker restart traefik
docker restart sablier-service
```
### On the Remote Host
```bash
cd /opt/stacks/<service>
docker compose down && docker compose up -d
docker stop <service>
```
## Setup Complete
Access your service by the proxy url.
---
# Deployment Plan for Multi-Server Setup
This section provides a complete deployment plan for scenarios where the core infrastructure (Traefik, Authelia, Sablier) runs on one server, and application services run on remote servers. This setup enables centralized control and routing while maintaining service isolation.
## Architecture Overview
- **Core Server**: Hosts Traefik (reverse proxy), Authelia (SSO), Sablier (lazy loading controller)
- **Remote/Media Servers**: Host application containers controlled by Sablier
- **Communication**: TLS-secured Docker API calls between servers
## Prerequisites
- Both servers must be on the same network and able to communicate
- SSH access configured between servers (key-based or password authentication supported)
- Domain configured with DuckDNS or similar
- The EZ-Homelab script handles Docker TLS certificate generation on the core server and automatic copying to remote servers
- Basic understanding of Docker concepts (optional - script guides you through setup)
## Step 1: Configure Core Server
### On Core Server Only
1. **Install Docker** (if not already installed):
```bash
curl -fsSL https://get.docker.com | sh
usermod -aG docker $USER
systemctl enable docker
systemctl start docker
# Log out and back in for group changes
```
2. **Configure Firewall** (optional, for security):
```bash
sudo ufw allow 2376/tcp # For Docker API access from remote servers
sudo ufw --force enable
```
The EZ-Homelab script will automatically generate TLS certificates and configure Docker daemon TLS when you deploy the core infrastructure.
## Step 2: Configure Remote Servers
### On Each Remote Server
1. **Install Docker** (if not already installed):
```bash
curl -fsSL https://get.docker.com | sh
usermod -aG docker $USER
systemctl enable docker
systemctl start docker
# Log out and back in for group changes
```
2. **Configure Firewall** (optional, for security):
```bash
sudo ufw allow 2376/tcp # For Docker API access from core server
sudo ufw --force enable
```
The EZ-Homelab script will automatically copy TLS certificates from the core server and configure Docker daemon TLS when you run the infrastructure-only deployment.
## Certificate and Secret Sharing
The EZ-Homelab script automatically handles certificate and secret sharing for infrastructure-only deployments:
### Automatic Process (Recommended)
1. **On Remote Server**: Run `./scripts/ez-homelab.sh` and select option 3
2. **Script Actions**:
- Prompts for core server IP
- Tests SSH connectivity
- Copies shared CA and TLS certificates from core server
- Generates server-specific certificates signed by the shared CA
- Configures Docker daemon for TLS on port 2376
### Manual Process (Fallback)
If automatic sharing fails, manually share certificates:
1. **On Core Server**:
```bash
# Generate server certificates for remote server
cd /opt/stacks/core/shared-ca
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=<REMOTE_IP>" -new -key server-key.pem -out server.csr
echo "subjectAltName = DNS:<REMOTE_IP>,IP:<REMOTE_IP>,IP:127.0.0.1" > extfile.cnf
openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
```
2. **On Remote Server**:
```bash
# Copy certificates
scp user@core-server:/opt/stacks/core/shared-ca/ca.pem /opt/stacks/core/shared-ca/
scp user@core-server:/opt/stacks/core/shared-ca/server-cert.pem /opt/stacks/core/shared-ca/
scp user@core-server:/opt/stacks/core/shared-ca/server-key.pem /opt/stacks/core/shared-ca/
# Update Docker daemon
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"tls": true,
"tlsverify": true,
"tlscacert": "/opt/stacks/core/shared-ca/ca.pem",
"tlscert": "/opt/stacks/core/shared-ca/server-cert.pem",
"tlskey": "/opt/stacks/core/shared-ca/server-key.pem"
}
EOF
sudo systemctl restart docker
```
## Step 3: Deploy Core Infrastructure
### On Core Server
1. **Run the EZ-Homelab script** with core deployment:
```bash
cd ~/EZ-Homelab
./scripts/ez-homelab.sh
# Select option 1 (Default Setup) or 2 (Core Only)
```
The script will:
- Generate Authelia secrets automatically
- Generate shared TLS CA and certificates
- Configure Docker daemon TLS
- Deploy Traefik, Authelia, and Sablier
- Set up certificates for secure communication
2. **Verify core deployment**:
```bash
# Check services are running
docker ps --filter "label=com.docker.compose.project=core"
# Test Authelia access
curl -k https://auth.<your-domain>
```
## Step 4: Deploy Remote Infrastructure
### On Remote/Media Server
1. **Run the EZ-Homelab script** with infrastructure-only deployment:
```bash
cd ~/EZ-Homelab
./scripts/ez-homelab.sh
# Select option 3 (Infrastructure Only)
```
The script will automatically:
- Prompt for core server IP address
- Establish SSH connection to core server
- Copy shared CA and generate server-specific certificates
- Configure Docker daemon for TLS on port 2376
- Set up required networks and directories
2. **Manual certificate setup** (if automatic fails):
If SSH connection fails, follow the manual process in the Certificate and Secret Sharing section above.
## Step 5: Configure Sablier for Remote Control
### On Core Server
Sablier uses middleware configuration in Traefik's dynamic files to control remote Docker daemons. The middleware specifies the remote server connection details.
1. **Create Sablier middleware configuration**:
`/opt/stacks/core/traefik/dynamic/sablier.yml`
```yaml
http:
middlewares:
sablier-<remote_hostname>-<group>:
plugin:
sablier:
sablierUrl: http://sablier-service:10000
group: <remote_hostname>-<group>
sessionDuration: 2m
ignoreUserAgent: curl
dynamic:
displayName: "<Service Group Display Name>"
theme: ghost
show-details-by-default: true
```
2. **Restart Traefik** to load the middleware:
```bash
docker restart traefik
```
## Step 6: Deploy Application Services
### On Remote Server
1. **Deploy application stacks** with Sablier labels:
```yaml
# Example: /opt/stacks/media-management/docker-compose.yml
services:
sonarr:
labels:
- sablier.enable=true
- sablier.group=<REMOTE_HOSTNAME>-media
- sablier.start-on-demand=true
```
2. **Deploy and stop services** for lazy loading:
```bash
cd /opt/stacks/media-management
docker compose up -d
docker compose stop
```
## Step 7: Configure Traefik Routing
### On Core Server
Since Traefik cannot auto-discover labels from remote Docker hosts, use the file provider method:
1. **Create external host configuration**:
`/opt/stacks/core/traefik/dynamic/external-host-<remote_server>.yml`
```yaml
http:
routers:
sonarr-remote:
rule: "Host(`sonarr.<DOMAIN>`)"
entrypoints:
- websecure
service: sonarr-remote
tls:
certResolver: letsencrypt
middlewares:
- sablier-<remote_hostname>-media@file
- authelia@docker
services:
sonarr-remote:
loadBalancer:
servers:
- url: "http://<REMOTE_IP>:8989"
passHostHeader: true
```
2. **Restart Traefik**:
```bash
docker restart traefik
```
## Step 8: Verification and Testing
1. **Check Sablier connection**:
```bash
# On core server
docker logs sablier-service
# Should show groups from remote server
```
2. **Test lazy loading**:
- Access `https://sonarr.<DOMAIN>`
- Should show Sablier loading page
- Container should start on remote server
3. **Verify Traefik routes**:
```bash
curl -k https://localhost:8080/api/http/routers | jq
```
## Troubleshooting
- **TLS Connection Issues**: Check certificate validity and paths on both core and remote servers
- **Sablier Not Detecting Groups**: Verify middleware configuration in `/opt/stacks/core/traefik/dynamic/sablier.yml` and that remote services have correct Sablier labels
- **Traefik Routing Problems**: Check external host YAML syntax and that Traefik has reloaded configuration
- **Network Connectivity**: Ensure ports 2376 (Docker API), 80, 443 are open between servers
- **Certificate Sharing Failed**: Verify SSH access between servers and that the shared CA exists on core server
## Security Considerations
- TLS certificates expire after 365 days - monitor and renew
- Limit Docker API access to trusted networks
- Use strong firewall rules
- Regularly update all components
This setup provides centralized management with distributed execution, optimal for resource management and security.

View File

@@ -1,111 +0,0 @@
# Traefik Routing Quick Reference
## Variables (used throughout):
```yaml
${infrastructure}
${description}
${watchtower_enable}
${service}
${sso_enable}
${sablier_enable}
${traefik_enable}
${port}
```
## Compose file labels section
### Service metadata
```yaml
- homelab.category=${infrastructure}
- homelab.description=${description}
- com.centurylinklabs.watchtower.enable=${watchtower_enable}
```
### Traefik labels
>**Traefik labels** are used for services on the same machine
They are ignored when the service is on a different machine
```yaml
- "traefik.enable=${traefik_enable}"
- "traefik.http.routers.${service}.rule=Host(`${service}.${DOMAIN}`)"
- "traefik.http.routers.${service}.entrypoints=websecure"
- "traefik.http.routers.${service}.tls.certresolver=letsencrypt"
- "traefik.http.routers.${service}.middlewares=authelia@docker"
- "traefik.http.services.${service}.loadbalancer.server.port=${port}"
```
### Sablier lazy loading
```yaml
- sablier.enable=${sablier_enable}
- sablier.group=${SERVER_HOSTNAME}-${service}
- sablier.start-on-demand=true
```
## External Host Yml Files
>**Recomended**: use 1 yml file per host
### external-host-production.yml
```yaml
http:
# Routes for External Host Services
routers:
# External Service Routing Template
${service}-${SERVER_HOSTNAME}:
rule: "Host(`${service}.${DOMAIN}`)"
entryPoints:
- websecure
service: ${service}-${SERVER_HOSTNAME}
tls:
certResolver: letsencrypt
middlewares:
- sablier-${SERVER_HOSTNAME}-${service}@file
- authelia@docker
# Middleware Definitions
middlewares:
# Service Definitions
services:
${service}-${SERVER_HOSTNAME}:
loadBalancer:
servers:
- url: "http://${SERVER_IP}:${port}"
passHostHeader: true
```
## sablier.yml
```yaml
# Session duration set to 5m for testing. Increase to 30m for production.
http:
middlewares:
# Authelia SSO middleware
authelia:
forwardauth:
address: http://authelia:9091/api/verify?rd=https://auth.${DOMAIN}/
authResponseHeaders:
- X-Secret
trustForwardHeader: true
# Sablier enabled Service Template
sablier-${SERVER_HOSTNAME}-${service}:
plugin:
sablier:
sablierUrl: http://sablier-service:10000
group: ${SERVER_HOSTNAME}-${service}
sessionDuration: 5m
ignoreUserAgent: curl
dynamic:
displayName: ${service}
theme: ghost
show-details-by-default: true
```

View File

@@ -1,480 +0,0 @@
# Action Report: SSL Wildcard Certificate Setup
**Date:** January 12, 2026
**Status:** ✅ Completed Successfully
**Impact:** All homelab services now have valid Let's Encrypt SSL certificates
---
## Problem Statement
Services were showing "not secure" warnings in browsers despite Traefik being configured for Let's Encrypt certificates. Multiple simultaneous certificate requests were failing due to DNS challenge conflicts.
## Root Causes Identified
### 1. **Multiple Simultaneous Certificate Requests**
- **Issue:** Each service (dockge, dozzle, glances, pihole, authelia) had `traefik.http.routers.*.tls.certresolver=letsencrypt` labels
- **Impact:** Traefik attempted to request individual certificates for each subdomain simultaneously
- **Consequence:** DuckDNS DNS challenge can only handle ONE TXT record at `_acme-challenge.kelin-hass.duckdns.org` at a time
- **Result:** All certificate requests failed with "Incorrect TXT record" errors
### 2. **DNS TXT Record Conflicts**
- **Issue:** Multiple services tried to create different TXT records at the same DNS location
- **Example:**
- Service A creates: `_acme-challenge.kelin-hass.duckdns.org` = "token1"
- Service B overwrites: `_acme-challenge.kelin-hass.duckdns.org` = "token2"
- Let's Encrypt validates Service A but finds "token2" → validation fails
- **DuckDNS Limitation:** Can only maintain ONE TXT record per domain
### 3. **Authelia Configuration Error**
- **Issue:** Environment variable `AUTHELIA_NOTIFIER_SMTP_PASSWORD` was set without corresponding SMTP configuration
- **Impact:** Authelia crashed on startup with "please ensure only one of the 'smtp' or 'filesystem' notifier is configured"
- **Consequence:** Services requiring Authelia authentication were inaccessible
### 4. **Stale DNS Records**
- **Issue:** Old TXT records from failed attempts persisted in DNS
- **Impact:** New certificate attempts validated against old, incorrect TXT records
## Solution Implemented
### Phase 1: Identify Certificate Request Pattern
**Actions:**
1. Discovered Traefik logs at `/var/log/traefik/traefik.log` (not stdout)
2. Analyzed logs showing multiple simultaneous DNS-01 challenges
3. Confirmed DuckDNS TXT record conflicts
**Command Used:**
```bash
docker exec traefik tail -f /var/log/traefik/traefik.log
```
### Phase 2: Configure Wildcard Certificate
**Actions:**
1. Removed `certresolver` labels from all services except Traefik
2. Configured wildcard certificate on Traefik router only
3. Added DNS propagation skip for faster validation
**Changes Made:**
**File:** `/home/kelin/AI-Homelab/docker-compose/core.yml`
```yaml
# Traefik - Only service with certresolver
traefik:
labels:
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN}"
- "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN}"
# Authelia - No certresolver, just tls=true
authelia:
labels:
- "traefik.http.routers.authelia.tls=true"
```
**File:** `/home/kelin/AI-Homelab/docker-compose/infrastructure.yml`
```yaml
# All infrastructure services - No certresolver
dockge:
labels:
- "traefik.http.routers.dockge.tls=true"
dozzle:
labels:
- "traefik.http.routers.dozzle.tls=true"
glances:
labels:
- "traefik.http.routers.glances.tls=true"
pihole:
labels:
- "traefik.http.routers.pihole.tls=true"
```
**File:** `/opt/stacks/core/traefik/traefik.yml`
```yaml
certificatesResolvers:
letsencrypt:
acme:
email: kelinfoxy@gmail.com
storage: /acme.json
dnsChallenge:
provider: duckdns
disablePropagationCheck: true # Added to skip DNS propagation wait
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
```
### Phase 3: Clear DNS and Reset Certificates
**Actions:**
1. Stopped all services to clear DNS TXT records
2. Reset `acme.json` to force fresh certificate request
3. Waited 60 seconds for DNS to fully clear
4. Restarted services with wildcard-only configuration
**Commands Executed:**
```bash
# Stop services
cd /opt/stacks/core && docker compose down
# Reset certificate storage
rm /opt/stacks/core/traefik/acme.json
touch /opt/stacks/core/traefik/acme.json
chmod 600 /opt/stacks/core/traefik/acme.json
chown kelin:kelin /opt/stacks/core/traefik/acme.json
# Wait for DNS to clear
sleep 60
dig +short TXT _acme-challenge.kelin-hass.duckdns.org # Verified empty
# Deploy updated configuration
cp /home/kelin/AI-Homelab/docker-compose/core.yml /opt/stacks/core/docker-compose.yml
cd /opt/stacks/core && docker compose up -d
```
### Phase 4: Fix Authelia Configuration
**Issue Found:** Environment variable triggering SMTP configuration check
**File:** `/opt/stacks/core/docker-compose.yml`
**Removed:**
```yaml
environment:
- AUTHELIA_NOTIFIER_SMTP_PASSWORD=${SMTP_PASSWORD} # ❌ Removed
```
**Command:**
```bash
cd /opt/stacks/core && docker compose up -d authelia
```
### Phase 5: Fix Infrastructure Services
**Issue:** Missing `networks:` header in compose file
**File:** `/opt/stacks/infrastructure/infrastructure.yml`
**Fixed:**
```yaml
# Before (incorrect):
traefik-network:
external: true
# After (correct):
networks:
traefik-network:
external: true
homelab-network:
driver: bridge
dockerproxy-network:
driver: bridge
```
**Command:**
```bash
cd /opt/stacks/infrastructure && docker compose -f infrastructure.yml up -d
```
## Results
### Certificate Obtained Successfully ✅
**acme.json Contents:**
```json
{
"letsencrypt": {
"Account": {
"Email": "kelinfoxy@gmail.com",
"Registration": {
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/2958966636"
}
},
"Certificates": [
{
"domain": {
"main": "dockge.kelin-hass.duckdns.org"
}
},
{
"domain": {
"main": "kelin-hass.duckdns.org",
"sans": ["*.kelin-hass.duckdns.org"]
}
}
]
}
}
```
**Certificate Details:**
- **Subject:** CN=kelin-hass.duckdns.org
- **Issuer:** C=US, O=Let's Encrypt, CN=R12
- **Coverage:** Wildcard certificate covering all subdomains
- **File Size:** 23KB (up from 0 bytes)
### Services Status
All services running with valid SSL certificates:
| Service | Status | URL | Certificate |
|---------|--------|-----|-------------|
| Traefik | ✅ Up | https://traefik.kelin-hass.duckdns.org | Valid |
| Authelia | ✅ Up | https://auth.kelin-hass.duckdns.org | Valid |
| Dockge | ✅ Up | https://dockge.kelin-hass.duckdns.org | Valid |
| Dozzle | ✅ Up | https://dozzle.kelin-hass.duckdns.org | Valid |
| Glances | ✅ Up | https://glances.kelin-hass.duckdns.org | Valid |
| Pi-hole | ✅ Up | https://pihole.kelin-hass.duckdns.org | Valid |
## Best Practices & Prevention
### 1. ✅ Use Wildcard Certificates with DuckDNS
**Rule:** Only ONE service should request certificates with DuckDNS DNS challenge
**Configuration:**
```yaml
# ✅ CORRECT: Only Traefik requests wildcard cert
traefik:
labels:
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN}"
- "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN}"
# ✅ CORRECT: Other services just enable TLS
other-service:
labels:
- "traefik.http.routers.service.tls=true" # Uses wildcard automatically
# ❌ WRONG: Multiple services requesting certs
other-service:
labels:
- "traefik.http.routers.service.tls.certresolver=letsencrypt" # DON'T DO THIS
```
### 2. ✅ DuckDNS DNS Challenge Limitations
**Understand the Constraint:**
- DuckDNS can only maintain ONE TXT record at `_acme-challenge.kelin-hass.duckdns.org`
- Multiple simultaneous challenges WILL fail
- Use wildcard certificate to avoid this limitation
**Alternative Providers (if needed):**
- Cloudflare: Supports multiple simultaneous DNS challenges
- Route53: Supports multiple TXT records
- Use HTTP challenge if DNS challenge isn't required
### 3. ✅ Traefik Logging Configuration
**Enable File Logging for Debugging:**
**File:** `/opt/stacks/core/traefik/traefik.yml`
```yaml
log:
level: DEBUG # Use DEBUG for troubleshooting, INFO for production
filePath: /var/log/traefik/traefik.log # Easier to tail than docker logs
# Mount in docker-compose.yml:
volumes:
- /var/log/traefik:/var/log/traefik
```
**Useful Commands:**
```bash
# Monitor certificate acquisition
docker exec traefik tail -f /var/log/traefik/traefik.log | grep -E "acme|certificate|DNS"
# Check for errors
docker exec traefik tail -100 /var/log/traefik/traefik.log | grep -E "error|Unable"
# View specific domain
docker exec traefik tail -200 /var/log/traefik/traefik.log | grep "kelin-hass.duckdns.org"
```
### 4. ✅ Certificate Troubleshooting Workflow
**When certificates aren't working:**
```bash
# 1. Check acme.json status
cat /opt/stacks/core/traefik/acme.json | python3 -m json.tool | grep -A5 "Certificates"
# 2. Check certificate count
python3 -c "import json; d=json.load(open('/opt/stacks/core/traefik/acme.json')); print(f'Certificates: {len(d[\"letsencrypt\"][\"Certificates\"])}')"
# 3. Test certificate being served
echo | openssl s_client -connect auth.kelin-hass.duckdns.org:443 -servername auth.kelin-hass.duckdns.org 2>/dev/null | openssl x509 -noout -subject -issuer
# 4. Check DNS TXT records
dig +short TXT _acme-challenge.kelin-hass.duckdns.org
# 5. Check Traefik logs
docker exec traefik tail -50 /var/log/traefik/traefik.log
```
### 5. ✅ Environment Variable Hygiene
**Principle:** Only set environment variables that are actually used
**Example - Authelia:**
```yaml
# ✅ CORRECT: Only variables for configured features
environment:
- AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET}
- AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET}
- AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY}
# ❌ WRONG: SMTP variable without SMTP configuration
environment:
- AUTHELIA_NOTIFIER_SMTP_PASSWORD=${SMTP_PASSWORD} # Causes crash if SMTP not in config.yml
```
### 6. ✅ Docker Compose File Validation
**Before deploying:**
```bash
# Validate syntax
docker compose -f /path/to/file.yml config
# Check for common errors
grep -n "^ [a-z]" file.yml # Networks should have "networks:" header
```
### 7. ✅ Certificate Renewal Strategy
**Automatic Renewal:**
- Traefik automatically renews certificates 30 days before expiration
- Wildcard certificate covers all subdomains (no individual renewals needed)
- Monitor `acme.json` for certificate expiration dates
**Backup acme.json:**
```bash
# Regular backup (e.g., daily cron)
cp /opt/stacks/core/traefik/acme.json /opt/backups/acme.json.$(date +%Y%m%d)
# Keep last 7 days
find /opt/backups -name "acme.json.*" -mtime +7 -delete
```
## Key Learnings
### Technical Insights
1. **DuckDNS Limitation:** Single TXT record constraint requires wildcard certificate approach
2. **DNS Propagation:** `disablePropagationCheck: true` speeds up validation but relies on fast DNS updates
3. **Traefik Labels:** `tls=true` vs `tls.certresolver=letsencrypt` - use former for wildcard coverage
4. **Environment Variables:** Can trigger configuration validation even without corresponding config file entries
### Process Insights
1. **Log Discovery:** Traefik logs to files by default, not always visible via `docker logs`
2. **DNS Clearing:** Stopping services and waiting 60s ensures DNS records fully clear
3. **Incremental Debugging:** Monitor logs during certificate acquisition to catch issues early
4. **Configuration Synchronization:** Repository files must be copied to deployment locations
## Documentation Updates
### Files Modified
**Repository:**
- `/home/kelin/AI-Homelab/docker-compose/core.yml`
- `/home/kelin/AI-Homelab/docker-compose/infrastructure.yml`
**Deployed:**
- `/opt/stacks/core/docker-compose.yml`
- `/opt/stacks/core/traefik/traefik.yml`
- `/opt/stacks/core/traefik/acme.json`
- `/opt/stacks/infrastructure/infrastructure.yml`
### Configuration Templates
**Wildcard Certificate Template:**
```yaml
services:
traefik:
labels:
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN}"
- "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN}"
any-other-service:
labels:
- "traefik.http.routers.service.tls=true" # No certresolver!
```
## Future Recommendations
### Short-term (Next Week)
1. ✅ Monitor certificate auto-renewal (should happen automatically)
2. ✅ Test browser access from different devices to verify SSL
3. ✅ Update homelab documentation with wildcard certificate pattern
4. ⚠️ Consider adding certificate monitoring alerts
### Medium-term (Next Month)
1. Set up automated `acme.json` backups
2. Document certificate troubleshooting runbook
3. Consider migrating to Cloudflare if more services are added
4. Implement certificate expiration monitoring
### Long-term (Next Quarter)
1. Evaluate alternative DNS providers for better DNS challenge support
2. Consider setting up staging Let's Encrypt for testing
3. Implement centralized logging for all services
4. Add Prometheus/Grafana monitoring for SSL certificate expiration
## Quick Reference
### Emergency Certificate Reset
```bash
# 1. Stop all services
cd /opt/stacks/core && docker compose down
cd /opt/stacks/infrastructure && docker compose -f infrastructure.yml down
# 2. Reset acme.json
rm /opt/stacks/core/traefik/acme.json
touch /opt/stacks/core/traefik/acme.json
chmod 600 /opt/stacks/core/traefik/acme.json
# 3. Wait for DNS to clear
sleep 60
# 4. Restart
cd /opt/stacks/core && docker compose up -d
cd /opt/stacks/infrastructure && docker compose -f infrastructure.yml up -d
# 5. Monitor
docker exec traefik tail -f /var/log/traefik/traefik.log
```
### Verify Certificate Command
```bash
echo | openssl s_client -connect ${SUBDOMAIN}.kelin-hass.duckdns.org:443 -servername ${SUBDOMAIN}.kelin-hass.duckdns.org 2>/dev/null | openssl x509 -noout -subject -issuer -dates
```
### Check All Service Certificates
```bash
for subdomain in auth traefik dockge dozzle glances pihole; do
echo "=== $subdomain.kelin-hass.duckdns.org ==="
echo | openssl s_client -connect $subdomain.kelin-hass.duckdns.org:443 -servername $subdomain.kelin-hass.duckdns.org 2>/dev/null | openssl x509 -noout -subject -issuer
echo
done
```
---
## Summary
Successfully implemented wildcard SSL certificate for all homelab services using Let's Encrypt DNS challenge via DuckDNS. Key success factor was recognizing DuckDNS's limitation of one TXT record at a time and configuring Traefik to request a single wildcard certificate instead of individual certificates per service. All services now accessible via HTTPS with valid certificates.
**Status:** ✅ Production Ready
**Next Review:** 30 days before certificate expiration (March 13, 2026)

View File

@@ -6,9 +6,11 @@ For most users, the automated setup script handles everything from system prepar
- **Fresh Debian/Ubuntu server** (or existing system) - **Fresh Debian/Ubuntu server** (or existing system)
- **Root/sudo access** - **Root/sudo access**
- **Internet connection** - **Internet connection**
- **Ports 80 and 443 forwarded** from your router to your server (required for SSL certificates) - **Ports 80 and 443 forwarded** from your router to your **core server only** (required for SSL certificates)
- **VS Code with GitHub Copilot** (for AI assistance) - **VS Code with GitHub Copilot** (for AI assistance)
**Note**: For multi-server setups, only the core server needs ports forwarded. Remote servers connect via Docker TLS (port 2376).
## Simple Setup ## Simple Setup
1. **Connect to your server** via SSH 1. **Connect to your server** via SSH
@@ -18,10 +20,12 @@ For most users, the automated setup script handles everything from system prepar
2. **Install git if needed** 2. **Install git if needed**
```bash ```bash
sudo apt update && sudo apt upgrade -y && sudo apt install git sudo apt update && sudo apt upgrade -y && sudo apt install git
```
3. **Clone the repository**: 3. **Clone the repository**:
```bash ```bash
git clone https://github.com/kelinfoxy/AI-Homelab.git git clone https://github.com/kelinfoxy/EZ-Homelab.git
cd AI-Homelab cd EZ-Homelab
```
4. **Configure environment**: 4. **Configure environment**:
```bash ```bash
cp .env.example .env cp .env.example .env
@@ -57,6 +61,78 @@ For most users, the automated setup script handles everything from system prepar
**That's it!** Your homelab is ready. **That's it!** Your homelab is ready.
**Access Dockge at `https://dockge.yourdomain.duckdns.org`** **Access Dockge at `https://dockge.yourdomain.duckdns.org`**
## Multi-Server Setup
To deploy services across multiple servers (e.g., Raspberry Pi, mini PCs):
### Core Server Setup (First)
1. Follow the main setup above (steps 1-5)
2. This server gets ports 80/443 forwarded from your router
3. This server generates the shared CA for Docker TLS communication
### Remote Server Setup (After Core)
1. **Clone repository on remote server**:
```bash
git clone https://github.com/kelinfoxy/EZ-Homelab.git
cd EZ-Homelab
```
2. **Copy `.env` from core server**:
```bash
# On core server
cd ~/EZ-Homelab
cat .env # Copy the contents
# On remote server
nano ~/EZ-Homelab/.env # Paste and save
```
3. **Run setup with Infrastructure-Only option**:
```bash
./scripts/ez-homelab.sh
# Select option 3: "Deploy Infrastructure Only (Remote Server)"
```
4. **When prompted, provide core server IP** for CA import
5. **Script automatically**:
- Copies shared CA from core server via SSH
- Configures Docker TLS with shared certificates
- Generates server certificates signed by shared CA
- Sets up Docker daemon for TLS on port 2376
- Deploys Traefik for local container discovery
- Deploys Sablier for local lazy loading
### What Gets Deployed Where
| Component | Core Server | Remote Servers |
|-----------|-------------|----------------|
| DuckDNS | ✅ Yes | ❌ No |
| Authelia | ✅ Yes | ❌ No |
| Traefik | ✅ Yes (multi-provider) | ✅ Yes (local only) |
| Sablier | ✅ Yes (own stack) | ✅ Yes (own stack) |
| Dockge | ✅ Yes | ✅ Yes |
| Services | ✅ Any | ✅ Any |
### Architecture Benefits
- **Single Domain**: All services accessible via core server's domain
- **No Port Forwarding**: Remote servers don't need router configuration
- **Automatic Discovery**: Core Traefik finds services on all servers
- **Local Control**: Each Sablier manages its own server's containers
- **Secure Communication**: All inter-server traffic uses TLS encryption
### Troubleshooting Multi-Server Setup
If remote server setup fails:
1. **Check SSH access** from remote to core server
2. **Verify firewall** allows port 2376 on remote servers
3. **Test TLS connection** from core:
```bash
cd /opt/stacks/core/shared-ca
docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem \
--tlskey=key.pem --host=tcp://REMOTE_IP:2376 ps
```
4. **Check logs**: See setup script output for specific errors
## What the Unified Setup Script Does ## What the Unified Setup Script Does
The `ez-homelab.sh` script is a comprehensive guided setup and deployment tool: The `ez-homelab.sh` script is a comprehensive guided setup and deployment tool:
@@ -81,8 +157,10 @@ The `ez-homelab.sh` script is a comprehensive guided setup and deployment tool:
**Infrastructure Setup & Deployment:** **Infrastructure Setup & Deployment:**
- ✅ Creates directory structure (`/opt/stacks/`) - ✅ Creates directory structure (`/opt/stacks/`)
- ✅ Sets up Docker networks (homelab, traefik, dockerproxy, media) - ✅ Sets up Docker networks (homelab, traefik, dockerproxy, media)
- ✅ Deploys selected service stacks - ✅ Deploys selected service stacks with individual deployment scripts
- ✅ Obtains wildcard SSL certificate (*.yourdomain.duckdns.org) - ✅ Obtains wildcard SSL certificate (*.yourdomain.duckdns.org)
- ✅ Configures Traefik for multi-server support (if applicable)
- ✅ Generates and distributes TLS certificates for Docker API (multi-server)
- ✅ Detects NVIDIA GPU and offers driver installation - ✅ Detects NVIDIA GPU and offers driver installation
- ✅ Opens Dockge when ready - ✅ Opens Dockge when ready
@@ -94,7 +172,8 @@ The `ez-homelab.sh` script is a comprehensive guided setup and deployment tool:
- Confirmation prompts for destructive actions - Confirmation prompts for destructive actions
## Release-Specific Notes ## Release-Specific Notes
- **Version**: Based on v0.1.0—ensure you're using the latest scripts. - **Current Version**: Production-ready with comprehensive multi-server support
- **Stacks**: Core, Infrastructure, and Dashboards deploy automatically. Others are inactive by default. - **Stacks**: Core, Infrastructure, Sablier, and Dashboards deploy automatically
- **Dashboards**: Homepage is preconfigured at `homepage.yoursubdomain.duckdns.org`. - **Dashboards**: Homepage is preconfigured at `homepage.yourdomain.duckdns.org`
- **Known Limitations**: Options 1 & 2 require additional testing; use Option 3 for production. - **Multi-Server**: Use option 3 for remote server infrastructure deployment
- **Modular Deployment**: Individual scripts in `docker-compose/*/deploy-*.sh` called by ez-homelab.sh

View File

@@ -1,24 +0,0 @@
Install Process Excellent
Deploy Porcess Excellent
Core, dashboards, infrastructure, & media deployed successfully!
Install & Deply script status: Working on the test system, needs testing on an actual fresh install.
Notes:
Authelia timeouts way too low set to 24 hours
media-extended stack was renamed to media-management, homepage refers to old stack name
media-extended folder still exist, needs Removed
media-management stack & utilities stack have many invalid image:tag errors, requires image:tag validation
monitoring-stack, prometheus, loki & promtail have errors
productivity stack came up without errors, but nextcloud gave untrusted domain error in browser, mealie works, but the rest of the stack gave error 404 in browser
EVERY compose file should include a section for the dockge urls containing all the proxy urls for services in the stack
homeassistant stack the zigbee2mqtt container gives an error cause it can't find the device, which doesn't exist on this system
resource limiting on a per stack or per container basis need to be implimented

View File

@@ -4,6 +4,21 @@
This document provides comprehensive guidelines for managing Docker services in your AI-powered homelab using Dockge, Traefik, and Authelia. These guidelines ensure consistency, maintainability, and reliability across your entire infrastructure. This document provides comprehensive guidelines for managing Docker services in your AI-powered homelab using Dockge, Traefik, and Authelia. These guidelines ensure consistency, maintainability, and reliability across your entire infrastructure.
## Multi-Server Architecture Support
EZ-Homelab supports two deployment models:
1. **Single Server**: All services on one server with Traefik and Sablier managing local containers
2. **Multi-Server**: Core server handles external traffic; remote servers run their own Traefik/Sablier instances
### Multi-Server Architecture Overview
- **Core Server**: DuckDNS, Traefik (multi-provider), Authelia, ports 80/443 forwarded
- **Remote Servers**: Traefik (local discovery only), Sablier (local containers only)
- **Routing**: Core Traefik uses external YAML files to route traffic to remote servers
- **Security**: Each server independently manages SSO and lazy loading for its services
- **No Docker API**: Servers communicate via HTTP/HTTPS, not Docker API TLS
## Table of Contents ## Table of Contents
1. [Philosophy](#philosophy) 1. [Philosophy](#philosophy)
@@ -100,6 +115,12 @@ AI will suggest `/mnt/` when data may exceed 50GB or grow continuously.
## Traefik and Authelia Integration ## Traefik and Authelia Integration
### Routing Decision Tree
**Is Traefik running on the SAME server as your service?**
- **YES**: Use Docker labels in the service's compose file (see below)
- **NO**: Comment out labels; add route to Traefik's external YAML file on the core server
### Every Local (on the same server) Service Needs Traefik Labels ### Every Local (on the same server) Service Needs Traefik Labels
**Default Configuration**: All services should use authelia SSO, traefik routing, and sablier lazy loading by default. **Default Configuration**: All services should use authelia SSO, traefik routing, and sablier lazy loading by default.
@@ -564,9 +585,13 @@ networks:
external: true external: true
``` ```
If Traefik & Sablier are on a remote server: ### Multi-Server Configuration
If Traefik is on a DIFFERENT server (e.g., service on remote server, Traefik on core):
* Comment out the traefik labels since they won't be used, don't delete them. * Comment out the traefik labels since they won't be used, don't delete them.
* Notify user to add the service and middleware to the traefic external host yml file, and the sablier.yml file. * Keep Sablier labels (each server has its own Sablier for local containers)
* Add route to Traefik's external YAML file on the core server
* Authelia SSO is handled by core server (no need for Authelia on remote servers)
**Example: Comment out Traefik labels in docker-compose.yml:** **Example: Comment out Traefik labels in docker-compose.yml:**
```yaml ```yaml
@@ -1402,7 +1427,7 @@ Homepage configuration must be kept synchronized with deployed services. The AI
1. **Hard-Coded URLs Required**: Homepage does NOT support variables in href links 1. **Hard-Coded URLs Required**: Homepage does NOT support variables in href links
- Template uses `{{HOMEPAGE_VAR_DOMAIN}}` as placeholder - Template uses `{{HOMEPAGE_VAR_DOMAIN}}` as placeholder
- Active config uses `kelin-hass.duckdns.org` hard-coded - Active config uses `yourdomain.duckdns.org` hard-coded
- AI must replace placeholders when deploying configs - AI must replace placeholders when deploying configs
2. **No Container Restart Needed**: Homepage picks up config changes instantly 2. **No Container Restart Needed**: Homepage picks up config changes instantly
@@ -1427,7 +1452,7 @@ Homepage configuration must be kept synchronized with deployed services. The AI
- Stack Name (compose-file.yml): - Stack Name (compose-file.yml):
- Service Name: - Service Name:
icon: service.png icon: service.png
href: https://subdomain.kelin-hass.duckdns.org # Hard-coded! href: https://subdomain.yourdomain.duckdns.org # Hard-coded!
description: Service description description: Service description
``` ```
@@ -1436,7 +1461,7 @@ Homepage configuration must be kept synchronized with deployed services. The AI
```bash ```bash
# When deploying from template: # When deploying from template:
cp /home/kelin/AI-Homelab/config-templates/homepage/*.yaml /opt/stacks/homepage/config/ cp /home/kelin/AI-Homelab/config-templates/homepage/*.yaml /opt/stacks/homepage/config/
sed -i 's/{{HOMEPAGE_VAR_DOMAIN}}/kelin-hass.duckdns.org/g' /opt/stacks/homepage/config/services.yaml sed -i 's/{{HOMEPAGE_VAR_DOMAIN}}/yourdomain.duckdns.org/g' /opt/stacks/homepage/config/services.yaml
# No restart needed - configs load instantly # No restart needed - configs load instantly
``` ```

View File

@@ -1,50 +1,53 @@
# Getting Started Guide # Getting Started Guide
Welcome to your AI-powered homelab! This guide will walk you through setting up your production-ready infrastructure with Dockge, Traefik, Authelia, and [50+ services](services-overview.md). Welcome to your EZ-Homelab! This guide will walk you through setting up one or more homelab servers with Dockge, Traefik, Authelia, and [50+ services](services-overview.md).
## Release v0.1.0 Highlights ## How It All Works
This is the first official release of EZ-Homelab, thoroughly tested on Debian 12. Key features include:
- Automated SSL via DuckDNS and Let's Encrypt. Before diving in, See [How Your AI Homelab Works](how-it-works.md) for a comprehensive overview.
- Authelia SSO enabled by default for security.
- Sablier lazy loading to save resources.
- 50+ preconfigured services across 10 stacks.
- See [Release Notes](../release-notes-v0.1.0.md) for full details.
## Getting Started Checklist ## Getting Started Checklist
- [ ] Clone this repository to your home folder - [ ] Clone this repository to your home folder
- [ ] Configure `.env` file with your domain and tokens ([see prerequisites](env-configuration.md)) - [ ] (optional) Configure `.env` file with your configuration details
- [ ] Forward ports 80 and 443 from your router to your server - [ ] Forward ports 80 and 443 from your router to your **core server only**
- [ ] Run unified setup script (generates Authelia secrets and admin user, deploys services) ([ez-homelab.sh](../scripts/ez-homelab.sh)) - [ ] Run ([ez-homelab.sh](../scripts/ez-homelab.sh))
- [ ] Access Dockge web UI (`https://dockge.${DOMAIN}`) - [ ] (Optional) Set up additional remote servers using option 3 in ez-homelab.sh
- [ ] Access Dockge web UI (`https://dockge.servername.${DOMAIN}`)
- [ ] Set up 2FA with Authelia ([Authelia setup guide](service-docs/authelia.md)) - [ ] Set up 2FA with Authelia ([Authelia setup guide](service-docs/authelia.md))
- [ ] (optional) Deploy additional stacks as needed via Dockge ([services overview](services-overview.md)) - [ ] Deploy additional stacks as needed via Dockge ([services overview](services-overview.md))
- [ ] Configure and use VS Code with Github Copilot to manage the server ([AI management](.github/copilot-instructions.md) | [Example prompts](ai-management-prompts.md)) - [ ] Configure VS Code with GitHub Copilot to manage services ([AI management](.github/copilot-instructions.md))
## Setup Options ## Setup Options
Choose the setup method that works best for you: Choose the setup method that works best for you:
### 🚀 Automated Setup (Recommended) ### 🚀 Automated Setup (Recommended)
For most users, the automated scripts handle everything. See [Automated Setup Guide](automated-setup.md) for step-by-step instructions. For most users, the automated scripts handle everything.
See [Automated Setup Guide](automated-setup.md) for step-by-step instructions.
### 🔧 Manual Setup ### 🔧 Manual Setup
If you prefer manual control or the automated script fails, see the [Manual Setup Guide](manual-setup.md) for detailed instructions. If you prefer manual control or the automated script fails,
see the [Manual Setup Guide](manual-setup.md) for detailed instructions.
### 🤖 AI-Assisted Setup ### 🤖 AI-Assisted Setup
Learn how to use VS Code with GitHub Copilot for AI-powered homelab management. See [AI VS Code Setup](ai-vscode-setup.md). Learn how to use VS Code with GitHub Copilot for AI-powered homelab management.
See [AI VS Code Setup](ai-vscode-setup.md).
## How It All Works
Before diving in, understand how your homelab infrastructure works together. See [How Your AI Homelab Works](how-it-works.md) for a comprehensive overview.
## SSL Certificates ## SSL Certificates
Your homelab uses Let's Encrypt for automatic HTTPS certificates. See [SSL Certificates Guide](ssl-certificates.md) for details on certificate management and troubleshooting. Your homelab uses Let's Encrypt for automatic HTTPS certificates.
See [SSL Certificates Guide](ssl-certificates.md) for details on certificate management and troubleshooting.
## What Comes Next ## What Comes Next
After setup, learn what to do with your running homelab. See [Post-Setup Guide](post-setup.md) for accessing services, customization, and maintenance. After setup, learn what to do with your running homelab.
See [Post-Setup Guide](post-setup.md) for accessing services, customization, and maintenance.
## On-Demand Remote Services ## Multi-Server Deployments
>NOTE:
**Core Server** refers to the server that has ports 80 & 443 forwarded to it.
All other servers are refered to as **Remote Server(s)**
For advanced users: Learn how to set up lazy-loading services on remote servers (like Raspberry Pi) that start automatically when accessed. See [On-Demand Remote Services](Ondemand-Remote-Services.md). Learn how to set up lazy-loading services on remote servers that start automatically when accessed. Each server runs its own Traefik and Sablier for local container management, while the core server handles all external routing.
See [Multi-Server Deployment](multi-server-deployment.md) for detailed multi-server architecture and setup instructions.

View File

@@ -11,43 +11,67 @@ Your homelab is a **Docker-based infrastructure** that automatically:
- Updates itself - Updates itself
- Backs up your data - Backs up your data
- Monitors system health - Monitors system health
- **Scales across multiple servers**
Everything runs in **containers** - like lightweight virtual machines - that are orchestrated by **Docker Compose** and managed through the **Dockge** web interface. Everything runs in **containers** - like lightweight virtual machines - that are orchestrated by **Docker Compose** and managed through the **Dockge** web interface.
## Single-Server vs Multi-Server
EZ-Homelab supports two deployment architectures:
### Single Server
- All services run on one machine
- Traefik and Sablier manage local services only
- Simplest setup for homelab beginners
- Ideal for: Single desktop/server, all services in one place
### Multi-Server
- **Core Server**: Handles external traffic (ports 80/443), runs DuckDNS/Traefik/Authelia
- **Remote Servers**: Run their own Traefik/Sablier for local container management
- Unified domain access: All services through `service.yourdomain.com`
- Servers communicate via HTTP/HTTPS (no Docker API TLS needed)
- Ideal for: Raspberry Pi clusters, NAS + desktop, distributed workloads
**This guide covers both architectures**, with multi-server notes where relevant.
## Core Components ## Core Components
### 🏠 **Homepage Dashboard** (`https://home.yourdomain.duckdns.org`) ### 🏠 **Homepage Dashboard** (`https://homepage.yourdomain.duckdns.org`)
Your central hub for accessing all services. Think of it as the "start menu" for your homelab. Your central hub for accessing all services. Think of it as the "start menu" for your homelab.
- **What it does**: Shows all your deployed services with quick links - **What it does**: Shows all your deployed services with quick links
- **AI Integration**: The AI can automatically add new services and configure widgets - **AI Integration**: The AI can automatically add new services and configure widgets
- **Customization**: Add weather, system stats, and service-specific widgets - **Customization**: Add weather, system stats, and service-specific widgets
- **Configuration**: [docker-compose/dashboards/](docker-compose/dashboards/) | [service-docs/homepage.md](service-docs/homepage.md) - **Configuration**: [docker-compose/dashboards/](docker-compose/dashboards/) | [service-docs/homepage.md](service-docs/homepage.md)
### 🐳 **Dockge** (`https://dockge.yourdomain.duckdns.org`) ### 🐳 **Dockge** (`https://dockge.servername.yourdomain.duckdns.org`)
Your primary management interface for deploying and managing services. Your primary management interface for deploying and managing services.
- **What it does**: Web-based Docker Compose manager - **What it does**: Web-based Docker Compose manager
- **Stacks**: Groups services into logical units (media, monitoring, productivity) - **Stacks**: Groups services into logical units (media, monitoring, productivity)
- **One-Click Deploy**: Upload compose files and deploy instantly - **One-Click Deploy**: Upload compose files and deploy instantly
- **Configuration**: [docker-compose/infrastructure/](docker-compose/infrastructure/) | [service-docs/dockge.md](service-docs/dockge.md) - **Multi-Server**: Deploy on core or remote servers from one interface
- **Configuration**: [docker-compose/dockge/](docker-compose/dockge/) | [service-docs/dockge.md](service-docs/dockge.md)
### 🔐 **Authelia** (`https://auth.yourdomain.duckdns.org`) ### 🔐 **Authelia** (`https://auth.yourdomain.duckdns.org`)
Your security gatekeeper that protects sensitive services. Your security gatekeeper that protects sensitive services.
- **What it does**: Single sign-on (SSO) authentication - **What it does**: Single sign-on (SSO) authentication
- **Security**: Two-factor authentication, session management - **Security**: Two-factor authentication, session management
- **Smart Bypass**: Automatically bypasses auth for media apps (Plex, Jellyfin) - **Smart Bypass**: Automatically bypasses auth for media apps (Plex, Jellyfin)
- **Multi-Server**: Core server only; protects all services across all servers
- **Configuration**: [docker-compose/core/](docker-compose/core/) | [service-docs/authelia.md](service-docs/authelia.md) - **Configuration**: [docker-compose/core/](docker-compose/core/) | [service-docs/authelia.md](service-docs/authelia.md)
### 🌐 **Traefik** (`https://traefik.yourdomain.duckdns.org`) ### 🌐 **Traefik** (`https://traefik.servername.yourdomain.duckdns.org`)
Your intelligent traffic director and SSL certificate manager. Your intelligent traffic director and SSL certificate manager.
- **What it does**: Reverse proxy that routes web traffic to the right services - **What it does**: Reverse proxy that routes web traffic to the right services
- **SSL**: Automatically obtains and renews free HTTPS certificates - **SSL**: Automatically obtains and renews free HTTPS certificates
- **Labels**: Services "advertise" themselves to Traefik via Docker labels - **Labels**: Services "advertise" themselves to Traefik via Docker labels
- **Multi-Server**: Core uses multi-provider (labels + YAML files); remote servers use labels only
- **Configuration**: [docker-compose/core/](docker-compose/core/) | [service-docs/traefik.md](service-docs/traefik.md) - **Configuration**: [docker-compose/core/](docker-compose/core/) | [service-docs/traefik.md](service-docs/traefik.md)
### 🦆 **DuckDNS** ### 🦆 **DuckDNS**
Your dynamic DNS service that gives your homelab a consistent domain name. Your dynamic DNS service that gives your homelab a consistent domain name.
- **What it does**: Updates `yourdomain.duckdns.org` to point to your home IP - **What it does**: Updates `yourdomain.duckdns.org` to point to your home IP
- **Integration**: Works with Traefik to get wildcard SSL certificates - **Integration**: Works with Traefik to get wildcard SSL certificates
- **Multi-Server**: Core server only (one domain for all servers)
- **Configuration**: [docker-compose/core/](docker-compose/core/) | [service-docs/duckdns.md](service-docs/duckdns.md) - **Configuration**: [docker-compose/core/](docker-compose/core/) | [service-docs/duckdns.md](service-docs/duckdns.md)
### 🛡️ **Gluetun (VPN)** ### 🛡️ **Gluetun (VPN)**
@@ -75,17 +99,26 @@ Your download traffic protector.
## Network Architecture ## Network Architecture
### Internal Networks ### Internal Networks (Per Server)
- **`traefik-network`**: All web-facing services connect here - **`traefik-network`**: All web-facing services connect here
- **`homelab-network`**: Internal service communication - **`homelab-network`**: Internal service communication
- **`media-network`**: Media services (Plex, Jellyfin, etc.) - **`media-network`**: Media services (Plex, Jellyfin, etc.)
- **VPN Networks**: Download services route through Gluetun - **VPN Networks**: Download services route through Gluetun
### External Access ### External Access
**Single Server:**
- **Port 80/443**: Only Traefik exposes these to the internet - **Port 80/443**: Only Traefik exposes these to the internet
- **Domain**: `*.yourdomain.duckdns.org` points to your home - **Domain**: `*.yourdomain.duckdns.org` points to your home
- **SSL**: Wildcard certificate covers all subdomains automatically - **SSL**: Wildcard certificate covers all subdomains automatically
**Multi-Server:**
- **Core Server Ports**: Only core forwards 80/443 to internet
- **Remote Servers**: No port forwarding needed; accessed through core
- **Traffic Flow**: Internet → Core Traefik → Remote Traefik → Service
- **SSL**: Core handles all SSL termination
- **Unified Domain**: `service.yourdomain.com` works for all servers
## Storage Strategy ## Storage Strategy
### Configuration Files ### Configuration Files
@@ -123,6 +156,44 @@ Some services start **on-demand** to save resources:
- **How it works**: Service starts when you first access it - **How it works**: Service starts when you first access it
- **Benefits**: Saves RAM and CPU when services aren't in use - **Benefits**: Saves RAM and CPU when services aren't in use
- **Configuration**: AI manages the lazy loading rules - **Configuration**: AI manages the lazy loading rules
- **Multi-Server**: Each server runs its own Sablier for LOCAL container management
## Multi-Server Architecture (Optional)
If you deploy across multiple servers:
### Core Server Responsibilities
- **External Traffic**: Only server with ports 80/443 forwarded
- **SSL Termination**: Handles all HTTPS certificates
- **SSO Gateway**: Authelia protects all services across servers
- **DNS Updates**: DuckDNS points domain to this server
- **Route Configuration**: Traefik YAML files route to remote servers
### Remote Server Responsibilities
- **Local Services**: Runs media, downloads, or specialized workloads
- **Local Management**: Own Traefik and Sablier instances
- **Internal Access**: Services listen on Docker networks only
- **Lazy Loading**: Sablier starts/stops containers locally
### Traffic Flow Example
```
User requests: https://sonarr.yourdomain.com
1. Internet → Core Server (ports 80/443)
2. Core Traefik → Authelia (check login)
3. Core Traefik → Remote Server (http://192.168.1.100:8989)
4. Remote Traefik → Sablier (is service running?)
5. Remote Sablier → Starts Container (if needed)
6. Service Response → Back through chain to user
```
### Setup Process
1. **Core**: Deploy with `ez-homelab.sh` option 1 or 2
2. **Remote**: Deploy with `ez-homelab.sh` option 3 per server
3. **Configure**: Add external YAML files on core for remote services
4. **Access**: All services available through core domain
See [Ondemand-Remote-Services.md](Ondemand-Remote-Services.md) for detailed setup.
## Monitoring & Maintenance ## Monitoring & Maintenance
@@ -154,53 +225,19 @@ Some services start **on-demand** to save resources:
## Scaling & Customization ## Scaling & Customization
### Adding Services ### Adding Services
**Single Server:**
- **Pre-built**: [50+ services](services-overview.md) ready to deploy - **Pre-built**: [50+ services](services-overview.md) ready to deploy
- **Custom**: AI can create configurations for any Docker service - **Custom**: AI can create configurations for any Docker service
- **External**: Proxy services on other devices (Raspberry Pi, NAS) - **External Devices**: Proxy services on Raspberry Pi, NAS via Traefik YAML files
**Multi-Server:**
- **Remote Services**: Deploy on any server with Traefik labels
- **Core Routing**: Add external YAML file on core to route traffic
- **Unified Access**: All services appear under same domain
- **See**: [Ondemand-Remote-Services.md](Ondemand-Remote-Services.md) for patterns
### Resource Management ### Resource Management
- **Limits**: CPU, memory, and I/O limits prevent resource exhaustion - **Limits**: CPU, memory, and I/O limits prevent resource exhaustion
- **Reservations**: Guaranteed minimum resources - **Reservations**: Guaranteed minimum resources
- **GPU Support**: Automatic NVIDIA GPU detection and configuration - **GPU Support**: Automatic NVIDIA GPU detection and configuration
## Troubleshooting Philosophy
- **Logs First**: Every service provides detailed logs. The AI can help analyze them.
- **Isolation Testing**: Deploy services one at a time to identify conflicts.
- **Configuration Validation**: AI validates Docker Compose syntax before deployment.
- **Rollback Ready**: Previous configurations are preserved for quick recovery.
## Getting Help
### Documentation Links
- **[Automated Setup](automated-setup.md)**: Step-by-step deployment
- **[SSL Certificates](ssl-certificates.md)**: HTTPS configuration details
- **[Post-Setup](post-setup.md)**: What to do after deployment
- **[AI VS Code Setup](ai-vscode-setup.md)**: Configure AI assistance
- **[AI Management Prompts](ai-management-prompts.md)**: Example commands for AI assistant
- **[Services Overview](../docs/services-overview.md)**: All available services
- **[Docker Guidelines](../docs/docker-guidelines.md)**: Technical details
### AI Assistance
- **In VS Code**: Use GitHub Copilot Chat for instant help
- **Examples**:
- "Add a new service to my homelab"
- "Fix SSL certificate issues"
- "Configure backup for my data"
- "Set up monitoring dashboard"
### Community Resources
- **GitHub Issues**: Report bugs or request features
- **Discussions**: Ask questions and share configurations
- **Wiki**: Community-contributed guides and tutorials
## Architecture Summary
Your homelab follows these principles:
- **Infrastructure as Code**: Everything defined in files
- **GitOps**: Version control for all configurations
- **Security First**: SSO protection by default
- **AI-Assisted**: Intelligent management and troubleshooting
- **Production Ready**: Monitoring, backups, and high availability
The result is a powerful, secure, and easy-to-manage homelab that grows with your needs!

View File

@@ -1,202 +0,0 @@
# Implementation Plan Corrections
## User Feedback Summary
### 1. Remove `prompt_for_server_role()` Function
**Issue**: Unnecessary function - should integrate with existing validation
**Solution**: Use existing menu structure (Option 2: Deploy Core, Option 3: Deploy Additional Server) and integrate role detection into existing `validate_and_prompt_variables()` function via dynamic REQUIRED_VARS
### 2. Remove `validate_env_file()` Function
**Issue**: Duplicates existing REQUIRED_VARS mechanism
**Solution**: Enhance existing system:
- Add `set_required_vars_for_deployment(type)` function to dynamically set REQUIRED_VARS
- Reuse existing `validate_and_prompt_variables()` (line 562) - already validates and prompts
- No new validation function needed
### 3. config-templates Folder is Deprecated
**Issue**: Plan references `config-templates/` which should be deleted
**Solution**: All working configs are in `docker-compose/` folder
- Update references: `docker-compose/core/traefik/traefik.yml` (not config-templates)
- Delete config-templates folder if still exists
### 4. .env Editing Should Be Optional
**Issue**: Plan suggests users manually edit .env in step 4.2
**Solution**:
- Script ALWAYS prompts for ALL required variables for the deployment option
- When user selects Option 3 (Deploy Additional Server):
- Call `set_required_vars_for_deployment("remote")`
- Sets REQUIRED_VARS to include: REMOTE_SERVER_IP, REMOTE_SERVER_HOSTNAME, REMOTE_SERVER_USER
- Call `validate_and_prompt_variables()` - prompts for all
- Save complete .env via `save_env_file()`
### 5. Deploy Scripts Should Auto-Backup
**Issue**: Migration path has manual backup steps
**Solution**:
- Deploy scripts MUST backup automatically before changes
- **Critical**: Verify backups are from `/opt/stacks/*/` (deployed location), NOT `~/EZ-Homelab/docker-compose/*/` (repo source)
- Expected backup pattern: `/opt/stacks/core/traefik.backup.TIMESTAMP/`
where TIMESTAMP is like YY_MM_DD_hh_mm
- Review all deploy functions for correct backup logic
### 6. Traefik Dynamic Folder Files Need Replacement
**Issue**: Existing `external-host-*.yml` files are for old method
**Solution**:
- During core deployment, replace entire `traefik/dynamic/` folder contents
- New files:
- `sablier.yml` (updated middleware format)
- Auto-generated provider-specific configs
- Deploy script should:
1. Backup `/opt/stacks/core/traefik/dynamic/` to timestamped folder
2. Copy new configs from `docker-compose/core/traefik/dynamic/`
3. Process variables via `localize_config_file()`
---
## Corrected Function List
### Functions to ADD (New):
**In common.sh:**
- `detect_server_role()` - Check .env to determine core vs remote
- `generate_traefik_provider_config()` - Generate YAML for remote provider
- `generate_sablier_middleware_config()` - Generate YAML for remote Sablier
- `add_remote_server_to_traefik()` - Register remote server with core
**In ez-homelab.sh:**
- `check_docker_installed()` - Silent Docker check with timeout (Pi-safe)
- `set_required_vars_for_deployment(type)` - Set REQUIRED_VARS dynamically
- `deploy_remote_server()` - Deploy Docker TLS + Sablier on remote servers
### Functions to MODIFY (Existing):
**In ez-homelab.sh:**
- `REQUIRED_VARS` (line 398) - Make dynamic via `set_required_vars_for_deployment()`
- `main()` - Add Docker pre-check, call `set_required_vars_for_deployment()` before validation
- `deploy_core()` - Auto-deploy Sablier stack after core stack
- `validate_and_prompt_variables()` (line 562) - NO CHANGES (already does what we need)
### Functions NOT NEEDED:
- ~~`prompt_for_server_role()`~~ - Use existing menu structure
- ~~`validate_env_file()`~~ - Use existing REQUIRED_VARS mechanism
---
## Corrected Workflow
### Option 2: Deploy Core (Existing Option)
```bash
main() {
case $DEPLOY_CHOICE in
2)
# Deploy Core
set_required_vars_for_deployment "core" # NEW
validate_and_prompt_variables # EXISTING - reuse
save_env_file # EXISTING
DEPLOY_CORE=true
# In deploy_core(): auto-deploy Sablier after core
;;
```
### Option 3: Deploy Additional Server (New Option)
```bash
3)
# Deploy Additional Server
set_required_vars_for_deployment "remote" # NEW - sets REQUIRED_VARS
validate_and_prompt_variables # EXISTING - prompts for all
save_env_file # EXISTING
deploy_remote_server # NEW function
;;
```
### REQUIRED_VARS Dynamic Setting
```bash
set_required_vars_for_deployment() {
local deployment_type="${1:-core}"
if [ "$deployment_type" == "core" ]; then
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_SUBDOMAINS" "DUCKDNS_TOKEN" "DOMAIN" "DEFAULT_USER" "DEFAULT_PASSWORD" "DEFAULT_EMAIL")
elif [ "$deployment_type" == "remote" ]; then
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "REMOTE_SERVER_IP" "REMOTE_SERVER_HOSTNAME" "REMOTE_SERVER_USER" "DEFAULT_USER" "DEFAULT_EMAIL")
fi
}
```
---
## Corrected File Structure
### Repo Source Files (docker-compose/):
```
docker-compose/
├── core/
│ ├── docker-compose.yml # Remove Sablier section
│ ├── traefik/
│ │ ├── traefik.yml # Static config with provider template
│ │ └── dynamic/
│ │ ├── sablier.yml # Updated middleware configs
│ │ └── (other dynamic configs)
│ └── authelia/
├── sablier/ # NEW STACK
│ ├── docker-compose.yml # Container name: sablier
│ └── README.md # Stack documentation
└── (other stacks)/
```
### Deployed Files (/opt/stacks/):
```
/opt/stacks/
├── core/ # Core stack only (no Sablier)
│ ├── docker-compose.yml
│ ├── traefik/
│ │ ├── config/traefik.yml
│ │ └── dynamic/
│ │ ├── sablier.yml
│ │ └── (auto-generated provider configs)
│ └── shared-ca/ # Shared CA certificates
├── sablier/ # Sablier stack (all servers)
│ ├── docker-compose.yml
│ └── .env # Environment variables (copied from repo)
└── (other stacks)/
```
---
## Critical Backup Check
**Problem**: Deploy scripts may backup from repo instead of deployed location
**Incorrect (if found)**:
```bash
# DON'T do this - backs up repo source, not deployed config
cp -r ~/EZ-Homelab/docker-compose/core/traefik ~/backups/
```
**Correct**:
```bash
# DO this - backs up deployed configuration
# Format: traefik.backup.YY_MM_DD_hh_mm
cp -r /opt/stacks/core/traefik /opt/stacks/core/traefik.backup.$(date +%y_%m_%d_%H_%M)
```
**Action**: Review `deploy_core()` and all deploy functions for correct backup paths
---
## Implementation Priority
1.**Delete** `config-templates/` folder
2.**Verify** deploy scripts backup from `/opt/stacks/` not repo
3.**Add** `set_required_vars_for_deployment()` function
4.**Add** `check_docker_installed()` function
5.**Modify** `main()` to use dynamic REQUIRED_VARS
6.**Create** `docker-compose/sablier/` stack
7.**Remove** Sablier from `docker-compose/core/docker-compose.yml`
8.**Add** common.sh functions for multi-server support
9.**Add** `deploy_remote_server()` function
10.**Update** `docker-compose/core/traefik/dynamic/` files
---
*Corrections Version: 1.0*
*Date: February 4, 2026*
*Based on: User feedback on implementation plan v2.0*

View File

@@ -17,26 +17,8 @@ sudo apt update && sudo apt upgrade -y
## Step 2: Install Docker ## Step 2: Install Docker
```bash ```bash
# Install dependencies curl -fsSL https://get.docker.com | sudo sh
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release sudo usermod -aG docker your-username
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Set up repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Or use convenience script
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add user to docker group
sudo usermod -aG docker $USER
sudo usermod -aG sudo $USER
# Log out and back in, or run: newgrp docker # Log out and back in, or run: newgrp docker
``` ```
@@ -45,32 +27,50 @@ sudo usermod -aG sudo $USER
```bash ```bash
cd ~ cd ~
git clone https://github.com/kelinfoxy/AI-Homelab.git git clone https://github.com/kelinfoxy/EZ-Homelab.git
cd AI-Homelab cd EZ-Homelab
``` ```
## Step 4: Configure Environment ## Step 4: Generate Secrets
```yaml
# Generate 3 Secrets for Authelia
# Generate hash for your password
docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password 'YourSecurePassword'
```
## Step 5: Configure Environment
```bash ```bash
cp .env.example .env cp .env.example .env
nano .env # Edit all required variables nano .env # Edit all required variables
``` ```
**Required variables:** **Minimum Required variables:**
- `DOMAIN` - Your DuckDNS domain * PUID
- `DUCKDNS_TOKEN` - Your DuckDNS token * PGID
- `ACME_EMAIL` - Your email for Let's Encrypt * TZ
- `AUTHELIA_JWT_SECRET` - Generate with: `openssl rand -hex 64` * SERVER_IP
- `AUTHELIA_SESSION_SECRET` - Generate with: `openssl rand -hex 64` * SERVER_HOSTNAME
- `AUTHELIA_STORAGE_ENCRYPTION_KEY` - Generate with: `openssl rand -hex 64` * DUCKDNS_SUBDOMAINS
- `SURFSHARK_USERNAME` and `SURFSHARK_PASSWORD` - If using VPN * DUCKDNS_TOKEN
* DOMAIN
* DEFAULT_USER
* DEFAULT_PASSWORD
* DEFAULT_EMAIL
**If using VPN**:
* SURFSHARK_USERNAM
* SURFSHARK_PASSWORD
* VPN_SERVER_COUNTRIES
## Step 5: Create Infrastructure ## Step 6: Create Infrastructure
```bash ```bash
# Create directories # Create directories
sudo mkdir -p /opt/stacks /mnt/{media,database,downloads,backups} sudo mkdir -p /opt/stacks /opt/dockge
sudo chown -R $USER:$USER /opt/stacks /mnt sudo chown -R $USER:$USER /opt/stacks
sudo chown -R $USER:$USER /opt/dockge
# Create networks # Create networks
docker network create traefik-network docker network create traefik-network
@@ -79,200 +79,17 @@ docker network create dockerproxy-network
docker network create media-network docker network create media-network
``` ```
## Step 6: Generate Authelia Password Hash ## Step 7: Copy folders
```yaml
```bash sudo cp ~/EZ-Homelab/docker-compose /opt/stacks
# Generate password hash (takes 30-60 seconds) # Move the dockge stack outside the stacks folder so you can't accidently stop dockge from dockge's webui
docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password 'YourSecurePassword' sudo mv /opt/stacks/dockge /opt/dockge
# Copy the hash starting with $argon2id$...
``` ```
## Step 7: Configure Authelia ## Step 8: Configure Stacks
```bash Some services require manualy editing the configuration files.
# Copy Authelia config templates Each stack has a script `deploy-stackname.sh` which will replace variables in the config files and the traefik labels in the compose file.
mkdir -p /opt/stacks/core/authelia
cp config-templates/authelia/* /opt/stacks/core/authelia/
# Edit users_database.yml See the `Readme.md` file in each stack folder for manual instructions for each stack.
nano /opt/stacks/core/authelia/users_database.yml
# Replace password hash and email in the users section:
users:
admin:
displayname: "Admin User"
password: "$argon2id$v=19$m=65536,t=3,p=4$..." # Your hash here
email: your.email@example.com
groups:
- admins
- users
```
## Step 8: Deploy Core Services
```bash
# Deploy core infrastructure
sudo mkdir -p /opt/stacks/core
cp docker-compose/core/docker-compose.yml /opt/stacks/core/docker-compose.yml
cp -r config-templates/traefik /opt/stacks/core/
cp .env /opt/stacks/core/
# Update Traefik email
sed -i "s/admin@example.com/$ACME_EMAIL/" /opt/stacks/core/traefik/traefik.yml
cd /opt/stacks/core
docker compose up -d
```
## Step 9: Deploy Infrastructure Stack
```bash
sudo mkdir -p /opt/stacks/infrastructure
cp docker-compose/infrastructure/docker-compose.yml /opt/stacks/infrastructure/docker-compose.yml
cp .env /opt/stacks/infrastructure/
cd /opt/stacks/infrastructure
docker compose up -d
```
## Step 10: Deploy Dashboards
```bash
sudo mkdir -p /opt/stacks/dashboards
cp docker-compose/dashboards/docker-compose.yml /opt/stacks/dashboards/docker-compose.yml
cp -r config-templates/homepage /opt/stacks/dashboards/
cp .env /opt/stacks/dashboards/
# Replace Homepage domain variables
find /opt/stacks/dashboards/homepage -type f \( -name "*.yaml" -o -name "*.yml" \) -exec sed -i "s/{{HOMEPAGE_VAR_DOMAIN}}/$DOMAIN/g" {} \;
cd /opt/stacks/dashboards
docker compose up -d
```
## Step 10.5: Multi-Server TLS Setup (Optional)
If you plan to deploy services on remote servers (like Raspberry Pi) that will be managed by Sablier for lazy loading, set up shared TLS certificates.
### On Core Server (where Traefik/Authelia run):
```bash
# Create shared CA directory
sudo mkdir -p /opt/stacks/core/shared-ca
sudo chown $USER:$USER /opt/stacks/core/shared-ca
# Generate shared CA certificate
cd /opt/stacks/core/shared-ca
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem -subj "/C=US/ST=State/L=City/O=Homelab/CN=Homelab-CA"
# Set proper permissions
chmod 600 ca-key.pem
chmod 644 ca.pem
```
### On Remote Servers:
```bash
# Create TLS directory
sudo mkdir -p /opt/stacks/core/shared-ca
sudo chown $USER:$USER /opt/stacks/core/shared-ca
# Copy shared CA from core server (replace CORE_IP with your core server IP)
scp user@CORE_IP:/opt/stacks/core/shared-ca/ca.pem /opt/stacks/core/shared-ca/
scp user@CORE_IP:/opt/stacks/core/shared-ca/ca-key.pem /opt/stacks/core/shared-ca/
# Generate client certificate for Docker client connections
openssl genrsa -out client-key.pem 4096
openssl req -subj "/CN=client" -new -key client-key.pem -out client.csr
openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem
# Configure Docker TLS
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"tls": true,
"tlsverify": true,
"tlscacert": "/opt/stacks/core/shared-ca/ca.pem",
"tlscert": "/opt/stacks/core/shared-ca/server-cert.pem",
"tlskey": "/opt/stacks/core/shared-ca/server-key.pem"
}
EOF
# Update Docker service to listen on TLS port
sudo sed -i 's|-H fd://|-H fd:// -H tcp://0.0.0.0:2376|' /lib/systemd/system/docker.service
sudo systemctl daemon-reload
sudo systemctl restart docker
# Test TLS connection from core server
# On core server, run: docker --tlsverify --tlscacert /opt/stacks/core/shared-ca/ca.pem --tlscert /opt/stacks/core/shared-ca/client-cert.pem --tlskey /opt/stacks/core/shared-ca/client-key.pem -H tcp://REMOTE_IP:2376 ps
```
## Step 11: Verify Deployment
```bash
# Check running containers
docker ps
# Check logs if any service fails
docker logs container-name
# Access services
echo "Dockge: https://dockge.$DOMAIN"
echo "Authelia: https://auth.$DOMAIN"
echo "Traefik: https://traefik.$DOMAIN"
```
## Troubleshooting
**Permission issues:**
```bash
# Ensure proper ownership
sudo chown -R $USER:$USER /opt/stacks
# Check group membership
groups $USER
```
**Container won't start:**
```bash
# Check logs
docker logs container-name
# Check compose file syntax
docker compose config
```
**Network conflicts:**
```bash
# List existing networks
docker network ls
# Remove and recreate if needed
docker network rm network-name
docker network create network-name
```
## When to Use Manual Setup
- Automated script fails on your system
- You want full control over each step
- You're using a non-Debian/Ubuntu distribution
- You need custom configurations
- You're troubleshooting deployment issues
## Post-Setup Notes
- **Lazy Loading**: Services use Sablier for resource efficiency. Expect short delays on first access—refresh if you see timeouts.
- **Security**: SSO is enabled by default; bypass only for media apps like Plex/Jellyfin.
- **Testing**: This release has been thoroughly tested, but manual setup may need tweaks—report issues on GitHub.
## Switching to Automated
If manual setup works, you can switch to the unified automated script for future updates:
```bash
# Just run the unified setup script
cd ~/EZ-Homelab
./scripts/ez-homelab.sh
```
The unified script is idempotent - it won't break existing configurations.

View File

@@ -0,0 +1,98 @@
# Multi-Server Deployment with On-Demand Services
## Overview
This guide explains the **current multi-server architecture** where:
- **Core Server**: Handles external traffic (ports 80/443); runs DuckDNS, Traefik (multi-provider), Authelia
- **Remote Servers**: Run their own Traefik (local-only) and Sablier (local containers)
- **No Docker API**: Servers communicate via HTTP/HTTPS; no TLS Docker API connections needed
- **Independent Management**: Each server manages its own containers with lazy loading
> **Note**: This document describes the current simplified architecture. For the legacy centralized Sablier approach, see the git history.
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────┐
│ CORE SERVER │
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ DuckDNS │ │ Traefik │ │ Authelia │ │ Core Services │ │
│ │ (SSL DNS) │ │ (multi- │ │ (SSO) │ │ (local) │ │
│ │ │ │ provider)│ │ │ │ │ │
│ └────────────┘ └────┬─────┘ └──────────┘ └──────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ Routes: │ │ │
│ │ • Local │ (labels) │ │
│ │ • Remote │ (YAML files)│ │
└──────────┼──────────────┼──────────────┼────────────────────────┘
│ │ │
Ports │ HTTP/HTTPS │ │
80/443 │ │ │
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ REMOTE SERVER (e.g., Pi) │
│ ┌──────────┐ ┌──────────┐ ┌───────┐ │
│ │ Traefik │ │ Sablier │ │ Media │ │
│ │ (local) │ │ (local) │ │ Apps │ │
│ └──────────┘ └──────────┘ └───────┘ │
└─────────────────────────────────────────┘
```
# Deployment Process
## Step 1: Deploy Core Server
On the core server, run `ez-homelab.sh`
* Use Option 1 to Install Prerequesites
* Then Option 2 to Deploy Core Server
This deploys: DuckDNS, Traefik(core), Authelia, Dashboards & Infrastructure
From Dockge you can start/stop any of the stacks or containers.
**Port Forwarding**:
- Forward ports 80 & 443 from your router
- Only this server requires port forwarding
## Step 2: Deploy Remote Server
On the remote server, run `ez-homelab.sh`
* Use Option 1 to Install Prerequesites
* Then Option 3 to Deploy Remote Server
This deploys: Traefik(local), Dashboards & Infrastructure
From Dockge you can start/stop any of the stacks or containers.
## How It Works
### Traffic Flow
1. **User accesses** `https://sonarr.yourdomain.duckdns.org`
2. **Core Traefik** receives request:
- Checks Authelia for authentication (SSO)
- Routes to remote server: `http://192.168.1.100:8989`
3. **Remote Traefik** receives forwarded request:
- Discovers service is stopped (lazy loaded)
- Forwards to local Sablier
4. **Remote Sablier**:
- Starts Sonarr container
- Shows loading page
- Redirects to service when ready
5. **Service responds** through the chain back to user
### Key Benefits
- **Independent Management**: Each server controls its own containers
- **Centralized Access**: All services accessed through one domain
- **Unified SSO**: Authelia on core server protects all services
- **Local Lazy Loading**: Sablier manages containers on the same server
## Performance Considerations
- **Latency**: One additional hop (core → remote) adds minimal latency
- **Resource Usage**: Each server runs lightweight Traefik + Sablier (~100MB combined)
- **Scalability**: Can add unlimited remote servers without complexity
- **Network**: Internal 1Gbps+ recommended between servers

View File

@@ -1,5 +1,28 @@
# Multi-Server Traefik and Sablier Implementation Plan # Multi-Server Traefik and Sablier Implementation Plan
> **⚠️ IMPLEMENTATION STATUS: COMPLETED (with modified approach)**
>
> **Date**: December 2024
> **Status**: The multi-server architecture has been successfully implemented, but with a **simplified approach** that differs from this original plan.
>
> **What Changed:**
> - ❌ **Traefik Multi-Docker Provider**: NOT implemented (complexity, Docker API security concerns)
> - ✅ **Per-Server Traefik**: Each server runs its own Traefik for LOCAL discovery only
> - ✅ **Per-Server Sablier**: Each server runs its own Sablier for LOCAL lazy loading
> - ✅ **Core Routing**: Core Traefik uses file provider (YAML) to route to remote services
> - ✅ **No Docker API TLS**: Servers communicate via HTTP/HTTPS, not Docker API
>
> **Current Documentation:**
> - **Architecture**: See [Ondemand-Remote-Services.md](Ondemand-Remote-Services.md) for the implemented approach
> - **Service Creation**: See [docker-guidelines.md](docker-guidelines.md) for multi-server patterns
> - **How It Works**: See [how-it-works.md](how-it-works.md) for architecture overview
>
> **This Document**: Preserved for historical reference and future consideration. The original plan's centralized Traefik approach may be revisited in the future if label-based remote discovery becomes a priority.
---
# Original Implementation Plan (Historical Reference)
## Executive Summary ## Executive Summary
This document outlines the implementation plan for enabling label-based automatic routing and lazy loading across multiple servers in the EZ-Homelab infrastructure. The goals are to: This document outlines the implementation plan for enabling label-based automatic routing and lazy loading across multiple servers in the EZ-Homelab infrastructure. The goals are to:

View File

@@ -4,6 +4,8 @@ Congratulations! Your AI-powered homelab is now running. Here's what to do next.
## Access Your Services ## Access Your Services
### Single Server Deployment
- **Homepage**: `https://homepage.yourdomain.duckdns.org` - **Homepage**: `https://homepage.yourdomain.duckdns.org`
- Great place to start exploring your services - Great place to start exploring your services
- After configuring your services, come back and add widgets with API keys (optional) - After configuring your services, come back and add widgets with API keys (optional)
@@ -22,12 +24,35 @@ Congratulations! Your AI-powered homelab is now running. Here's what to do next.
- **VS Code**: `https://code.yourdomain.duckdns.org` - **VS Code**: `https://code.yourdomain.duckdns.org`
- Install GitHub Copilot Chat extension - Install GitHub Copilot Chat extension
- Open the AI-Homelab repository - Open the EZ-Homelab repository
- Use AI assistance for: - Use AI assistance for:
- Adding new services - Adding new services
- Configuring Traefik routing - Configuring Traefik routing
- Managing Docker stacks - Managing Docker stacks
### Multi-Server Deployment
If you deployed across multiple servers:
**Core Server Services** (accessed from anywhere):
- All services above (Homepage, Dockge, Authelia, Traefik, code-server)
- Access these through your domain: `service.yourdomain.duckdns.org`
**Remote Server Services** (accessed through core):
- Remote services automatically routed through core Traefik
- Example: `https://sonarr.yourdomain.duckdns.org` → Core → Remote Server
- SSO protection applied by core Authelia
**Direct Local Access** (on remote server network):
- Services also accessible via local IP: `http://192.168.1.100:8989`
- Useful for troubleshooting or local management
- No SSO protection on local access
**Management Tips:**
- Use core Dockge to manage all servers (if configured)
- Each server has its own local Traefik/Sablier for container management
- All services appear under unified domain through core routing
## Monitoring Services ## Monitoring Services
- Use Dockge to easily view live container logs - Use Dockge to easily view live container logs

View File

@@ -1,240 +0,0 @@
# EZ-Homelab Script Audit Report
## Generated: 30 January 2026
### Executive Summary
The `ez-homelab.sh` script is a comprehensive Bash-based deployment tool for the EZ-Homelab project. It handles system setup, Docker configuration, and multi-stage service deployment. The script supports three main deployment modes with varying complexity. While functional for infrastructure-only deployments (Option 3), the core-only deployment (Option 2) has critical issues with Authelia secret generation and configuration that prevent successful deployment.
### Script Architecture
#### Global Variables & Constants
- **Color codes**: RED, GREEN, YELLOW, BLUE, NC for console output formatting
- **Logging functions**: `log_info()`, `log_success()`, `log_warning()`, `log_error()`
- **Deployment flags**: DEPLOY_CORE, DEPLOY_INFRASTRUCTURE, DEPLOY_DASHBOARDS, SETUP_STACKS
- **Configuration variables**: DOMAIN, SERVER_IP, ADMIN_USER, etc.
- **Path variables**: SCRIPT_DIR, REPO_DIR, ACTUAL_USER
#### Core Functions
##### 1. `replace_env_placeholders()`
**Purpose**: Replaces `${VAR}` placeholders in files with actual environment variable values
**Process**:
- Takes file path as argument
- Uses `grep` to find all `${VAR}` patterns
- Checks if each variable exists in environment
- Uses `sed` for replacement: `s|\${VAR}|${!VAR}|g`
- Accumulates missing variables in `MISSING_VARS_SUMMARY`
**Issues**:
- Only reports missing variables at end, doesn't fail deployment
- No validation of replacement success
##### 2. `generate_shared_ca()`
**Purpose**: Creates shared Certificate Authority for multi-server TLS
**Process**:
- Creates `/opt/stacks/core/shared-ca/` directory
- Generates 4096-bit RSA CA key and certificate (365 days validity)
- Sets ownership to `$ACTUAL_USER:$ACTUAL_USER`
**Output**: ca.pem, ca-key.pem files
##### 3. `setup_multi_server_tls()`
**Purpose**: Configures TLS for remote Docker access using shared CA
**Process**:
- Prompts for core server IP if not set
- Tests SSH connectivity (key auth first, then password)
- Fetches CA certificates from core server via SCP
- Calls `setup_docker_tls()` if successful
**Issues**:
- Complex SSH authentication logic
- No fallback if CA fetch fails
- TLS_ISSUES_SUMMARY populated but not always accurate
##### 4. `load_env_file()`
**Purpose**: Loads existing configuration from `.env` file
**Process**:
- Checks for `$REPO_DIR/.env` existence
- Sources the file if found
- Displays current configuration values
- Returns 0 if file exists, 1 if not
**Issues**: No validation of loaded values
##### 5. `save_env_file()`
**Purpose**: Persists configuration to `.env` file
**Process**:
- Creates `.env` from `.env.example` if needed
- Updates values using `sed` replacements
- For core deployment: generates Authelia secrets and password hash
**Critical Issue**: Authelia secret generation is here, but may not be called in all deployment paths
##### 6. `prompt_for_values()`
**Purpose**: Interactive configuration collection
**Process**:
- Shows current/default values
- Allows user to accept defaults or enter custom values
- Handles sensitive inputs (passwords) with `-s` flag
- Sets ADMIN_* variables for core deployment
**Issues**: Complex logic with many conditional branches
##### 7. `system_setup()`
**Purpose**: Performs initial system configuration (requires root)
**Process**:
1. System package updates
2. Installs prerequisites (curl, wget, git, etc.)
3. Installs/configures Docker and Docker Compose
4. Generates shared CA
5. Configures Docker TLS
6. Sets up UFW firewall
7. Configures automatic updates
8. Creates Docker networks
9. Sets directory ownership
**Issues**:
- Requires logout/login for Docker group changes
- No rollback on failure
##### 8. `deploy_dockge()`
**Purpose**: Deploys Dockge stack management interface
**Process**:
- Copies compose file and .env to `/opt/dockge/`
- Replaces placeholders
- Runs `docker compose up -d`
**Output**: Dockge service running
##### 9. `deploy_core()`
**Purpose**: Deploys core infrastructure stack
**Process**:
1. Copies compose file and .env to `/opt/stacks/core/`
2. Copies Traefik and Authelia config templates
3. Replaces placeholders in all config files
4. Generates shared CA
5. Replaces Authelia-specific secrets and user data
6. Runs `docker compose up -d`
**Critical Issues**:
- Assumes Authelia secrets exist in environment
- No validation that secrets were generated
- Complex placeholder replacement logic
##### 10. `deploy_infrastructure()` / `deploy_dashboards()`
**Purpose**: Deploy additional service stacks
**Process**: Similar to deploy_core but simpler
- Copy files, replace placeholders, deploy
**Issues**: Conditional Authelia middleware removal when core not deployed
##### 11. `setup_docker_tls()`
**Purpose**: Configures Docker daemon for TLS
**Process**:
1. Creates TLS directory
2. Uses shared CA or generates local CA
3. Generates server and client certificates
4. Updates Docker daemon.json
5. Modifies systemd service for TCP 2376
6. Restarts Docker service
##### 12. `setup_stacks_for_dockge()`
**Purpose**: Prepares all service stacks for Dockge management
**Process**:
- Iterates through predefined stack list
- Copies compose files and configs
- Replaces placeholders
- Prepares but doesn't deploy stacks
### Deployment Flow Analysis
#### Option 1: Default Setup
**Flags**: DEPLOY_CORE=true, DEPLOY_INFRASTRUCTURE=true, DEPLOY_DASHBOARDS=true, SETUP_STACKS=true
**Flow**:
1. System setup (if needed)
2. Prompt for values
3. Save env file (generates Authelia secrets)
4. Deploy Dockge
5. Deploy core (uses generated secrets)
6. Deploy infrastructure
7. Deploy dashboards
8. Setup stacks for Dockge
#### Option 2: Core Only
**Flags**: DEPLOY_CORE=true, DEPLOY_INFRASTRUCTURE=false, DEPLOY_DASHBOARDS=true, SETUP_STACKS=true
**Flow**:
1. System setup (if needed)
2. Prompt for values
3. Save env file (generates Authelia secrets)
4. Deploy Dockge
5. Deploy core (uses generated secrets)
6. Deploy dashboards
7. Setup stacks for Dockge
#### Option 3: Infrastructure Only
**Flags**: DEPLOY_CORE=false, DEPLOY_INFRASTRUCTURE=true, DEPLOY_DASHBOARDS=false, SETUP_STACKS=true
**Flow**:
1. System setup (if needed)
2. Prompt for values
3. Save env file (no Authelia secrets generated)
4. Setup multi-server TLS
5. Deploy Dockge
6. Deploy infrastructure
7. Setup stacks for Dockge
### Critical Issues Identified
#### 1. Authelia Secret Generation Timing (Option 2)
**Problem**: In Option 2, `save_env_file()` is called and should generate Authelia secrets, but the deployment may fail if secrets aren't properly set.
**Root Cause**: The `save_env_file()` function generates secrets only when `DEPLOY_CORE=true`, but the generation logic may not execute or persist correctly.
**Impact**: Authelia container fails to start due to missing JWT_SECRET, SESSION_SECRET, or STORAGE_ENCRYPTION_KEY
#### 2. Environment Variable Persistence
**Problem**: After `save_env_file()`, the script sources the .env file, but there may be a timing issue where variables aren't available for `deploy_core()`.
**Evidence**: The script does `source "$REPO_DIR/.env"` in `perform_deployment()`, but if secrets weren't saved properly, they'll be empty.
#### 3. Placeholder Replacement Order
**Problem**: `replace_env_placeholders()` is called during deployment, but if environment variables are missing, replacements fail silently.
**Impact**: Configuration files contain literal `${VAR}` strings instead of actual values.
#### 4. Authelia Password Hash Generation
**Problem**: Password hash generation happens in `save_env_file()`, but requires Docker to be running and Authelia image to be available.
**Issues**:
- May fail if Docker isn't ready
- Uses complex docker run command that could timeout
- No fallback if hash generation fails
#### 5. Multi-Server TLS Complexity
**Problem**: `setup_multi_server_tls()` has complex SSH logic that can fail in multiple ways.
**Issues**:
- SSH key vs password detection unreliable
- No retry logic for connection failures
- Error reporting doesn't clearly indicate resolution steps
#### 6. Directory Creation Race Conditions
**Problem**: Script creates directories with sudo, then tries to write files as regular user.
**Potential Issue**: Permission conflicts if ownership isn't set correctly.
### Recommendations
#### Immediate Fixes for Option 2
1. **Add Secret Validation**: After `save_env_file()`, validate that all required Authelia secrets exist before proceeding with deployment.
2. **Improve Error Handling**: Make `replace_env_placeholders()` fail deployment if critical variables are missing.
3. **Add Authelia Health Check**: After core deployment, verify Authelia container is running and healthy.
#### Structural Improvements
1. **Separate Secret Generation**: Move Authelia secret generation to a dedicated function called before deployment.
2. **Add Pre-deployment Validation**: Create a validation function that checks all required environment variables and Docker state before starting deployment.
3. **Simplify TLS Setup**: Reduce complexity in multi-server TLS setup with better error handling and user guidance.
4. **Add Rollback Capability**: Implement cleanup functions for failed deployments.
#### Code Quality
1. **Reduce Function Complexity**: Break down large functions like `deploy_core()` into smaller, testable units.
2. **Add Logging**: Increase verbosity for debugging deployment issues.
3. **Configuration Management**: Consider using a configuration file format (YAML/JSON) instead of .env for complex setups.
### Testing Recommendations
1. **Unit Test Functions**: Test individual functions like `replace_env_placeholders()` and `generate_shared_ca()` in isolation.
2. **Integration Testing**: Test each deployment option in a clean environment.
3. **Error Scenario Testing**: Test failure modes (missing Docker, network issues, invalid credentials).
### Conclusion
The `ez-homelab.sh` script is a solid foundation for automated homelab deployment, but Option 2 (Core Only) has critical issues with Authelia secret management that prevent reliable deployment. The script needs focused improvements in error handling, validation, and secret generation to achieve the reliability required for critical infrastructure deployment.

View File

@@ -17,10 +17,12 @@
**Category:** Core Infrastructure **Category:** Core Infrastructure
**Docker Image:** [authelia/authelia](https://hub.docker.com/r/authelia/authelia) **Docker Image:** [authelia/authelia](https://hub.docker.com/r/authelia/authelia)
**Default Stack:** `core.yml` **Default Stack:** `core` (deployed on core server only)
**Web UI:** `https://auth.${DOMAIN}` **Web UI:** `https://auth.${DOMAIN}`
**Authentication:** Self-authenticating (login portal) **Authentication:** Self-authenticating (login portal)
**Multi-Server Note:** Authelia runs only on the core server and provides centralized SSO authentication for all services across all servers. Remote server services use the `authelia@docker` middleware to authenticate against the core server's Authelia instance.
## What is Authelia? ## What is Authelia?
Authelia is an open-source authentication and authorization server providing single sign-on (SSO) and two-factor authentication (2FA) for your applications via a web portal. It acts as a gatekeeper between Traefik and your services. Authelia is an open-source authentication and authorization server providing single sign-on (SSO) and two-factor authentication (2FA) for your applications via a web portal. It acts as a gatekeeper between Traefik and your services.

View File

@@ -15,10 +15,12 @@
**Category:** Core Infrastructure **Category:** Core Infrastructure
**Docker Image:** [linuxserver/duckdns](https://hub.docker.com/r/linuxserver/duckdns) **Docker Image:** [linuxserver/duckdns](https://hub.docker.com/r/linuxserver/duckdns)
**Default Stack:** `core.yml` **Default Stack:** `core` (deployed on core server only)
**Web UI:** No web interface (runs silently) **Web UI:** No web interface (runs silently)
**Authentication:** Not applicable **Authentication:** Not applicable
**Multi-Server Note:** DuckDNS runs only on the core server where Traefik generates the wildcard SSL certificate. This single certificate is used for all services across all servers in your homelab.
## What is DuckDNS? ## What is DuckDNS?
DuckDNS is a free dynamic DNS (DDNS) service that provides you with a memorable subdomain under `duckdns.org` and keeps it updated with your current IP address. It's perfect for homelabs where your ISP provides a dynamic IP address that changes periodically. DuckDNS is a free dynamic DNS (DDNS) service that provides you with a memorable subdomain under `duckdns.org` and keeps it updated with your current IP address. It's perfect for homelabs where your ISP provides a dynamic IP address that changes periodically.

View File

@@ -15,17 +15,25 @@
## Overview ## Overview
**Category:** Core Infrastructure **Category:** Resource Management
**Docker Image:** [sablierapp/sablier](https://hub.docker.com/r/sablierapp/sablier) **Docker Image:** [acouvreur/sablier](https://hub.docker.com/r/acouvreur/sablier)
**Default Stack:** `core.yml` **Default Stack:** `sablier` (separate stack, deployed on each server)
**Web UI:** No web UI (API only) **Web UI:** `https://sablier.${DOMAIN}` (on core server)
**Authentication:** None required **Authentication:** Protected by Authelia (SSO)
**Purpose:** On-demand container startup and resource management **Purpose:** On-demand container startup and resource management
**Multi-Server Note:** Each server runs its own Sablier instance that only manages local containers. This eliminates the need for remote Docker API connections and creates a more resilient architecture.
## What is Sablier? ## What is Sablier?
Sablier is a lightweight service that enables lazy loading for Docker containers. It automatically starts containers when they're accessed through Traefik and stops them after a period of inactivity, helping to conserve system resources and reduce power consumption. Sablier is a lightweight service that enables lazy loading for Docker containers. It automatically starts containers when they're accessed through Traefik and stops them after a period of inactivity, helping to conserve system resources and reduce power consumption.
**In EZ-Homelab's multi-server architecture**, each server runs its own Sablier instance managing only local containers, providing:
- **Decentralized Control**: No single point of failure
- **Simplified Networking**: No remote Docker API connections needed
- **Better Security**: Each Sablier only accesses local Docker socket
- **Independent Operation**: Remote servers function even if core server is down
### Key Features ### Key Features
- **On-Demand Startup:** Containers start automatically when accessed - **On-Demand Startup:** Containers start automatically when accessed
- **Automatic Shutdown:** Containers stop after configurable inactivity periods - **Automatic Shutdown:** Containers stop after configurable inactivity periods
@@ -63,24 +71,43 @@ When a request comes in for a service with Sablier enabled:
## Configuration in AI-Homelab ## Configuration in AI-Homelab
Sablier is deployed as part of the core infrastructure stack and requires no additional configuration for basic operation. It automatically discovers services with the appropriate labels. Sablier is deployed as a **separate stack** (`/opt/stacks/sablier/`) on each server and requires no additional configuration for basic operation. It automatically discovers services with the appropriate labels.
### Deployment Location
**Core Server:** `/opt/stacks/sablier/docker-compose.yml`
**Remote Servers:** `/opt/stacks/sablier/docker-compose.yml`
Each Sablier instance:
- Runs independently on its server
- Connects only to local Docker socket (`/var/run/docker.sock`)
- Manages containers on the same server only
- Has its own web dashboard (if enabled)
### Service Integration ### Service Integration
Add these labels to any service that should use lazy loading: Add these labels to any service that should use lazy loading:
**Local Service (same server as Sablier):**
```yaml ```yaml
services: services:
myservice: myservice:
# ... other configuration ... # ... other configuration ...
labels: labels:
- "sablier.enable=true" - "sablier.enable=true"
- "sablier.group=core-myservice" # Optional: group related services - "sablier.group=${SERVER_HOSTNAME}-myservice" # Include server hostname
- "sablier.start-on-demand=true"
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.myservice.rule=Host(`myservice.${DOMAIN}`)" - "traefik.http.routers.myservice.rule=Host(`myservice.${DOMAIN}`)"
# ... other Traefik labels ... # ... other Traefik labels ...
``` ```
**Note on Group Naming:** Always prefix group names with `${SERVER_HOSTNAME}` to avoid conflicts between servers:
- Core server: `core-myservice`
- Remote Pi: `pi-myservice`
- Remote NAS: `nas-myservice`
### Advanced Configuration ### Advanced Configuration
For services requiring custom timeouts or group management: For services requiring custom timeouts or group management:

View File

@@ -16,10 +16,12 @@
**Category:** Core Infrastructure **Category:** Core Infrastructure
**Docker Image:** [traefik](https://hub.docker.com/_/traefik) **Docker Image:** [traefik](https://hub.docker.com/_/traefik)
**Default Stack:** `core.yml` **Default Stack:** `core` (multi-provider on core server), `infrastructure` or separate (local-only on remote servers)
**Web UI:** `https://traefik.${DOMAIN}` **Web UI:** `https://traefik.${DOMAIN}`
**Authentication:** Protected by Authelia (SSO) **Authentication:** Protected by Authelia (SSO)
**Multi-Server Note:** The core server runs a multi-provider Traefik instance that discovers services across all servers. Remote servers run their own local Traefik instances for container discovery only.
## What is Traefik? ## What is Traefik?
Traefik is a modern HTTP reverse proxy and load balancer designed for microservices and containerized applications. It automatically discovers services and configures routing, making it ideal for Docker environments. Traefik is a modern HTTP reverse proxy and load balancer designed for microservices and containerized applications. It automatically discovers services and configures routing, making it ideal for Docker environments.
@@ -45,6 +47,7 @@ Traefik is a modern HTTP reverse proxy and load balancer designed for microservi
## How It Works ## How It Works
**Single Server Architecture:**
``` ```
Internet → Your Domain → Router (Port 80/443) → Traefik Internet → Your Domain → Router (Port 80/443) → Traefik
├→ Plex (plex.domain.com) ├→ Plex (plex.domain.com)
@@ -53,6 +56,18 @@ Internet → Your Domain → Router (Port 80/443) → Traefik
└→ [Other Services] └→ [Other Services]
``` ```
**Multi-Server Architecture:**
```
Internet → Router (Port 80/443) → Core Server Traefik (Multi-Provider)
├→ Local Services (plex.domain.com)
├→ Remote Server 1 via Docker TLS :2376
│ ├→ Remote Traefik (discovery only)
│ └→ Services (pi-service.domain.com)
└→ Remote Server 2 via Docker TLS :2376
├→ Remote Traefik (discovery only)
└→ Services (nas-service.domain.com)
```
### Request Flow ### Request Flow
1. **User visits** `https://plex.yourdomain.duckdns.org` 1. **User visits** `https://plex.yourdomain.duckdns.org`
@@ -83,6 +98,20 @@ Traefik automatically:
## Configuration in AI-Homelab ## Configuration in AI-Homelab
### Multi-Server Deployment
**Core Server (Primary Traefik):**
- Multi-provider configuration discovers services on all servers
- Receives all external traffic (ports 80/443 forwarded)
- Manages SSL certificates for all services
- Routes traffic to local and remote services
**Remote Servers (Secondary Traefik):**
- Local Docker provider only (discovers containers on same server)
- No external ports exposed
- Services use labels for local discovery
- Core Traefik connects via Docker TLS (port 2376)
### Directory Structure ### Directory Structure
``` ```
@@ -96,6 +125,8 @@ Traefik automatically:
### Static Configuration (`traefik.yml`) ### Static Configuration (`traefik.yml`)
**Core Server Configuration (Multi-Provider):**
```yaml ```yaml
api: api:
dashboard: true # Enable web dashboard dashboard: true # Enable web dashboard
@@ -119,20 +150,46 @@ certificatesResolvers:
acme: acme:
email: your-email@example.com email: your-email@example.com
storage: /acme.json storage: /acme.json
# For testing environments: Use Let's Encrypt staging to avoid rate limits
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory
dnsChallenge: dnsChallenge:
provider: duckdns provider: duckdns
# Note: Explicit resolvers can cause DNS propagation check failures
# Remove resolvers to use system's DNS for better DuckDNS TXT record resolution providers:
docker:
# Local Docker provider
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik-network
# Optional: Additional Docker providers for remote servers
# Uncomment and configure for multi-server setup
# docker:
# endpoint: "tcp://remote-server-ip:2376"
# tls:
# ca: /path/to/ca.pem
# cert: /path/to/cert.pem
# key: /path/to/key.pem
# exposedByDefault: false
file:
directory: /dynamic
watch: true
```
**Remote Server Configuration (Local Only):**
```yaml
api:
dashboard: false # Disable dashboard on remote servers
entryPoints:
websecure:
address: ":443" # Internal only, not exposed
providers: providers:
docker: docker:
endpoint: "unix:///var/run/docker.sock" endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false exposedByDefault: false
file: network: traefik-network
directory: /dynamic
watch: true
``` ```
### Environment Variables ### Environment Variables
@@ -144,6 +201,8 @@ ACME_EMAIL=your-email@example.com
### Service Labels Example ### Service Labels Example
**For Local Services (same server as Traefik):**
```yaml ```yaml
services: services:
myservice: myservice:
@@ -159,6 +218,42 @@ services:
- traefik-network - traefik-network
``` ```
**For Remote Services (different server):**
Don't use Traefik labels. Instead, create an external host file on the core server:
```yaml
# /opt/stacks/core/traefik/dynamic/external-host-remoteserver.yml
http:
routers:
myservice-remote:
rule: "Host(`myservice.${DOMAIN}`)"
entryPoints:
- websecure
service: myservice-remote
tls:
certResolver: letsencrypt
middlewares:
- authelia@docker
services:
myservice-remote:
loadBalancer:
servers:
- url: "http://remote-server-ip:8080"
passHostHeader: true
```
labels:
- "traefik.enable=true"
- "traefik.http.routers.myservice.rule=Host(`myservice.${DOMAIN}`)"
- "traefik.http.routers.myservice.entrypoints=websecure"
- "traefik.http.routers.myservice.tls=true" # Uses wildcard cert automatically
- "traefik.http.routers.myservice.middlewares=authelia@docker"
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
networks:
- traefik-network
```
## Official Resources ## Official Resources
- **Website:** https://traefik.io - **Website:** https://traefik.io

View File

@@ -1,235 +0,0 @@
# Services Overview
This document provides a comprehensive overview of all 50+ pre-configured services available in the AI-Homelab repository.
## Services Overview
| Stacks (12) | Services (70 + 6db) | SSO | Storage | Access URLs |
|-------|----------|-----|---------|-------------|
| **📦 core.yaml (4)** | **Deploy First** | | | |
| ├─ DuckDNS | Dynamic DNS updater | - | /opt/stacks/core/duckdns | No UI |
| ├─ Traefik | Reverse proxy + SSL | ✓ | /opt/stacks/core/traefik | traefik.${DOMAIN} |
| ├─ Authelia | SSO authentication | - | /opt/stacks/core/authelia | auth.${DOMAIN} |
| └─ Sablier | Lazy loading service | - | /opt/stacks/core/sablier | No UI |
| **🔒 vpn.yaml (2)** | **VPN Services** | | | |
| ├─ Gluetun | VPN (Surfshark) | - | /opt/stacks/vpn/gluetun | No UI |
| └─ qBittorrent | Torrent (via VPN) | ✓ | /mnt/downloads | qbit.${DOMAIN} |
| **🔧 infrastructure.yaml** (6)** | | | | |
| ├─ Pi-hole | DNS + Ad blocking | ✓ | /opt/stacks/infrastructure | pihole.${DOMAIN} |
| ├─ Watchtower | Auto container updates | - | /opt/stacks/infrastructure | No UI |
| ├─ Dozzle | Docker log viewer | ✓ | /opt/stacks/infrastructure | dozzle.${DOMAIN} |
| ├─ Glances | System monitoring | ✓ | /opt/stacks/infrastructure | glances.${DOMAIN} |
| ├─ Code Server | VS Code in browser | ✓ | /opt/stacks/infrastructure | code.${DOMAIN} |
| └─ Docker Proxy | Secure socket access | - | /opt/stacks/infrastructure | No UI |
| **📊 dashboards.yaml** (2) | | | | |
| ├─ Homepage | App dashboard (AI cfg) | ✓ | /opt/stacks/dashboards | home.${DOMAIN} |
| └─ Homarr | Modern dashboard | ✓ | /opt/stacks/dashboards | homarr.${DOMAIN} |
| **🎬 media.yaml** (2) | | | | |
| ├─ Jellyfin | Media server (OSS) | ✗ | /mnt/media, /mnt/transcode | jellyfin.${DOMAIN} |
| └─ Calibre-Web | Ebook reader | ✓ | /opt/stacks/media, /mnt/media | calibre.${DOMAIN} |
| **📺 media-management.yaml** (9) | | | | |
| ├─ Sonarr | TV automation | ✓ | /opt/stacks/media-management, /mnt/media | sonarr.${DOMAIN} |
| ├─ Radarr | Movie automation | ✓ | /opt/stacks/media-management, /mnt/media | radarr.${DOMAIN} |
| ├─ Prowlarr | Indexer manager | ✓ | /opt/stacks/media-management | prowlarr.${DOMAIN} |
| ├─ Readarr | Ebooks/Audiobooks | ✓ | /opt/stacks/media-management, /mnt/media | readarr.${DOMAIN} |
| ├─ Lidarr | Music manager | ✓ | /opt/stacks/media-management, /mnt/media | lidarr.${DOMAIN} |
| ├─ Lazy Librarian | Book automation | ✓ | /opt/stacks/media-management, /mnt/media | lazylibrarian.${DOMAIN} |
| ├─ Mylar3 | Comic manager | ✓ | /opt/stacks/media-management, /mnt/media | mylar.${DOMAIN} |
| ├─ Jellyseerr | Media requests | ✓ | /opt/stacks/media-management | jellyseerr.${DOMAIN} |
| └─ FlareSolverr | Cloudflare bypass | - | /opt/stacks/media-management | No UI |
| **🔄 transcoders.yaml** (3) | | | | |
| ├─ Tdarr Server | Transcoding server | ✓ | /opt/stacks/transcoders, /mnt/transcode | tdarr.${DOMAIN} |
| ├─ Tdarr Node | Transcoding worker | - | /mnt/transcode-cache | No UI |
| └─ Unmanic | Library optimizer | ✓ | /opt/stacks/transcoders, /mnt/transcode | unmanic.${DOMAIN} |
| **📖 wikis.yaml** (4) | | | | |
| ├─ DokuWiki | File-based wiki | ✓ | /opt/stacks/wikis | dokuwiki.${DOMAIN} |
| ├─ BookStack | Documentation | ✓ | /opt/stacks/wikis | docs.${DOMAIN} |
| │ └─ bookstack-db | MariaDB | - | /opt/stacks/wikis | No UI |
| └─ MediaWiki | Wiki platform | ✓ | /opt/stacks/wikis | mediawiki.${DOMAIN} |
| **🏠 homeassistant.yaml** (6) | | | | |
| ├─ Home Assistant | HA platform | ✗ | /opt/stacks/homeassistant | ha.${DOMAIN} |
| ├─ ESPHome | ESP firmware mgr | ✓ | /opt/stacks/homeassistant | esphome.${DOMAIN} |
| ├─ TasmoAdmin | Tasmota device mgr | ✓ | /opt/stacks/homeassistant | tasmoadmin.${DOMAIN} |
| ├─ Node-RED | Automation flows | ✓ | /opt/stacks/homeassistant | nodered.${DOMAIN} |
| ├─ Mosquitto | MQTT broker | - | /opt/stacks/homeassistant | Ports 1883, 9001 |
| └─ Zigbee2MQTT | Zigbee bridge | ✓ | /opt/stacks/homeassistant | zigbee2mqtt.${DOMAIN} |
| **💼 productivity.yaml** (8 + 6 DBs) | | | | |
| ├─ Nextcloud | File sync platform | ✓ | /opt/stacks/productivity, /mnt/nextcloud | nextcloud.${DOMAIN} |
| │ └─ nextcloud-db | MariaDB | - | /opt/stacks/productivity | No UI |
| ├─ Mealie | Recipe manager | ✗ | /opt/stacks/productivity | mealie.${DOMAIN} |
| ├─ WordPress | Blog platform | ✗ | /opt/stacks/productivity | blog.${DOMAIN} |
| │ └─ wordpress-db | MariaDB | - | /opt/stacks/productivity | No UI |
| ├─ Gitea | Git service | ✓ | /opt/stacks/productivity, /mnt/git | git.${DOMAIN} |
| │ └─ gitea-db | PostgreSQL | - | /opt/stacks/productivity | No UI |
| └─ Jupyter Lab | Notebooks | ✓ | /opt/stacks/productivity | jupyter.${DOMAIN} |
| **🛠️ utilities.yaml** (5) | | | | |
| ├─ Vaultwarden | Password manager | ✗ | /opt/stacks/utilities | bitwarden.${DOMAIN} |
| ├─ Backrest | Backup (restic) | ✓ | /opt/stacks/utilities, /mnt/backups | backrest.${DOMAIN} |
| ├─ Duplicati | Encrypted backups | ✓ | /opt/stacks/utilities, /mnt/backups | duplicati.${DOMAIN} |
| ├─ Form.io | Form builder | ✓ | /opt/stacks/utilities | forms.${DOMAIN} |
| │ └─ formio-mongo | MongoDB | - | /opt/stacks/utilities | No UI |
| └─ Authelia-Redis | Session storage | - | /opt/stacks/utilities | No UI |
| **📈 monitoring.yaml** (8) | | | | |
| ├─ Prometheus | Metrics collection | ✓ | /opt/stacks/monitoring | prometheus.${DOMAIN} |
| ├─ Grafana | Visualization | ✓ | /opt/stacks/monitoring | grafana.${DOMAIN} |
| ├─ Loki | Log aggregation | - | /opt/stacks/monitoring | Via Grafana |
| ├─ Promtail | Log shipper | - | /opt/stacks/monitoring | No UI |
| ├─ Node Exporter | Host metrics | - | /opt/stacks/monitoring | No UI |
| ├─ cAdvisor | Container metrics | - | /opt/stacks/monitoring | Internal :8080 |
| └─ Uptime Kuma | Uptime monitoring | ✓ | /opt/stacks/monitoring | status.${DOMAIN} |
| **🔧 alternatives.yaml** (6) | | | | |
| ├─ Dockge | Stack manager (PRIMARY) | ✓ | /opt/stacks/alternatives | dockge.${DOMAIN} |
| ├─ Portainer | Container management | ✓ | /opt/stacks/alternatives | portainer.${DOMAIN} |
| ├─ Authentik Server | SSO with web UI | ✓ | /opt/stacks/alternatives | authentik.${DOMAIN} |
| │ ├─ authentik-worker | Background tasks | - | /opt/stacks/alternatives | No UI |
| │ ├─ authentik-db | PostgreSQL | - | /opt/stacks/alternatives | No UI |
| │ └─ authentik-redis | Cache/messaging | - | /opt/stacks/alternatives | No UI |
| └─ Plex | Media server | ✗ | /mnt/media, /mnt/transcode | plex.${DOMAIN} |
| **🏠 homeassistant.yaml** (7) | | | | |
| ├─ Home Assistant | HA platform | ✗ | /opt/stacks/homeassistant | ha.${DOMAIN} |
| ├─ ESPHome | ESP firmware mgr | ✓ | /opt/stacks/homeassistant | esphome.${DOMAIN} |
| ├─ TasmoAdmin | Tasmota device mgr | ✓ | /opt/stacks/homeassistant | tasmoadmin.${DOMAIN} |
| ├─ Node-RED | Automation flows | ✓ | /opt/stacks/homeassistant | nodered.${DOMAIN} |
| ├─ Mosquitto | MQTT broker | - | /opt/stacks/homeassistant | Ports 1883, 9001 |
| ├─ Zigbee2MQTT | Zigbee bridge | ✓ | /opt/stacks/homeassistant | zigbee2mqtt.${DOMAIN} |
| └─ MotionEye | Video surveillance | ✓ | /opt/stacks/homeassistant, /mnt/surveillance | motioneye.${DOMAIN} |
| **💼 productivity.yaml** (8 + 6 DBs) | | | | |
| ├─ Nextcloud | File sync platform | ✓ | /opt/stacks/productivity, /mnt/nextcloud | nextcloud.${DOMAIN} |
| │ └─ nextcloud-db | MariaDB | - | /opt/stacks/productivity | No UI |
| ├─ Mealie | Recipe manager | ✗ | /opt/stacks/productivity | mealie.${DOMAIN} |
| ├─ WordPress | Blog platform | ✗ | /opt/stacks/productivity | blog.${DOMAIN} |
| │ └─ wordpress-db | MariaDB | - | /opt/stacks/productivity | No UI |
| ├─ Gitea | Git service | ✓ | /opt/stacks/productivity, /mnt/git | git.${DOMAIN} |
| │ └─ gitea-db | PostgreSQL | - | /opt/stacks/productivity | No UI |
| ├─ DokuWiki | File-based wiki | ✓ | /opt/stacks/productivity | wiki.${DOMAIN} |
| ├─ BookStack | Documentation | ✓ | /opt/stacks/productivity | docs.${DOMAIN} |
| │ └─ bookstack-db | MariaDB | - | /opt/stacks/productivity | No UI |
| ├─ MediaWiki | Wiki platform | ✓ | /opt/stacks/productivity | mediawiki.${DOMAIN} |
| │ └─ mediawiki-db | MariaDB | - | /opt/stacks/productivity | No UI |
| └─ Form.io | Form builder | ✓ | /opt/stacks/productivity | forms.${DOMAIN} |
| └─ formio-mongo | MongoDB | - | /opt/stacks/productivity | No UI |
| **🛠️ utilities.yaml** (7) | | | | |
| ├─ Vaultwarden | Password manager | ✗ | /opt/stacks/utilities | bitwarden.${DOMAIN} |
| ├─ Backrest | Backup (restic) | ✓ | /opt/stacks/utilities, /mnt/backups | backrest.${DOMAIN} |
| ├─ Duplicati | Encrypted backups | ✓ | /opt/stacks/utilities, /mnt/backups | duplicati.${DOMAIN} |
| ├─ Code Server | VS Code in browser | ✓ | /opt/stacks/utilities | code.${DOMAIN} |
| ├─ Form.io | Form platform | ✓ | /opt/stacks/utilities | forms.${DOMAIN} |
| │ └─ formio-mongo | MongoDB | - | /opt/stacks/utilities | No UI |
| └─ Authelia-Redis | Session storage | - | /opt/stacks/utilities | No UI |
| **📈 monitoring.yaml** (8) | | | | |
| ├─ Prometheus | Metrics collection | ✓ | /opt/stacks/monitoring | prometheus.${DOMAIN} |
| ├─ Grafana | Visualization | ✓ | /opt/stacks/monitoring | grafana.${DOMAIN} |
| ├─ Loki | Log aggregation | - | /opt/stacks/monitoring | Via Grafana |
| ├─ Promtail | Log shipper | - | /opt/stacks/monitoring | No UI |
| ├─ Node Exporter | Host metrics | - | /opt/stacks/monitoring | No UI |
| ├─ cAdvisor | Container metrics | - | /opt/stacks/monitoring | Internal :8080 |
| └─ Uptime Kuma | Uptime monitoring | ✓ | /opt/stacks/monitoring | status.${DOMAIN} |
| **👨‍💻 development.yaml** (6) | | | | |
| ├─ GitLab CE | Git + CI/CD | ✓ | /opt/stacks/development, /mnt/git | gitlab.${DOMAIN} |
| ├─ PostgreSQL | SQL database | - | /opt/stacks/development | Port 5432 |
| ├─ Redis | In-memory store | - | /opt/stacks/development | Port 6379 |
| ├─ pgAdmin | PostgreSQL UI | ✓ | /opt/stacks/development | pgadmin.${DOMAIN} |
| ├─ Jupyter Lab | Notebooks | ✓ | /opt/stacks/development | jupyter.${DOMAIN} |
| └─ Code Server | VS Code | ✓ | /opt/stacks/development | code.${DOMAIN} |
**Legend:** ✓ = Protected by SSO | ✗ = Bypasses SSO | - = No web UI
## Quick Deployment Order
1. **Create Networks** (one-time setup)
```bash
docker network create traefik-network
docker network create homelab-network
docker network create dockerproxy-network
```
2. **Deploy Core Stack** (required first)
```bash
cd /opt/stacks/core/
docker compose up -d
```
3. **Deploy Infrastructure**
```bash
cd /opt/stacks/infrastructure/
docker compose up -d
```
4. **Deploy Dashboards**
```bash
cd /opt/stacks/dashboards/
docker compose up -d
```
5. **Deploy Additional Stacks** (as needed)
- Media: `/opt/stacks/media/`
- Media Management: `/opt/stacks/media-management/`
- Transcoders: `/opt/stacks/transcoders/`
- Wikis: `/opt/stacks/wikis/`
- Home Automation: `/opt/stacks/homeassistant/`
- Productivity: `/opt/stacks/productivity/`
- Utilities: `/opt/stacks/utilities/`
- Monitoring: `/opt/stacks/monitoring/`
- Alternatives: `/opt/stacks/alternatives/`
## Toggling SSO (Authelia) On/Off
You can easily enable or disable SSO protection for any service by modifying its Traefik labels in the docker-compose.yml file.
### To Enable SSO on a Service
Add the Authelia middleware to the service's Traefik labels:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.servicename.rule=Host(`servicename.${DOMAIN}`)"
- "traefik.http.routers.servicename.entrypoints=websecure"
- "traefik.http.routers.servicename.tls.certresolver=letsencrypt"
- "traefik.http.routers.servicename.middlewares=authelia@docker" # ← Add this line
- "traefik.http.services.servicename.loadbalancer.server.port=8080"
```
### To Disable SSO on a Service
Comment out (don't remove) the middleware line:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.servicename.rule=Host(`servicename.${DOMAIN}`)"
- "traefik.http.routers.servicename.entrypoints=websecure"
- "traefik.http.routers.servicename.tls.certresolver=letsencrypt"
# - "traefik.http.routers.servicename.middlewares=authelia@docker" # ← Commented out (not removed)
- "traefik.http.services.servicename.loadbalancer.server.port=8080"
```
After making changes, redeploy the service:
```bash
# From inside the stack directory
cd /opt/stacks/stack-name/
docker compose up -d
# Or from anywhere, using the full path
docker compose -f /opt/stacks/stack-name/docker-compose.yml up -d
```
**Stopping a Service:**
```bash
# From inside the stack directory
cd /opt/stacks/stack-name/
docker compose down
# Or from anywhere, using the full path
docker compose -f /opt/stacks/stack-name/docker-compose.yml down
```
**Use Cases for Development/Production:**
- **Security First**: All services start with SSO enabled by default for maximum security
- **Development**: Keep SSO enabled to protect services during testing
- **Production**: Disable SSO only for services needing direct app/API access (Plex, Jellyfin)
- **Gradual Exposure**: Comment out SSO only when ready to expose a service
- **Quick Toggle**: AI assistant can modify these labels automatically when you ask

View File

@@ -6,22 +6,18 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| Stacks (12) | Services (50 + 6db) | SSO | Storage | Access URLs | | Stacks (12) | Services (50 + 6db) | SSO | Storage | Access URLs |
|-------|----------|-----|---------|-------------| |-------|----------|-----|---------|-------------|
| **🔀 alternatives.yaml** (6 + 3 DBs) | | | | | | **📦 core.yaml (3)** | **Deploy First** | | | |
| ├─ Dockge | Stack manager (PRIMARY) | ✓ | /opt/stacks/alternatives | dockge.${DOMAIN} |
| ├─ Portainer | Container management | ✓ | /opt/stacks/alternatives | portainer.${DOMAIN} |
| ├─ Authentik Server | SSO with web UI | ✓ | /opt/stacks/alternatives | authentik.${DOMAIN} |
| │ ├─ authentik-worker | Background tasks | - | /opt/stacks/alternatives | No UI |
| │ ├─ authentik-db | PostgreSQL | - | /opt/stacks/alternatives | No UI |
| │ └─ authentik-redis | Cache/messaging | - | /opt/stacks/alternatives | No UI |
| └─ Plex | Media server | ✗ | /mnt/media, /mnt/transcode | plex.${DOMAIN} |
| **📦 core.yaml (4)** | **Deploy First** | | | |
| ├─ DuckDNS | Dynamic DNS updater | - | /opt/stacks/core/duckdns | No UI | | ├─ DuckDNS | Dynamic DNS updater | - | /opt/stacks/core/duckdns | No UI |
| ├─ Traefik | Reverse proxy + SSL | ✓ | /opt/stacks/core/traefik | traefik.${DOMAIN} | | ├─ Traefik | Reverse proxy + SSL | ✓ | /opt/stacks/core/traefik | traefik.${DOMAIN} |
| ─ Authelia | SSO authentication | - | /opt/stacks/core/authelia | auth.${DOMAIN} | | ─ Authelia | SSO authentication | - | /opt/stacks/core/authelia | auth.${DOMAIN} |
| └─ Sablier | Lazy loading service | - | /opt/stacks/core/sablier | No UI | | &nbsp;
| **🔄 sablier.yaml (1)** | **Deploy on Each Server** | | | |
| └─ Sablier | Lazy loading service | - | /opt/stacks/sablier | sablier.${DOMAIN} |
| &nbsp;
| **📊 dashboards.yaml** (2) | | | | | | **📊 dashboards.yaml** (2) | | | | |
| ├─ Homepage | App dashboard (AI cfg) | ✓ | /opt/stacks/dashboards | home.${DOMAIN} | | ├─ Homepage | App dashboard (AI cfg) | ✓ | /opt/stacks/dashboards | home.${DOMAIN} |
| └─ Homarr | Modern dashboard | ✓ | /opt/stacks/dashboards | homarr.${DOMAIN} | | └─ Homarr | Modern dashboard | ✓ | /opt/stacks/dashboards | homarr.${DOMAIN} |
| &nbsp;
| **🏠 homeassistant.yaml** (7) | | | | | | **🏠 homeassistant.yaml** (7) | | | | |
| ├─ Home Assistant | HA platform | ✗ | /opt/stacks/homeassistant | ha.${DOMAIN} | | ├─ Home Assistant | HA platform | ✗ | /opt/stacks/homeassistant | ha.${DOMAIN} |
| ├─ ESPHome | ESP firmware mgr | ✓ | /opt/stacks/homeassistant | esphome.${DOMAIN} | | ├─ ESPHome | ESP firmware mgr | ✓ | /opt/stacks/homeassistant | esphome.${DOMAIN} |
@@ -30,6 +26,7 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| ├─ Mosquitto | MQTT broker | - | /opt/stacks/homeassistant | Ports 1883, 9001 | | ├─ Mosquitto | MQTT broker | - | /opt/stacks/homeassistant | Ports 1883, 9001 |
| ├─ Zigbee2MQTT | Zigbee bridge | ✓ | /opt/stacks/homeassistant | zigbee2mqtt.${DOMAIN} | | ├─ Zigbee2MQTT | Zigbee bridge | ✓ | /opt/stacks/homeassistant | zigbee2mqtt.${DOMAIN} |
| └─ MotionEye | Video surveillance | ✓ | /opt/stacks/homeassistant, /mnt/surveillance | motioneye.${DOMAIN} | | └─ MotionEye | Video surveillance | ✓ | /opt/stacks/homeassistant, /mnt/surveillance | motioneye.${DOMAIN} |
| &nbsp;
| **🔧 infrastructure.yaml** (6)** | | | | | | **🔧 infrastructure.yaml** (6)** | | | | |
| ├─ Pi-hole | DNS + Ad blocking | ✓ | /opt/stacks/infrastructure | pihole.${DOMAIN} | | ├─ Pi-hole | DNS + Ad blocking | ✓ | /opt/stacks/infrastructure | pihole.${DOMAIN} |
| ├─ Watchtower | Auto container updates | - | /opt/stacks/infrastructure | No UI | | ├─ Watchtower | Auto container updates | - | /opt/stacks/infrastructure | No UI |
@@ -37,6 +34,7 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| ├─ Glances | System monitoring | ✓ | /opt/stacks/infrastructure | glances.${DOMAIN} | | ├─ Glances | System monitoring | ✓ | /opt/stacks/infrastructure | glances.${DOMAIN} |
| ├─ Code Server | VS Code in browser | ✓ | /opt/stacks/infrastructure | code.${DOMAIN} | | ├─ Code Server | VS Code in browser | ✓ | /opt/stacks/infrastructure | code.${DOMAIN} |
| └─ Docker Proxy | Secure socket access | - | /opt/stacks/infrastructure | No UI | | └─ Docker Proxy | Secure socket access | - | /opt/stacks/infrastructure | No UI |
| &nbsp;
| **📺 media-management.yaml** (9) | | | | | | **📺 media-management.yaml** (9) | | | | |
| ├─ Sonarr | TV automation | ✓ | /opt/stacks/media-management, /mnt/media | sonarr.${DOMAIN} | | ├─ Sonarr | TV automation | ✓ | /opt/stacks/media-management, /mnt/media | sonarr.${DOMAIN} |
| ├─ Radarr | Movie automation | ✓ | /opt/stacks/media-management, /mnt/media | radarr.${DOMAIN} | | ├─ Radarr | Movie automation | ✓ | /opt/stacks/media-management, /mnt/media | radarr.${DOMAIN} |
@@ -47,9 +45,11 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| ├─ Mylar3 | Comic manager | ✓ | /opt/stacks/media-management, /mnt/media | mylar.${DOMAIN} | | ├─ Mylar3 | Comic manager | ✓ | /opt/stacks/media-management, /mnt/media | mylar.${DOMAIN} |
| ├─ Jellyseerr | Media requests | ✓ | /opt/stacks/media-management | jellyseerr.${DOMAIN} | | ├─ Jellyseerr | Media requests | ✓ | /opt/stacks/media-management | jellyseerr.${DOMAIN} |
| └─ FlareSolverr | Cloudflare bypass | - | /opt/stacks/media-management | No UI | | └─ FlareSolverr | Cloudflare bypass | - | /opt/stacks/media-management | No UI |
| &nbsp;
| **🎬 media.yaml** (2) | | | | | | **🎬 media.yaml** (2) | | | | |
| ├─ Jellyfin | Media server (OSS) | ✗ | /mnt/media, /mnt/transcode | jellyfin.${DOMAIN} | | ├─ Jellyfin | Media server (OSS) | ✗ | /mnt/media, /mnt/transcode | jellyfin.${DOMAIN} |
| └─ Calibre-Web | Ebook reader | ✓ | /opt/stacks/media, /mnt/media | calibre.${DOMAIN} | | └─ Calibre-Web | Ebook reader | ✓ | /opt/stacks/media, /mnt/media | calibre.${DOMAIN} |
| &nbsp;
| **📈 monitoring.yaml** (8) | | | | | | **📈 monitoring.yaml** (8) | | | | |
| ├─ Prometheus | Metrics collection | ✓ | /opt/stacks/monitoring | prometheus.${DOMAIN} | | ├─ Prometheus | Metrics collection | ✓ | /opt/stacks/monitoring | prometheus.${DOMAIN} |
| ├─ Grafana | Visualization | ✓ | /opt/stacks/monitoring | grafana.${DOMAIN} | | ├─ Grafana | Visualization | ✓ | /opt/stacks/monitoring | grafana.${DOMAIN} |
@@ -58,6 +58,7 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| ├─ Node Exporter | Host metrics | - | /opt/stacks/monitoring | No UI | | ├─ Node Exporter | Host metrics | - | /opt/stacks/monitoring | No UI |
| ├─ cAdvisor | Container metrics | - | /opt/stacks/monitoring | Internal :8080 | | ├─ cAdvisor | Container metrics | - | /opt/stacks/monitoring | Internal :8080 |
| └─ Uptime Kuma | Uptime monitoring | ✓ | /opt/stacks/monitoring | status.${DOMAIN} | | └─ Uptime Kuma | Uptime monitoring | ✓ | /opt/stacks/monitoring | status.${DOMAIN} |
| &nbsp;
| **💼 productivity.yaml** (5 + 4 DBs) | | | | | | **💼 productivity.yaml** (5 + 4 DBs) | | | | |
| ├─ Nextcloud | File sync platform | ✓ | /opt/stacks/productivity, /mnt/nextcloud | nextcloud.${DOMAIN} | | ├─ Nextcloud | File sync platform | ✓ | /opt/stacks/productivity, /mnt/nextcloud | nextcloud.${DOMAIN} |
| │ └─ nextcloud-db | MariaDB | - | /opt/stacks/productivity | No UI | | │ └─ nextcloud-db | MariaDB | - | /opt/stacks/productivity | No UI |
@@ -67,10 +68,12 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| ├─ Gitea | Git service | ✓ | /opt/stacks/productivity, /mnt/git | git.${DOMAIN} | | ├─ Gitea | Git service | ✓ | /opt/stacks/productivity, /mnt/git | git.${DOMAIN} |
| │ └─ gitea-db | PostgreSQL | - | /opt/stacks/productivity | No UI | | │ └─ gitea-db | PostgreSQL | - | /opt/stacks/productivity | No UI |
| └─ Jupyter Lab | Notebooks | ✓ | /opt/stacks/productivity | jupyter.${DOMAIN} | | └─ Jupyter Lab | Notebooks | ✓ | /opt/stacks/productivity | jupyter.${DOMAIN} |
| &nbsp;
| **🔄 transcoders.yaml** (3) | | | | | | **🔄 transcoders.yaml** (3) | | | | |
| ├─ Tdarr Server | Transcoding server | ✓ | /opt/stacks/transcoders, /mnt/transcode | tdarr.${DOMAIN} | | ├─ Tdarr Server | Transcoding server | ✓ | /opt/stacks/transcoders, /mnt/transcode | tdarr.${DOMAIN} |
| ├─ Tdarr Node | Transcoding worker | - | /mnt/transcode-cache | No UI | | ├─ Tdarr Node | Transcoding worker | - | /mnt/transcode-cache | No UI |
| └─ Unmanic | Library optimizer | ✓ | /opt/stacks/transcoders, /mnt/transcode | unmanic.${DOMAIN} | | └─ Unmanic | Library optimizer | ✓ | /opt/stacks/transcoders, /mnt/transcode | unmanic.${DOMAIN} |
| &nbsp;
| **🛠️ utilities.yaml** (7) | | | | | | **🛠️ utilities.yaml** (7) | | | | |
| ├─ Vaultwarden | Password manager | ✗ | /opt/stacks/utilities | bitwarden.${DOMAIN} | | ├─ Vaultwarden | Password manager | ✗ | /opt/stacks/utilities | bitwarden.${DOMAIN} |
| ├─ Backrest | Backup (restic) | ✓ | /opt/stacks/utilities, /mnt/backups | backrest.${DOMAIN} | | ├─ Backrest | Backup (restic) | ✓ | /opt/stacks/utilities, /mnt/backups | backrest.${DOMAIN} |
@@ -79,151 +82,55 @@ This document provides a comprehensive overview of all 50+ pre-configured servic
| ├─ Form.io | Form platform | ✓ | /opt/stacks/utilities | forms.${DOMAIN} | | ├─ Form.io | Form platform | ✓ | /opt/stacks/utilities | forms.${DOMAIN} |
| │ └─ formio-mongo | MongoDB | - | /opt/stacks/utilities | No UI | | │ └─ formio-mongo | MongoDB | - | /opt/stacks/utilities | No UI |
| └─ Authelia-Redis | Session storage | - | /opt/stacks/utilities | No UI | | └─ Authelia-Redis | Session storage | - | /opt/stacks/utilities | No UI |
| &nbsp;
| **🔒 vpn.yaml (2)** | **VPN Services** | | | | | **🔒 vpn.yaml (2)** | **VPN Services** | | | |
| ├─ Gluetun | VPN (Surfshark) | - | /opt/stacks/vpn/gluetun | No UI | | ├─ Gluetun | VPN (Surfshark) | - | /opt/stacks/vpn/gluetun | No UI |
| └─ qBittorrent | Torrent (via VPN) | ✓ | /mnt/downloads | qbit.${DOMAIN} | | └─ qBittorrent | Torrent (via VPN) | ✓ | /mnt/downloads | qbit.${DOMAIN} |
| &nbsp;
| **📖 wikis.yaml** (4) | | | | | | **📖 wikis.yaml** (4) | | | | |
| ├─ DokuWiki | File-based wiki | ✓ | /opt/stacks/wikis | dokuwiki.${DOMAIN} | | ├─ DokuWiki | File-based wiki | ✓ | /opt/stacks/wikis | dokuwiki.${DOMAIN} |
| ├─ BookStack | Documentation | ✓ | /opt/stacks/wikis | docs.${DOMAIN} | | ├─ BookStack | Documentation | ✓ | /opt/stacks/wikis | docs.${DOMAIN} |
| │ └─ bookstack-db | MariaDB | - | /opt/stacks/wikis | No UI | | │ └─ bookstack-db | MariaDB | - | /opt/stacks/wikis | No UI |
| └─ MediaWiki | Wiki platform | ✓ | /opt/stacks/wikis | mediawiki.${DOMAIN} | | └─ MediaWiki | Wiki platform | ✓ | /opt/stacks/wikis | mediawiki.${DOMAIN} |
| &nbsp;
| **🔀 alternatives.yaml** (6 + 3 DBs) | | | | |
| ├─ Portainer | Container management | ✓ | /opt/stacks/alternatives | portainer.${DOMAIN} |
| ├─ Authentik Server | SSO with web UI | ✓ | /opt/stacks/alternatives | authentik.${DOMAIN} |
| │ ├─ authentik-worker | Background tasks | - | /opt/stacks/alternatives | No UI |
| │ ├─ authentik-db | PostgreSQL | - | /opt/stacks/alternatives | No UI |
| │ └─ authentik-redis | Cache/messaging | - | /opt/stacks/alternatives | No UI |
| └─ Plex | Media server | ✗ | /mnt/media, /mnt/transcode | plex.${DOMAIN} |
**Legend:** ✓ = Protected by SSO | ✗ = Bypasses SSO | - = No web UI **Legend:** ✓ = Protected by SSO | ✗ = Bypasses SSO | - = No web UI
## Quick Deployment Order ## Service Configuration
1. **Create Networks** (one-time setup) Some services require no initial configuration (ie. creates it on first run if needed)
```bash
docker network create traefik-network
docker network create homelab-network
docker network create dockerproxy-network
```
2. **Deploy Core Stack** (required first) ### Other services have config files/folders in the stack folder for easy deployment.
```bash * These typicaly use values from variables in .env however **can not access the variables.**
cd /opt/stacks/core/ * `ez-homelab.sh` handles variable replacement on deployment.
docker compose up -d * Each stack folder contains a `deploy-stacksname.sh` that will do the same for that stack.
```
3. **Deploy Infrastructure**
```bash
cd /opt/stacks/infrastructure/
docker compose up -d
```
4. **Deploy Dashboards** ## Multi-Server Deployment Notes
```bash
cd /opt/stacks/dashboards/
docker compose up -d
```
5. **Deploy Additional Stacks** (as needed) ### Core Server `There can be only one`
- Alternatives: `/opt/stacks/alternatives/`
- Core: `/opt/stacks/core/` (deploy first)
- Dashboards: `/opt/stacks/dashboards/`
- Home Automation: `/opt/stacks/homeassistant/`
- Infrastructure: `/opt/stacks/infrastructure/`
- Media: `/opt/stacks/media/`
- Media Management: `/opt/stacks/media-management/`
- Monitoring: `/opt/stacks/monitoring/`
- Productivity: `/opt/stacks/productivity/`
- Transcoders: `/opt/stacks/transcoders/`
- Utilities: `/opt/stacks/utilities/`
- VPN: `/opt/stacks/vpn/`
- Wikis: `/opt/stacks/wikis/`
## Toggling SSO (Authelia) On/Off `Forward ports 80 & 443 from your router`
You can easily enable or disable SSO protection for any service by modifying its Traefik labels in the docker-compose.yml file. - **DuckDNS**: Dynamic DNS and SSL certificate management
- **Authelia**: Centralized SSO authentication for all servers
- **Traefik** (core): Multi-provider configuration to route to all servers
### To Enable SSO on a Service ### Remote Server Services (Deploy on Each Server)
- **Traefik** (local): Discovers containers through labels
- **Sablier**: Manages lazy loading for local containers only
- **Dockge**: Stack management interface
Add the Authelia middleware to the service's Traefik labels: ### Architecture Overview
- **TLS Communication**: Remote servers connect via Docker TLS (port 2376)
```yaml - **Unified Access**: All services accessible through core server domain
labels: - **Service Routing**: Having Traefik on every server enables label based service discovery
- "traefik.enable=true" - **Lazy Loading**: Having Sablier on every server enables label based lazy loading configuration
- "traefik.http.routers.servicename.rule=Host(`servicename.${DOMAIN}`)"
- "traefik.http.routers.servicename.entrypoints=websecure"
- "traefik.http.routers.servicename.tls.certresolver=letsencrypt"
- "traefik.http.routers.servicename.middlewares=authelia@docker" # ← Add this line
- "traefik.http.services.servicename.loadbalancer.server.port=8080"
```
### To Disable SSO on a Service
Comment out (don't remove) the middleware line:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.servicename.rule=Host(`servicename.${DOMAIN}`)"
- "traefik.http.routers.servicename.entrypoints=websecure"
- "traefik.http.routers.servicename.tls.certresolver=letsencrypt"
# - "traefik.http.routers.servicename.middlewares=authelia@docker" # ← Commented out (not removed)
- "traefik.http.services.servicename.loadbalancer.server.port=8080"
```
After making changes, redeploy the service:
```bash
# From inside the stack directory
cd /opt/stacks/stack-name/
docker compose up -d
# Or from anywhere, using the full path
docker compose -f /opt/stacks/stack-name/docker-compose.yml up -d
```
**Stopping a Service:**
```bash
# From inside the stack directory
cd /opt/stacks/stack-name/
docker compose down
# Or from anywhere, using the full path
docker compose -f /opt/stacks/stack-name/docker-compose.yml down
```
**Use Cases for Development/Production:**
- **Security First**: All services start with SSO enabled by default for maximum security
- **Development**: Keep SSO enabled to protect services during testing
- **Production**: Disable SSO only for services needing direct app/API access (Plex, Jellyfin)
- **Gradual Exposure**: Comment out SSO only when ready to expose a service
- **Quick Toggle**: AI assistant can modify these labels automatically when you ask
## Storage Recommendations
| Data Type | Recommended Location | Reason |
|-----------|---------------------|--------|
| Configuration files | `/opt/stacks/stack-name/` | Easy access, version control |
| Small databases (< 10GB) | `/opt/stacks/stack-name/db/` | Manageable on system drive |
| Media files (movies, TV, music) | `/mnt/media/` | Large, continuous growth |
| Downloads | `/mnt/downloads/` | Temporary, high throughput |
| Backups | `/mnt/backups/` | Large, separate from system |
| Surveillance footage | `/mnt/surveillance/` | Continuous recording |
| Large databases (> 10GB) | `/mnt/databases/` | Growth over time |
| Transcoding cache | `/mnt/transcode-cache/` | High I/O, large temporary files |
| Git repositories | `/mnt/git/` | Can grow large |
| Nextcloud data | `/mnt/nextcloud/` | User files, photos |
## Configuration Templates
All configuration templates are available in `config-templates/`:
- `traefik/` - Static and dynamic Traefik configuration
- `authelia/` - Complete Authelia setup with user database
- `homepage/` - Dashboard services, widgets, and Docker integration
- `prometheus/` - Metrics scrape configurations
- `loki/` - Log aggregation settings
- `promtail/` - Log shipping configuration
- `redis/` - Redis server configuration
## Additional Resources
- **Getting Started**: See [docs/getting-started.md](getting-started.md) for detailed deployment
- **Docker Guidelines**: See [docs/docker-guidelines.md](docker-guidelines.md) for management patterns
- **Quick Reference**: See [docs/quick-reference.md](quick-reference.md) for common commands
- **Proxying External Hosts**: See [docs/proxying-external-hosts.md](proxying-external-hosts.md) for Raspberry Pi, NAS, etc.
- **AI Assistant**: Use GitHub Copilot in VS Code with `.github/copilot-instructions.md` for intelligent homelab management

View File

@@ -1,56 +0,0 @@
# EZ-Homelab Script Fixes & Improvements
## Critical Fixes (Implement First)
- [x] **Secret Validation**: Add validation after `save_env_file()` to ensure Authelia secrets exist before deployment
- [x] **Better Placeholder Error Handling**: Make `replace_env_placeholders()` fail deployment if critical variables are missing
- [x] **Debug Logging**: Add toggleable comprehensive logging to file for troubleshooting
- [x] **Simplified Placeholder Logic**: Streamline the replacement process in `deploy_core()`
- [x] **Standardized .env Placeholders**: Update .env.example and .env with consistent placeholder format
- [x] **File Permission Issues**: Fix ownership problems when copying files as root then accessing as user
- [x] **REMOTE_SERVER_HOSTNAME Error**: Remove multi-server config files from core deployments to prevent critical errors
- [x] **Docker Compose Variable Expansion**: Remove AUTHELIA_ADMIN_PASSWORD from core stack .env to prevent argon2id hash expansion warnings
## High Priority Issues
- [ ] **Authelia Password Hash Generation Reliability**
- Issue: Docker-based password hash generation can fail if Docker isn't ready or Authelia image pull fails
- Impact: Deployment fails with cryptic errors
- Fix: Add retry logic and fallback to local hash generation
- [x] **Environment Variable Persistence Issues**
- Issue: Timing issues with when .env is sourced vs when variables are needed
- Impact: Variables not available when functions expect them
- Fix: Implemented safe .env loading that doesn't expand special characters + filtered .env files per stack
## Medium Priority Issues
- [ ] **Multi-Server TLS Setup Complexity**
- Issue: Complex SSH authentication logic with multiple failure points
- Impact: TLS setup often fails, preventing remote Docker access
- Fix: Simplify to use SSH config files and better error messages
- [ ] **Directory Permission Race Conditions**
- Issue: Script creates directories with sudo then writes as regular user
- Impact: Permission conflicts during file operations
- Fix: Consistent ownership handling throughout
- [ ] **Missing Pre-deployment Validation**
- Issue: No comprehensive checks before starting deployment
- Impact: Failures occur mid-deployment after time investment
- Fix: Add validation phase checking Docker, networks, environment
## Low Priority Issues
- [ ] **Function Complexity**
- Issue: Large functions like `deploy_core()` and `prompt_for_values()` are hard to test/debug
- Impact: Bugs are harder to isolate and fix
- Fix: Break down into smaller, focused functions
- [ ] **No Rollback Capability**
- Issue: Failed deployments leave partial state
- Impact: Manual cleanup required, risk of inconsistent state
- Fix: Add cleanup functions for failed deployments
## Implementation Notes
- Start with Critical Fixes to make Option 2 deployment reliable
- Test each fix individually before moving to next
- Use debug logging to validate fixes work correctly
- Update documentation after each major change
- Consider backward compatibility with existing deployments

View File

@@ -7,8 +7,8 @@ Wildcard SSL certificate acquisition via DuckDNS DNS-01 challenge consistently f
### Why Both Domain and Wildcard are Required ### Why Both Domain and Wildcard are Required
Let's Encrypt requires validation of BOTH domains when using SAN (Subject Alternative Name) certificates: Let's Encrypt requires validation of BOTH domains when using SAN (Subject Alternative Name) certificates:
- `kelin-hass.duckdns.org` (apex domain) - `yourdomain.duckdns.org` (apex domain)
- `*.kelin-hass.duckdns.org` (wildcard) - `*.yourdomain.duckdns.org` (wildcard)
This is a Let's Encrypt policy - you cannot obtain just the wildcard certificate. Both must be validated simultaneously. This is a Let's Encrypt policy - you cannot obtain just the wildcard certificate. Both must be validated simultaneously.
@@ -23,13 +23,13 @@ ping -c 2 ns1.duckdns.org # FAIL: 100% packet loss
ping -c 2 99.79.143.35 # FAIL: 100% packet loss (direct IP) ping -c 2 99.79.143.35 # FAIL: 100% packet loss (direct IP)
# DNS queries to authoritative servers - timeout # DNS queries to authoritative servers - timeout
dig @99.79.143.35 kelin-hass.duckdns.org # FAIL: timeout dig @99.79.143.35 yourdomain.duckdns.org # FAIL: timeout
dig @35.182.183.211 kelin-hass.duckdns.org # FAIL: timeout dig @35.182.183.211 yourdomain.duckdns.org # FAIL: timeout
dig @3.97.58.28 kelin-hass.duckdns.org # FAIL: timeout dig @3.97.58.28 yourdomain.duckdns.org # FAIL: timeout
# Queries to recursive resolvers - SUCCESS # Queries to recursive resolvers - SUCCESS
dig @8.8.8.8 kelin-hass.duckdns.org # SUCCESS dig @8.8.8.8 yourdomain.duckdns.org # SUCCESS
dig @1.1.1.1 kelin-hass.duckdns.org # SUCCESS dig @1.1.1.1 yourdomain.duckdns.org # SUCCESS
# Traceroute analysis # Traceroute analysis
traceroute 99.79.143.35 traceroute 99.79.143.35
@@ -83,15 +83,15 @@ The lego library **must** also query the authoritative nameservers directly to v
``` ```
propagation: time limit exceeded: last error: authoritative nameservers: propagation: time limit exceeded: last error: authoritative nameservers:
DNS call error: read udp 172.19.0.2:53666->3.97.58.28:53: i/o timeout DNS call error: read udp 172.19.0.2:53666->3.97.58.28:53: i/o timeout
[ns=ns6.duckdns.org.:53, question='_acme-challenge.kelin-hass.duckdns.org. IN TXT'] [ns=ns6.duckdns.org.:53, question='_acme-challenge.yourdomain.duckdns.org. IN TXT']
``` ```
**Phase 2: SOA record query failure** **Phase 2: SOA record query failure**
``` ```
propagation: time limit exceeded: last error: could not find zone: propagation: time limit exceeded: last error: could not find zone:
[fqdn=_acme-challenge.kelin-hass.duckdns.org.] [fqdn=_acme-challenge.yourdomain.duckdns.org.]
unexpected response for 'kelin-hass.duckdns.org.' unexpected response for 'yourdomain.duckdns.org.'
[question='kelin-hass.duckdns.org. IN SOA', code=SERVFAIL] [question='yourdomain.duckdns.org. IN SOA', code=SERVFAIL]
``` ```
## Working Configuration (Self-Signed Certificates) ## Working Configuration (Self-Signed Certificates)

View File

@@ -15,12 +15,12 @@ echo "╚═══════════════════════
echo "╔═════════════════════════════════════════════════════════════╗ echo "╔═════════════════════════════════════════════════════════════╗
echo "║ ✅ SERVER_IP: 192.168.4.4 ║ echo "║ ✅ SERVER_IP: 192.168.4.4 ║
echo "║ ✅ SERVER_HOSTNAME: jasper ║ echo "║ ✅ SERVER_HOSTNAME: jasper ║
echo "║ ✅ DUCKDNS_SUBDOMAINS: kelinreij echo "║ ✅ DUCKDNS_SUBDOMAINS: yourdomain
echo "║ ✅ DUCKDNS_TOKEN: 41ef7faa-fc93-41d2-a32f-340fd2b75b2f echo "║ ✅ DUCKDNS_TOKEN: your-duckdns-token
echo "║ ✅ DOMAIN: kelinreij.duckdns.org echo "║ ✅ DOMAIN: yourdomain.duckdns.org ║
echo "║ ✅ DEFAULT_USER: kelin ║ echo "║ ✅ DEFAULT_USER: admin ║
echo "║ ✅ DEFAULT_PASSWORD: Tiberi0u$ echo "║ ✅ DEFAULT_PASSWORD: changeme
echo "║ ✅ DEFAULT_EMAIL: kelinshomelab@gmail.com echo "║ ✅ DEFAULT_EMAIL: admin@example.com
echo "╚═════════════════════════════════════════════════════════════╝ echo "╚═════════════════════════════════════════════════════════════╝
echo "╔═════════════════════════════════════════════════════════════╗ echo "╔═════════════════════════════════════════════════════════════╗
@@ -31,16 +31,16 @@ echo "╔═══════════════════════
echo "║ Deployment Complete! ║ echo "║ Deployment Complete! ║
echo "║ SSL Certificates may take a few minutes to be issued. ║ echo "║ SSL Certificates may take a few minutes to be issued. ║
echo "║ ║ echo "║ ║
echo "║ https://dockge.kelinreij.duckdns.org ║ echo "║ https://dockge.yourdomain.duckdns.org ║
echo "║ http://192.168.4.4:5001 ║ echo "║ http://192.168.4.4:5001 ║
echo "║ ║ echo "║ ║
echo "║ https://homepage.kelinreij.duckdns.org ║ echo "║ https://homepage.yourdomain.duckdns.org ║
echo "║ http://192.168.4.4:3003 ║ echo "║ http://192.168.4.4:3003 ║
echo "║ ║ echo "║ ║
echo "║ https://authelia.kelinreij.duckdns.org ║ echo "║ https://authelia.yourdomain.duckdns.org ║
echo "║ http://192.168.4.4:9091 ║ echo "║ http://192.168.4.4:9091 ║
echo "║ ║ echo "║ ║
echo "║ https://traefik.kelinreij.duckdns.org ║ echo "║ https://traefik.yourdomain.duckdns.org ║
echo "║ http://192.168.4.4:8080 ║ echo "║ http://192.168.4.4:8080 ║
echo "║ ║ echo "║ ║
echo "╚═════════════════════════════════════════════════════════════╝ echo "╚═════════════════════════════════════════════════════════════╝

View File

@@ -94,6 +94,30 @@ load_env_file_safely() {
fi fi
done < "$env_file" done < "$env_file"
# Second pass: expand any ${VAR} references in loaded variables
while IFS= read -r line || [ -n "$line" ]; do
# Skip comments and empty lines
[[ $line =~ ^[[:space:]]*# ]] && continue
[[ -z "$line" ]] && continue
# Parse KEY=VALUE
if [[ $line =~ ^([^=]+)=(.*)$ ]]; then
local key="${BASH_REMATCH[1]}"
key=$(echo "$key" | xargs)
# Get current value
local current_value="${!key}"
# Check if value contains ${...} and expand it
if [[ "$current_value" =~ \$\{[^}]+\} ]]; then
# Use eval to expand the variable reference safely within quotes
local expanded_value=$(eval echo "\"$current_value\"")
export "$key"="$expanded_value"
debug_log "Expanded $key variable reference"
fi
fi
done < "$env_file"
debug_log "Env file loaded successfully" debug_log "Env file loaded successfully"
} }
load_env_file() { load_env_file() {
@@ -226,134 +250,197 @@ generate_shared_ca() {
log_success "Shared CA generated" log_success "Shared CA generated"
} }
# Setup SSH key authentication to core server
setup_ssh_key_to_core() {
local key_name="id_rsa_${SERVER_HOSTNAME}_to_core"
local key_path="/home/$ACTUAL_USER/.ssh/$key_name"
log_info "Setting up SSH key authentication to core server..."
# Ensure .ssh directory exists
mkdir -p "/home/$ACTUAL_USER/.ssh"
chmod 700 "/home/$ACTUAL_USER/.ssh"
# Check if key already exists
if [ -f "$key_path" ]; then
log_info "SSH key already exists: $key_path"
# Test if key works with aggressive timeout
log_info "Testing existing SSH key..."
if timeout 3 bash -c "LC_ALL=C ssh -i '$key_path' -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no \
-o ServerAliveInterval=1 -o ServerAliveCountMax=1 -o LogLevel=ERROR \
'${CORE_SERVER_USER}@${CORE_SERVER_IP}' 'echo test' 2>&1 | grep -v 'locale\|LC_ALL\|setlocale' | grep -q 'test'"; then
log_success "Existing SSH key works, skipping key setup"
export SSH_KEY_PATH="$key_path"
return 0
else
log_warning "Existing key doesn't work or connection failed, will regenerate and install"
rm -f "$key_path" "$key_path.pub"
fi
fi
# Generate new SSH key
log_info "Generating SSH key: $key_path"
ssh-keygen -t rsa -b 4096 -f "$key_path" -N "" \
-C "${SERVER_HOSTNAME}-to-core-${CORE_SERVER_HOSTNAME}" 2>&1 | grep -v "^Generating\|^Your identification\|^Your public key"
if [ ${PIPESTATUS[0]} -ne 0 ]; then
log_error "Failed to generate SSH key"
return 1
fi
log_success "SSH key generated"
# Install key on core server using password
log_info "Installing SSH key on core server ${CORE_SERVER_IP}..."
if ! command -v sshpass &> /dev/null; then
log_info "sshpass is not installed. Installing now..."
sudo apt-get update -qq && sudo apt-get install -y sshpass >/dev/null 2>&1
fi
# Debug: Check if password is set
if [ -z "$CORE_SERVER_PASSWORD" ]; then
log_error "CORE_SERVER_PASSWORD is empty!"
log_error "Check your .env file - ensure CORE_SERVER_PASSWORD is set correctly"
return 1
fi
# Show password length for debugging (not actual password)
log_info "Password length: ${#CORE_SERVER_PASSWORD} characters"
# Test SSH connection with password first
log_info "Testing SSH connection with password..."
if ! LC_ALL=C sshpass -p "$CORE_SERVER_PASSWORD" ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
-o LogLevel=ERROR \
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" "echo 'SSH connection successful'" 2>&1 | grep -v "locale\|LC_ALL\|setlocale" | grep -q "successful"; then
log_error "SSH password authentication failed"
log_error "Please verify the password in your .env file is correct"
log_error "You can test manually: sshpass -p 'YOUR_PASSWORD' ssh ${CORE_SERVER_USER}@${CORE_SERVER_IP}"
return 1
fi
log_success "SSH password authentication works"
# Read the public key
local pub_key=$(cat "${key_path}.pub")
# Copy key to core server using direct SSH command (more reliable than ssh-copy-id)
log_info "Adding public key to authorized_keys on core server..."
LC_ALL=C sshpass -p "$CORE_SERVER_PASSWORD" ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
-o LogLevel=ERROR \
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$pub_key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" 2>&1 | grep -v "locale\|LC_ALL\|setlocale"
if [ $? -ne 0 ]; then
log_error "Failed to copy SSH key to core server"
log_error "Please verify:"
echo " 1. Core server IP is correct: ${CORE_SERVER_IP}"
echo " 2. Username is correct: ${CORE_SERVER_USER}"
echo " 3. Password is correct"
echo " 4. SSH server is running on core server"
return 1
fi
# Verify key works
log_info "Verifying SSH key authentication..."
if LC_ALL=C ssh -i "$key_path" -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o LogLevel=ERROR \
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" "echo 'SSH key authentication successful'" 2>&1 | grep -v "locale\|LC_ALL\|setlocale" | grep -q "successful"; then
log_success "SSH key authentication verified"
# Export key path for use by other functions
export SSH_KEY_PATH="$key_path"
return 0
else
log_error "SSH key verification failed"
return 1
fi
}
# Function to setup multi-server TLS for remote servers # Function to setup multi-server TLS for remote servers
setup_multi_server_tls() { setup_multi_server_tls() {
local ca_dir="/opt/stacks/core/shared-ca" local ca_dir="/opt/stacks/core/shared-ca"
# Use the SSH key path that was exported by setup_ssh_key_to_core
if [ -z "$SSH_KEY_PATH" ]; then
log_error "SSH_KEY_PATH not set. Please run setup_ssh_key_to_core first."
return 1
fi
local key_path="$SSH_KEY_PATH"
log_info "Using SSH key: $key_path"
sudo mkdir -p "$ca_dir" sudo mkdir -p "$ca_dir"
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$ca_dir" sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$ca_dir"
if [ -n "$CORE_SERVER_IP" ]; then log_info "Fetching shared CA from core server ${CORE_SERVER_IP}..."
log_info "Setting up multi-server TLS using shared CA from core server $CORE_SERVER_IP..."
else # Check if shared CA exists on core server
# Prompt for core server IP if not set log_info "Checking for shared CA on core server..."
read -p "Enter the IP address of your core server: " CORE_SERVER_IP
while [ -z "$CORE_SERVER_IP" ]; do SHARED_CA_PATH=""
log_warning "Core server IP is required for shared TLS"
read -p "Enter the IP address of your core server: " CORE_SERVER_IP # Test for shared-ca directory (preferred location)
done if LC_ALL=C ssh -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR "${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
log_info "Setting up multi-server TLS using shared CA from core server $CORE_SERVER_IP..." "test -f /opt/stacks/core/shared-ca/ca.pem && test -f /opt/stacks/core/shared-ca/ca-key.pem && echo 'EXISTS'" 2>/dev/null | grep -q "EXISTS"; then
SHARED_CA_PATH="/opt/stacks/core/shared-ca"
log_success "Found shared CA in: $SHARED_CA_PATH"
# Test for docker-tls directory (alternative location)
elif LC_ALL=C ssh -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR "${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
"test -f /opt/stacks/core/docker-tls/ca.pem && test -f /opt/stacks/core/docker-tls/ca-key.pem && echo 'EXISTS'" 2>/dev/null | grep -q "EXISTS"; then
SHARED_CA_PATH="/opt/stacks/core/docker-tls"
log_success "Found shared CA in: $SHARED_CA_PATH"
fi fi
# Prompt for SSH username if not set if [ -z "$SHARED_CA_PATH" ]; then
if [ -z "$SSH_USER" ]; then log_error "Shared CA not found on core server"
DEFAULT_SSH_USER="${DEFAULT_USER:-$USER}" log_error "Please ensure core server is fully deployed with Option 2 first"
read -p "SSH username for core server [$DEFAULT_SSH_USER]: " SSH_USER log_info "Checking what exists on core server..."
SSH_USER="${SSH_USER:-$DEFAULT_SSH_USER}" LC_ALL=C ssh -i "$key_path" -o StrictHostKeyChecking=no "${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
fi "ls -la /opt/stacks/core/shared-ca/ /opt/stacks/core/docker-tls/ 2>&1" | grep -v "locale\|LC_ALL\|setlocale"
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Shared CA not found on core server ${CORE_SERVER_IP}
# Test SSH connection - try key authentication first
log_info "Testing SSH connection to core server ($SSH_USER@$CORE_SERVER_IP)..."
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes "$SSH_USER@$CORE_SERVER_IP" "echo 'SSH connection successful'" 2>/dev/null; then
log_success "SSH connection established using key authentication"
USE_SSHPASS=false
else
# Key authentication failed, try password authentication
log_info "Key authentication failed, trying password authentication..."
read -s -p "Enter SSH password for $SSH_USER@$CORE_SERVER_IP: " SSH_PASSWORD
echo ""
if sshpass -p "$SSH_PASSWORD" ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "echo 'SSH connection successful'" 2>/dev/null; then
log_success "SSH connection established using password authentication"
USE_SSHPASS=true
else
log_error "Cannot connect to core server via SSH. Please check:"
echo " 1. SSH is running on the core server"
echo " 2. SSH keys are properly configured, or username/password are correct"
echo " 3. The core server IP is correct"
echo ""
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Cannot connect to core server $CORE_SERVER_IP via SSH
This will prevent Sablier from connecting to remote Docker daemons.
To fix this: To fix this:
1. Ensure SSH is running on the core server 1. Deploy core server first using Option 2
2. Configure SSH keys or provide correct password 2. Verify CA exists: ssh ${CORE_SERVER_USER}@${CORE_SERVER_IP} 'ls -la /opt/stacks/core/shared-ca/'
3. Verify the core server IP is correct 3. Re-run Option 3 deployment"
4. Test SSH connection: ssh $SSH_USER@$CORE_SERVER_IP return 1
Without SSH access, shared CA cannot be fetched for secure multi-server TLS."
return
fi
fi fi
# Fetch shared CA certificates from core server # Copy shared CA from core server using SCP
log_info "Fetching shared CA certificates from core server..." log_info "Copying shared CA certificates..."
SHARED_CA_EXISTS=false
# Check if shared CA exists on core server (check both old and new locations) # Copy ca.pem
if [ "$USE_SSHPASS" = true ] && [ -n "$SSH_PASSWORD" ]; then if ! LC_ALL=C scp -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR \
if sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/shared-ca/ca.pem ] && [ -f /opt/stacks/core/shared-ca/ca-key.pem ] && [ -r /opt/stacks/core/shared-ca/ca.pem ] && [ -r /opt/stacks/core/shared-ca/ca-key.pem ]" 2>/dev/null; then "${CORE_SERVER_USER}@${CORE_SERVER_IP}:${SHARED_CA_PATH}/ca.pem" \
SHARED_CA_EXISTS=true "$ca_dir/" 2>/dev/null; then
SHARED_CA_PATH="/opt/stacks/core/shared-ca" log_error "Failed to copy ca.pem from core server"
log_info "Detected CA certificate and key in shared-ca location" TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Could not copy shared CA from ${CORE_SERVER_IP}
elif sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/docker-tls/ca.pem ] && [ -f /opt/stacks/core/docker-tls/ca-key.pem ] && [ -r /opt/stacks/core/docker-tls/ca.pem ] && [ -r /opt/stacks/core/docker-tls/ca-key.pem ]" 2>/dev/null; then
SHARED_CA_EXISTS=true To fix this:
SHARED_CA_PATH="/opt/stacks/core/docker-tls" 1. Verify SSH key works: ssh -i $key_path ${CORE_SERVER_USER}@${CORE_SERVER_IP}
log_info "Detected CA certificate and key in docker-tls location" 2. Check file permissions: ssh ${CORE_SERVER_USER}@${CORE_SERVER_IP} 'ls -la ${SHARED_CA_PATH}/'
fi 3. Manually copy if needed: scp -i $key_path ${CORE_SERVER_USER}@${CORE_SERVER_IP}:${SHARED_CA_PATH}/ca* $ca_dir/"
else return 1
if ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/shared-ca/ca.pem ] && [ -f /opt/stacks/core/shared-ca/ca-key.pem ] && [ -r /opt/stacks/core/shared-ca/ca.pem ] && [ -r /opt/stacks/core/shared-ca/ca-key.pem ]" 2>/dev/null; then
SHARED_CA_EXISTS=true
SHARED_CA_PATH="/opt/stacks/core/shared-ca"
log_info "Detected CA certificate and key in shared-ca location"
elif ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/docker-tls/ca.pem ] && [ -f /opt/stacks/core/docker-tls/ca-key.pem ] && [ -r /opt/stacks/core/docker-tls/ca.pem ] && [ -r /opt/stacks/core/docker-tls/ca-key.pem ]" 2>/dev/null; then
SHARED_CA_EXISTS=true
SHARED_CA_PATH="/opt/stacks/core/docker-tls"
log_info "Detected CA certificate and key in docker-tls location"
fi
fi fi
if [ "$SHARED_CA_EXISTS" = true ]; then # Copy ca-key.pem
# Copy existing shared CA from core server if ! LC_ALL=C scp -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR \
set +e "${CORE_SERVER_USER}@${CORE_SERVER_IP}:${SHARED_CA_PATH}/ca-key.pem" \
scp_output=$(scp -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca.pem" "$SSH_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca-key.pem" "$ca_dir/" 2>&1) "$ca_dir/" 2>/dev/null; then
scp_exit_code=$? log_error "Failed to copy ca-key.pem from core server"
set -e return 1
if [ $scp_exit_code -eq 0 ]; then fi
log_success "Shared CA certificate and key fetched from core server"
log_success "Shared CA copied successfully"
# Now setup Docker TLS using the shared CA
setup_docker_tls setup_docker_tls
else
log_error "Failed to fetch shared CA certificate and key from core server"
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Could not copy shared CA from core server $CORE_SERVER_IP
SCP Error: $scp_output
To fix this:
1. Ensure SSH key authentication works: ssh $ACTUAL_USER@$CORE_SERVER_IP
2. Verify core server has: $SHARED_CA_PATH/ca.pem and ca-key.pem
3. Check file permissions on core server: ls -la $SHARED_CA_PATH/
4. Manually copy CA: scp $ACTUAL_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca.pem $ca_dir/
scp $ACTUAL_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca-key.pem $ca_dir/
5. Regenerate server certificates: run setup_docker_tls after copying
6. Restart Docker: sudo systemctl restart docker
Then restart Sablier on the core server to reconnect."
return
fi
else
log_warning "Shared CA certificates not found on core server."
log_info "Please ensure the core server has been set up first and has generated the shared CA certificates."
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Shared CA certificates not found on core server $CORE_SERVER_IP
This will prevent Sablier from connecting to remote Docker daemons.
To fix this:
1. Ensure the core server is set up and has generated shared CA certificates
2. Verify SSH access: ssh $ACTUAL_USER@$CORE_SERVER_IP
3. Check core server locations: /opt/stacks/core/shared-ca/ or /opt/stacks/core/docker-tls/
4. Manually copy CA certificates if needed
5. Re-run the infrastructure deployment
Without shared CA, remote Docker access will not work securely."
return
fi
} }
# Get script directory and repo directory # Get script directory and repo directory
@@ -373,7 +460,6 @@ fi
# Default values # Default values
DOMAIN="" DOMAIN=""
SERVER_IP="" SERVER_IP=""
CORE_SERVER_IP=""
ADMIN_USER="" ADMIN_USER=""
ADMIN_EMAIL="" ADMIN_EMAIL=""
AUTHELIA_ADMIN_PASSWORD="" AUTHELIA_ADMIN_PASSWORD=""
@@ -381,7 +467,13 @@ DEPLOY_CORE=false
DEPLOY_INFRASTRUCTURE=false DEPLOY_INFRASTRUCTURE=false
DEPLOY_DASHBOARDS=false DEPLOY_DASHBOARDS=false
SETUP_STACKS=false SETUP_STACKS=false
DEPLOY_REMOTE_SERVER=false
TLS_ISSUES_SUMMARY="" TLS_ISSUES_SUMMARY=""
CORE_SERVER_IP=""
CORE_SERVER_HOSTNAME=""
CORE_SERVER_USER=""
CORE_SERVER_PASSWORD=""
SSH_KEY_PATH=""
# Required variables for configuration # Required variables for configuration
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_SUBDOMAINS" "DUCKDNS_TOKEN" "DOMAIN" "DEFAULT_USER" "DEFAULT_PASSWORD" "DEFAULT_EMAIL") REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_SUBDOMAINS" "DUCKDNS_TOKEN" "DOMAIN" "DEFAULT_USER" "DEFAULT_PASSWORD" "DEFAULT_EMAIL")
@@ -874,6 +966,26 @@ deploy_core() {
sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/docker-compose.yml sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/docker-compose.yml
sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/.env sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/.env
# Fix multi-line secrets in .env file (merge split lines)
debug_log "Fixing multi-line secrets in .env file"
python3 << 'PYFIX'
import sys
with open('/opt/stacks/core/.env', 'r') as f:
lines = f.readlines()
new_lines = []
i = 0
while i < len(lines):
if any(k in lines[i] for k in ['AUTHELIA_JWT_SECRET=', 'AUTHELIA_SESSION_SECRET=', 'AUTHELIA_STORAGE_ENCRYPTION_KEY=']):
if i + 1 < len(lines) and '=' not in lines[i+1] and lines[i+1].strip() and not lines[i+1].strip().startswith('#'):
new_lines.append(lines[i].rstrip('\n') + lines[i+1].lstrip())
i += 2
continue
new_lines.append(lines[i])
i += 1
with open('/opt/stacks/core/.env', 'w') as f:
f.writelines(new_lines)
PYFIX
# Escape $ characters in password hashes to prevent Docker Compose variable substitution # Escape $ characters in password hashes to prevent Docker Compose variable substitution
sed -i '/^AUTHELIA_ADMIN_PASSWORD_HASH=/ s/\$/\\$/g' /opt/stacks/core/.env sed -i '/^AUTHELIA_ADMIN_PASSWORD_HASH=/ s/\$/\\$/g' /opt/stacks/core/.env
@@ -960,13 +1072,9 @@ deploy_core() {
# Remove invalid session.cookies section from Authelia config (not supported in v4.37.5) # Remove invalid session.cookies section from Authelia config (not supported in v4.37.5)
debug_log "Removing invalid session.cookies section from Authelia config" debug_log "Removing invalid session.cookies section from Authelia config"
sed -i '/^ cookies:/,/^$/d' /opt/stacks/core/authelia/configuration.yml sed -i '/^ cookies:/,/^$/d' /opt/stacks/core/authelia/config/configuration.yml
# Move config files to the correct location for Docker mount # Ensure proper ownership of Authelia files
debug_log "Moving Authelia config files to config directory"
mkdir -p /opt/stacks/core/authelia/config
mv /opt/stacks/core/authelia/configuration.yml /opt/stacks/core/authelia/config/
mv /opt/stacks/core/authelia/users_database.yml /opt/stacks/core/authelia/config/
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/authelia sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/authelia
# Generate shared CA for multi-server TLS # Generate shared CA for multi-server TLS
@@ -1392,7 +1500,7 @@ set_required_vars_for_deployment() {
debug_log "Set REQUIRED_VARS for core deployment" debug_log "Set REQUIRED_VARS for core deployment"
;; ;;
"remote") "remote")
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_DOMAIN" "DEFAULT_USER" "REMOTE_SERVER_IP" "REMOTE_SERVER_HOSTNAME" "REMOTE_SERVER_USER") REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DOMAIN" "DEFAULT_USER" "CORE_SERVER_IP" "CORE_SERVER_HOSTNAME" "CORE_SERVER_USER" "CORE_SERVER_PASSWORD")
debug_log "Set REQUIRED_VARS for remote deployment" debug_log "Set REQUIRED_VARS for remote deployment"
;; ;;
*) *)
@@ -1404,9 +1512,23 @@ set_required_vars_for_deployment() {
# Deploy remote server # Deploy remote server
deploy_remote_server() { deploy_remote_server() {
# Enable verbose mode for remote deployment
VERBOSE=true
log_info "Deploying Remote Server Configuration" log_info "Deploying Remote Server Configuration"
echo "" echo ""
# Set ACTUAL_USER if not already set (needed for SSH key paths)
if [ -z "$ACTUAL_USER" ]; then
if [ "$EUID" -eq 0 ]; then
ACTUAL_USER=${SUDO_USER:-$USER}
else
ACTUAL_USER=$USER
fi
export ACTUAL_USER
debug_log "Set ACTUAL_USER=$ACTUAL_USER"
fi
# Check Docker is installed # Check Docker is installed
if ! check_docker_installed; then if ! check_docker_installed; then
log_error "Docker must be installed before deploying remote server" log_error "Docker must be installed before deploying remote server"
@@ -1414,34 +1536,91 @@ deploy_remote_server() {
fi fi
# Ensure we have core server information # Ensure we have core server information
if [ -z "$REMOTE_SERVER_IP" ] || [ -z "$REMOTE_SERVER_HOSTNAME" ]; then if [ -z "$CORE_SERVER_IP" ] || [ -z "$CORE_SERVER_HOSTNAME" ]; then
log_error "Remote server IP and hostname are required" log_error "Core server IP and hostname are required"
return 1 return 1
fi fi
log_info "Configuring Docker TLS for remote API access..." # Step 1: Setup SSH key authentication to core server
setup_docker_tls log_info "Step 1: Setting up SSH key authentication to core server..."
log_info "Using ACTUAL_USER=$ACTUAL_USER for SSH key path"
log_info "Target: ${CORE_SERVER_USER}@${CORE_SERVER_IP}"
if ! setup_ssh_key_to_core; then
log_error "Failed to setup SSH key authentication"
return 1
fi
log_success "SSH key authentication setup complete"
echo ""
log_info "Fetching shared CA from core server..." # Step 2: Fetch shared CA and setup Docker TLS
setup_multi_server_tls "$REMOTE_SERVER_IP" "$REMOTE_SERVER_USER" log_info "Step 2: Fetching shared CA from core server..."
log_info "Using SSH key: $SSH_KEY_PATH"
if ! setup_multi_server_tls; then
log_error "Failed to setup multi-server TLS"
return 1
fi
echo ""
log_info "Deploying Sablier stack for local lazy loading..." # Step 3: Create required Docker networks
log_info "Step 3: Creating required Docker networks..."
docker network create traefik-network 2>/dev/null && log_success "Created traefik-network" || log_info "traefik-network already exists"
docker network create homelab-network 2>/dev/null && log_success "Created homelab-network" || log_info "homelab-network already exists"
echo ""
# Step 4: Install envsubst if not present
if ! command -v envsubst &> /dev/null; then
log_info "Installing envsubst (gettext-base)..."
sudo apt-get update -qq && sudo apt-get install -y gettext-base >/dev/null 2>&1
log_success "envsubst installed"
fi
# Step 5: Copy all stacks to remote server
log_info "Step 5: Copying all stacks to remote server..."
copy_all_stacks_for_remote
echo ""
# Step 6: Deploy Dockge
log_info "Step 6: Deploying Dockge..."
deploy_dockge
echo ""
# Step 7: Deploy Traefik (local instance for container discovery)
log_info "Step 7: Deploying local Traefik..."
deploy_traefik_stack
echo ""
# Step 8: Deploy Sablier stack for local lazy loading
log_info "Step 8: Deploying Sablier stack..."
deploy_sablier_stack deploy_sablier_stack
echo ""
log_info "Registering this remote server with core Traefik..." # Step 9: Deploy Infrastructure stack
log_info "Step 9: Deploying Infrastructure stack..."
deploy_infrastructure
echo ""
# Step 10: Register this remote server with core Traefik
log_info "Step 10: Registering with core Traefik..."
register_remote_server_with_core register_remote_server_with_core
echo ""
log_success "Remote server deployment complete!" log_success "Remote server deployment complete!"
echo "" echo ""
echo "This server is now configured to:" echo "This server is now configured to:"
echo " - Accept Docker API connections via TLS (port 2376)" echo " - Accept Docker API connections via TLS (port 2376)"
echo " - Run local Traefik for container discovery"
echo " - Run Sablier for local container lazy loading" echo " - Run Sablier for local container lazy loading"
echo " - Run infrastructure services"
echo " - Have its containers discovered by core Traefik" echo " - Have its containers discovered by core Traefik"
echo "" echo ""
echo "Services deployed on this server will automatically:" echo "Services deployed on this server will automatically:"
echo " - Be discovered by Traefik on the core server" echo " - Be discovered by Traefik on the core server"
echo " - Get SSL certificates via core Traefik" echo " - Get SSL certificates via core Traefik"
echo " - Be accessible at: https://servicename.${DUCKDNS_DOMAIN}" echo " - Be accessible at: https://servicename.${DOMAIN}"
echo ""
echo "Additional stacks available in /opt/stacks/ (not started):"
echo " - dashboards, media, media-management, monitoring, productivity"
echo " - transcoders, utilities, vpn, wikis, homeassistant, alternatives"
echo "" echo ""
} }
@@ -1449,15 +1628,19 @@ deploy_remote_server() {
register_remote_server_with_core() { register_remote_server_with_core() {
debug_log "Registering remote server with core Traefik via SSH" debug_log "Registering remote server with core Traefik via SSH"
if [ -z "$REMOTE_SERVER_IP" ] || [ -z "$REMOTE_SERVER_USER" ]; then local key_name="id_rsa_${SERVER_HOSTNAME}_to_core"
log_error "REMOTE_SERVER_IP and REMOTE_SERVER_USER are required" local key_path="/home/$ACTUAL_USER/.ssh/$key_name"
if [ -z "$CORE_SERVER_IP" ] || [ -z "$CORE_SERVER_USER" ]; then
log_error "CORE_SERVER_IP and CORE_SERVER_USER are required"
return 1 return 1
fi fi
log_info "Connecting to core server to register this remote server..." log_info "Connecting to core server to register this remote server..."
# SSH to core server and run registration function # SSH to core server and run registration function
ssh -o ConnectTimeout=10 "${REMOTE_SERVER_USER}@${REMOTE_SERVER_IP}" bash <<EOF LC_ALL=C ssh -i "$key_path" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o LogLevel=ERROR \
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" bash <<EOF 2>&1 | grep -v "locale\|LC_ALL\|setlocale"
# Source common.sh to get registration function # Source common.sh to get registration function
source ~/EZ-Homelab/scripts/common.sh source ~/EZ-Homelab/scripts/common.sh
@@ -1483,28 +1666,137 @@ deploy_sablier_stack() {
local sablier_dir="/opt/stacks/sablier" local sablier_dir="/opt/stacks/sablier"
# Create sablier stack directory # Create sablier stack directory with sudo
if [ ! -d "$sablier_dir" ]; then if [ ! -d "$sablier_dir" ]; then
mkdir -p "$sablier_dir" sudo mkdir -p "$sablier_dir" || { log_error "Failed to create $sablier_dir"; return 1; }
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir" sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir"
log_success "Created $sablier_dir"
fi
# Check if source files exist
if [ ! -f "$REPO_DIR/docker-compose/sablier/docker-compose.yml" ]; then
log_error "Sablier docker-compose.yml not found in repo at $REPO_DIR/docker-compose/sablier/"
return 1
fi fi
# Copy stack files # Copy stack files
cp "$REPO_DIR/docker-compose/sablier/docker-compose.yml" "$sablier_dir/" log_info "Copying Sablier stack files from $REPO_DIR/docker-compose/sablier/..."
cp "$REPO_DIR/.env" "$sablier_dir/" cp "$REPO_DIR/docker-compose/sablier/docker-compose.yml" "$sablier_dir/" || { log_error "Failed to copy docker-compose.yml"; return 1; }
cp "$REPO_DIR/.env" "$sablier_dir/" || { log_error "Failed to copy .env"; return 1; }
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/docker-compose.yml" sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/docker-compose.yml"
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/.env" sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/.env"
log_success "Stack files copied"
# Remove Authelia and other unnecessary variables from sablier .env
sed -i '/^AUTHELIA_/d' "$sablier_dir/.env"
sed -i '/^DEFAULT_PASSWORD=/d' "$sablier_dir/.env"
sed -i '/^CORE_SERVER_PASSWORD=/d' "$sablier_dir/.env"
# Localize the docker-compose file # Localize the docker-compose file
localize_compose_labels "$sablier_dir/docker-compose.yml" localize_compose_labels "$sablier_dir/docker-compose.yml"
# Deploy # Deploy
log_info "Starting Sablier container..."
cd "$sablier_dir" cd "$sablier_dir"
run_cmd docker compose up -d if ! docker compose up -d; then
log_error "Failed to start Sablier stack"
return 1
fi
log_success "Sablier stack deployed at $sablier_dir" log_success "Sablier stack deployed at $sablier_dir"
} }
# Copy all stacks for remote server (except core)
copy_all_stacks_for_remote() {
debug_log "Copying all stacks for remote server"
# Create base stacks directory
sudo mkdir -p /opt/stacks
sudo mkdir -p /opt/dockge
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge
# List of stacks to copy (all except core and dockge - dockge is handled separately)
local stacks=(
"alternatives"
"dashboards"
"homeassistant"
"infrastructure"
"media"
"media-management"
"monitoring"
"productivity"
"sablier"
"traefik"
"transcoders"
"utilities"
"vpn"
"wikis"
)
local copied_count=0
for stack in "${stacks[@]}"; do
local src_dir="$REPO_DIR/docker-compose/$stack"
local dest_dir="/opt/stacks/$stack"
# Skip if source doesn't exist
if [ ! -d "$src_dir" ]; then
debug_log "Skipping $stack - source not found"
continue
fi
# Create destination directory
mkdir -p "$dest_dir"
# Copy docker-compose.yml and any config directories
if [ -f "$src_dir/docker-compose.yml" ]; then
cp "$src_dir/docker-compose.yml" "$dest_dir/"
cp "$REPO_DIR/.env" "$dest_dir/"
# Copy any subdirectories (config, etc.)
for item in "$src_dir"/*; do
if [ -d "$item" ]; then
cp -r "$item" "$dest_dir/"
fi
done
# Clean up sensitive data from .env
sed -i '/^AUTHELIA_/d' "$dest_dir/.env"
sed -i '/^DEFAULT_PASSWORD=/d' "$dest_dir/.env"
sed -i '/^CORE_SERVER_PASSWORD=/d' "$dest_dir/.env"
# Localize compose file
localize_compose_labels "$dest_dir/docker-compose.yml" || true
copied_count=$((copied_count + 1))
debug_log "Copied $stack to $dest_dir"
fi
done
log_success "Copied $copied_count stacks to /opt/stacks/"
}
# Deploy Traefik stack (standalone for remote servers)
deploy_traefik_stack() {
debug_log "Deploying Traefik stack"
local traefik_dir="/opt/stacks/traefik"
# Create required directories
mkdir -p "$traefik_dir/config"
mkdir -p "$traefik_dir/dynamic"
# Deploy
log_info "Starting Traefik container..."
cd "$traefik_dir"
if ! docker compose up -d; then
log_error "Failed to start Traefik stack"
return 1
fi
log_success "Traefik stack deployed at $traefik_dir"
}
# Show help function # Show help function
show_help() { show_help() {
echo "EZ-Homelab Setup & Deployment Script" echo "EZ-Homelab Setup & Deployment Script"
@@ -1792,14 +2084,7 @@ main() {
echo "⚠️ IMPORTANT: Deploying an additional server requires an existing core server to be already deployed." echo "⚠️ IMPORTANT: Deploying an additional server requires an existing core server to be already deployed."
echo "The core server provides essential services like Traefik, Authelia, and shared TLS certificates." echo "The core server provides essential services like Traefik, Authelia, and shared TLS certificates."
echo "" echo ""
read -p "Do you have an existing core server deployed? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Returning to main menu. Please deploy a core server first using Option 2."
echo ""
sleep 2
continue
fi
DEPLOY_CORE=false DEPLOY_CORE=false
DEPLOY_INFRASTRUCTURE=false DEPLOY_INFRASTRUCTURE=false
DEPLOY_DASHBOARDS=false DEPLOY_DASHBOARDS=false
@@ -1835,9 +2120,6 @@ main() {
echo "" echo ""
# Prepare deployment environment
prepare_deployment
# Handle remote server deployment separately # Handle remote server deployment separately
if [ "$DEPLOY_REMOTE_SERVER" = true ]; then if [ "$DEPLOY_REMOTE_SERVER" = true ]; then
# Prompt for configuration values # Prompt for configuration values
@@ -1846,6 +2128,9 @@ main() {
# Save configuration # Save configuration
save_env_file save_env_file
# Reload .env file to get all variables including expanded ones
load_env_file
# Deploy remote server # Deploy remote server
deploy_remote_server deploy_remote_server