From 21ee910267d188701d79f0268d5f43e62ad69b7e Mon Sep 17 00:00:00 2001 From: kelinfoxy Date: Sat, 17 Jan 2026 20:22:10 -0500 Subject: [PATCH] Merge remote updates, accepting remote versions for conflicted files --- .github/copilot-instructions.md | 364 +------ config-templates/homepage/services.yaml | 270 +++--- docker-compose/core.yml | 61 +- docker-compose/monitoring.yml | 40 +- docs/service-docs/qbittorrent.md | 16 +- docs/services-overview.md | 245 ++++- scripts/deploy-homelab.sh | 734 +++++---------- scripts/setup-homelab.sh | 1149 +++++------------------ services to proxy.yaml | 33 + 9 files changed, 940 insertions(+), 1972 deletions(-) create mode 100644 services to proxy.yaml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4324b34..497eefe 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,355 +1,71 @@ # AI Homelab Management Assistant -You are an AI assistant for the **AI-Homelab** project - a production-ready Docker homelab infrastructure managed through GitHub Copilot in VS Code. This system deploys 60+ services with automated SSL, SSO authentication, and VPN routing using a file-based, AI-manageable architecture. +You are an AI assistant for managing Docker-based homelab infrastructure using Dockge, Traefik, Authelia, and Gluetun. -## Project Architecture +## Architecture Overview +- **Stacks**: All services in `/opt/stacks/stack-name/docker-compose.yml` managed via Dockge +- **Reverse Proxy**: Traefik routes traffic with automatic SSL via Let's Encrypt +- **SSO**: Authelia protects admin interfaces (bypass for Plex/Jellyfin apps) +- **VPN**: Gluetun (Surfshark WireGuard) for secure downloads +- **Networks**: `traefik-network`, `homelab-network`, `media-network` (external) +- **Storage**: Bind mounts in `/opt/stacks/` for configs; `/mnt/` for large data (>50GB) -### Core Infrastructure (Deploy First) -The **core stack** (`/opt/stacks/core/`) contains essential services that must run before others: -- **DuckDNS**: Dynamic DNS with Let's Encrypt DNS challenge for wildcard SSL (`*.yourdomain.duckdns.org`) -- **Traefik**: Reverse proxy with automatic HTTPS termination (labels-based routing, file provider for external hosts) -- **Authelia**: SSO authentication (auto-generated secrets, file-based user database) -- **Gluetun**: VPN client (Surfshark WireGuard/OpenVPN) for download services +## Core Workflow +1. **Deploy Core First**: DuckDNS + Traefik + Authelia + Gluetun via `./scripts/deploy-homelab.sh` +2. **Add Services**: Create compose files with Traefik labels, deploy via Dockge +3. **Manage via Files**: No web UIs - all config in YAML files -### Deployment Model -- **Two-script setup**: `setup-homelab.sh` (system prep, Docker install, secrets generation) → `deploy-homelab.sh` (automated deployment) -- **Dockge-based management**: All stacks in `/opt/stacks/`, managed via web UI at `dockge.${DOMAIN}` -- **Automated workflows**: Scripts create directories, configure networks, deploy stacks, wait for health checks -- **Repository location**: `/home/kelin/AI-Homelab/` (templates in `docker-compose/`, docs in `docs/`) - -### File Structure Standards -``` -/opt/stacks/ -├── core/ # DuckDNS, Traefik, Authelia, Gluetun (deploy FIRST) -├── infrastructure/ # Dockge, Pi-hole, monitoring tools -├── dashboards/ # Homepage (AI-configured), Homarr -├── media/ # Plex, Jellyfin, Calibre-web, qBittorrent -├── media-management/ # *arr services (Sonarr, Radarr, etc.) -├── homeassistant/ # Home Assistant, Node-RED, Zigbee2MQTT -├── productivity/ # Nextcloud, Gitea, Bookstack -├── monitoring/ # Grafana, Prometheus, Uptime Kuma -└── utilities/ # Duplicati, FreshRSS, Wallabag -``` - -### Network Architecture -- **traefik-network**: Primary network for all services behind Traefik -- **Gluetun network mode**: Download clients use `network_mode: "service:gluetun"` for VPN routing -- **Port mapping**: Only core services expose ports (80/443 for Traefik); others route via Traefik labels - -## Critical Operational Principles - -### 1. Security-First SSO Strategy -- **Default stance**: ALL services start with Authelia middleware enabled -- **Bypass exceptions**: Only Plex and Jellyfin (for device/app compatibility) -- **Disabling SSO**: Comment (don't delete) the middleware line: `# - "traefik.http.routers.SERVICE.middlewares=authelia@docker"` -- **Rationale**: Security by default; users explicitly opt-out for specific services - -### 2. Traefik Label Patterns -Standard routing configuration for new services: -```yaml -labels: - - "traefik.enable=true" - - "traefik.http.routers.SERVICE.rule=Host(`SERVICE.${DOMAIN}`)" - - "traefik.http.routers.SERVICE.entrypoints=websecure" - - "traefik.http.routers.SERVICE.tls.certresolver=letsencrypt" # Uses wildcard cert - - "traefik.http.routers.SERVICE.middlewares=authelia@docker" # SSO protection - - "traefik.http.services.SERVICE.loadbalancer.server.port=PORT" # If not default -``` - -### 3. Storage Strategy -- **Configs**: Bind mount `./service/config:/config` relative to stack directory -- **Small data**: Named volumes (databases, app data <50GB) -- **Large data**: External mounts `/mnt/media`, `/mnt/downloads` (user must configure) -- **Secrets**: `.env` files in stack directories (auto-copied from `~/AI-Homelab/.env`) - -### 4. LinuxServer.io Preference -- Use `lscr.io/linuxserver/*` images when available (PUID/PGID support for permissions) -- Standard environment: `PUID=1000`, `PGID=1000`, `TZ=${TZ}` - -### 5. External Host Proxying -Proxy non-Docker services (Raspberry Pi, NAS) via Traefik file provider: -- Create routes in `/opt/stacks/core/traefik/dynamic/external.yml` -- Example pattern documented in `docs/proxying-external-hosts.md` -- AI can manage these YAML files directly - -## Developer Workflows - -### First-Time Deployment -```bash -cd ~/AI-Homelab -sudo ./scripts/setup-homelab.sh # System prep, Docker install, Authelia secrets -# Reboot if NVIDIA drivers installed -sudo ./scripts/deploy-homelab.sh # Deploy core+infrastructure stacks, open Dockge -``` - -### Managing Services via Scripts -- **setup-homelab.sh**: Idempotent system preparation (skips completed steps, runs on bare Debian) - - Steps: Update system → Install Docker → Configure firewall → Generate Authelia secrets → Create directories/networks → NVIDIA driver detection - - Auto-generates: JWT secret (64 hex), session secret (64 hex), encryption key (64 hex), admin password hash - - Creates `homelab-network` and `traefik-network` Docker networks -- **deploy-homelab.sh**: Automated stack deployment (requires `.env` configured first) - - Steps: Validate prerequisites → Create directories → Deploy core → Deploy infrastructure → Deploy dashboards → Prepare additional stacks → Wait for Dockge - - Copies `.env` to `/opt/stacks/core/.env` and `/opt/stacks/infrastructure/.env` - - Waits for service health checks before proceeding - -### Testing Changes -```bash -# Test in isolation before modifying stacks -docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi # GPU test -docker compose -f docker-compose.yml config # Validate YAML syntax -docker compose -f docker-compose.yml up -d SERVICE # Deploy single service -docker compose logs -f SERVICE # Monitor logs -``` - -### Common Operations -```bash -cd /opt/stacks/STACK_NAME -docker compose up -d # Start stack -docker compose restart SERVICE # Restart service -docker compose logs -f SERVICE # Tail logs -docker compose pull && docker compose up -d # Update images -``` - -## Creating a New Docker Service - -## Creating a New Docker Service - -### Service Definition Template +## Service Template ```yaml services: service-name: - image: linuxserver/service:latest # Pin versions in production; prefer LinuxServer.io + image: lscr.io/linuxserver/service:latest # Pin versions, prefer LinuxServer container_name: service-name restart: unless-stopped networks: - - traefik-network + - homelab-network volumes: - - ./service-name/config:/config # Config in stack directory - - service-data:/data # Named volume for persistent data - # Large data on separate drives: - # - /mnt/media:/media - # - /mnt/downloads:/downloads + - /opt/stacks/stack-name/config:/config # Configs + - /mnt/large-data:/data # Large data on separate drives environment: - - PUID=${PUID:-1000} - - PGID=${PGID:-1000} + - PUID=1000 + - PGID=1000 - TZ=${TZ} labels: - "traefik.enable=true" - "traefik.http.routers.service-name.rule=Host(`service.${DOMAIN}`)" - "traefik.http.routers.service-name.entrypoints=websecure" - "traefik.http.routers.service-name.tls.certresolver=letsencrypt" - - "traefik.http.routers.service-name.middlewares=authelia@docker" # SSO enabled by default - - "traefik.http.services.service-name.loadbalancer.server.port=8080" # If non-standard port - - "homelab.category=category-name" - - "homelab.description=Service description" + - "traefik.http.routers.service-name.middlewares=authelia@docker" # SSO enabled + - "traefik.http.services.service-name.loadbalancer.server.port=8080" volumes: service-data: driver: local networks: - traefik-network: + homelab-network: external: true ``` -### VPN-Routed Service (Downloads) -Route through Gluetun for VPN protection: -```yaml -services: - # Gluetun already running in core stack - - qbittorrent: - image: lscr.io/linuxserver/qbittorrent:latest - container_name: qbittorrent - network_mode: "service:gluetun" # Routes through VPN - depends_on: - - gluetun - volumes: - - ./qbittorrent/config:/config - - /mnt/downloads:/downloads - environment: - - PUID=1000 - - PGID=1000 - - TZ=${TZ} - # No ports needed - mapped in Gluetun service - # No Traefik labels - access via Gluetun's network -``` +## Key Patterns +- **SSO Bypass**: Comment out `authelia@docker` middleware for Plex/Jellyfin +- **VPN Routing**: Use `network_mode: "service:gluetun"` for download clients +- **Environment**: Secrets in `.env` files, referenced as `${VAR}` +- **Dependencies**: Core stack must deploy first +- **Updates**: `docker compose pull && docker compose up -d` -Add ports to Gluetun in core stack: -```yaml -gluetun: - ports: - - 8080:8080 # qBittorrent WebUI -``` +## Critical Files +- `docker-compose/core.yml`: Essential infrastructure stack +- `config-templates/`: Authelia/Traefik configs +- `scripts/deploy-homelab.sh`: Automated deployment +- `.env`: All environment variables -### Authelia Bypass Example (Jellyfin) -```yaml -labels: - - "traefik.enable=true" - - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.${DOMAIN}`)" - - "traefik.http.routers.jellyfin.entrypoints=websecure" - - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" - # NO authelia middleware - direct access for apps/devices -``` +## Safety First +- Always consider stack-wide impacts +- Test changes with `docker run` first +- Backup configs before modifications +- Use LinuxServer images for proper permissions +- Document non-obvious configurations -## Modifying Existing Services - -## Modifying Existing Services - -### Safe Modification Process -1. **Read entire compose file** - understand dependencies (networks, volumes, depends_on) -2. **Check for impacts** - search for service references across other compose files -3. **Validate YAML** - `docker compose config` before deploying -4. **Test in place** - restart single service: `docker compose up -d SERVICE` -5. **Monitor logs** - `docker compose logs -f SERVICE` to verify functionality - -### Common Modifications -- **Toggle SSO**: Comment/uncomment `middlewares=authelia@docker` label -- **Change port**: Update `loadbalancer.server.port` label -- **Add VPN routing**: Change to `network_mode: "service:gluetun"`, map ports in Gluetun -- **Update subdomain**: Modify `Host()` rule in Traefik labels -- **Environment changes**: Update in `.env`, redeploy: `docker compose up -d` - -## Project-Specific Conventions - -### Why Traefik vs Nginx Proxy Manager -- **File-based configuration**: AI can modify labels/YAML directly (no web UI clicks) -- **Docker label discovery**: Services auto-register routes when deployed -- **Let's Encrypt automation**: Wildcard cert via DuckDNS DNS challenge (single cert for all services) -- **Dynamic reloading**: Changes apply without container restarts - -### Authelia Password Generation -Secrets auto-generated by `setup-homelab.sh`: -- JWT secret: `openssl rand -hex 64` -- Session secret: `openssl rand -hex 64` -- Encryption key: `openssl rand -hex 64` -- Admin password: Hashed with `docker run authelia/authelia:latest authelia crypto hash generate argon2` -- Stored in `.env` file, never committed to git - -### DuckDNS Wildcard Certificate -- **Single certificate**: `*.yourdomain.duckdns.org` covers all subdomains -- **DNS challenge**: Traefik uses DuckDNS token for Let's Encrypt validation -- **Certificate storage**: `/opt/stacks/core/traefik/acme.json` (600 permissions) -- **Renewal**: Traefik handles automatically (90-day Let's Encrypt certs) -- **Usage**: Services use `tls.certresolver=letsencrypt` label (no per-service cert requests) - -### Homepage Dashboard AI Configuration -Homepage (`/opt/stacks/dashboards/`) uses dynamic variable replacement: -- Services configured in `homepage/config/services.yaml` -- URLs use `${DOMAIN}` variable replaced at runtime -- AI can add/remove service entries by editing YAML -- Bookmarks, widgets configured similarly in separate YAML files - -## Key Documentation References - -- **[Getting Started](../docs/getting-started.md)**: Step-by-step deployment guide -- **[Docker Guidelines](../docs/docker-guidelines.md)**: Comprehensive service management patterns (1000+ lines) -- **[Services Reference](../docs/services-reference.md)**: All 60+ pre-configured services -- **[Proxying External Hosts](../docs/proxying-external-hosts.md)**: Traefik file provider patterns for non-Docker services -- **[Quick Reference](../docs/quick-reference.md)**: Command cheat sheet and troubleshooting - -## Pre-Deployment Safety Checks - -Before deploying any service changes: -- [ ] YAML syntax valid (`docker compose config`) -- [ ] No port conflicts (check `docker ps --format "table {{.Names}}\t{{.Ports}}"`) -- [ ] Networks exist (`docker network ls | grep -E 'traefik-network|homelab-network'`) -- [ ] Volume paths correct (`/opt/stacks/` for configs, `/mnt/` for large data) -- [ ] `.env` variables populated (source stack `.env` and check `echo $DOMAIN`) -- [ ] Traefik labels complete (enable, rule, entrypoint, tls, middleware) -- [ ] SSO appropriate (default enabled, bypass only for Plex/Jellyfin) -- [ ] VPN routing configured if download service (network_mode + Gluetun ports) -- [ ] LinuxServer.io image available (check hub.docker.com/u/linuxserver) - -## Troubleshooting Common Issues - -### Service Won't Start -```bash -docker compose logs SERVICE # Check error messages -docker compose config # Validate YAML syntax -docker ps -a | grep SERVICE # Check exit code -``` -Common causes: Port conflict, missing `.env` variable, network not found, volume permission denied - -### Traefik Not Routing -```bash -docker logs traefik | grep SERVICE # Check if route registered -curl -k https://traefik.${DOMAIN}/api/http/routers # Inspect routes (if API enabled) -``` -Verify: Service on `traefik-network`, labels correctly formatted, `traefik.enable=true`, Traefik restarted after label changes - -### Authelia SSO Not Prompting -Check middleware: `docker compose config | grep -A5 SERVICE | grep authelia` -Verify: Authelia container running, middleware label present, no bypass rule in `authelia/configuration.yml` - -### VPN Not Working (Gluetun) -```bash -docker exec gluetun sh -c "curl -s ifconfig.me" # Check VPN IP -docker logs gluetun | grep -i wireguard # Verify connection -``` -Verify: `SURFSHARK_PRIVATE_KEY` set in `.env`, service using `network_mode: "service:gluetun"`, ports mapped in Gluetun - -### Wildcard Certificate Issues -```bash -docker logs traefik | grep -i certificate -ls -lh /opt/stacks/core/traefik/acme.json # Should be 600 permissions -``` -Verify: DuckDNS token valid, `DUCKDNS_TOKEN` in `.env`, DNS propagation (wait 2-5 min), acme.json writable by Traefik - -## AI Management Capabilities - -You can manage this homelab by: -- **Creating services**: Generate compose files in `/opt/stacks/` with proper Traefik labels -- **Modifying routes**: Edit Traefik labels in compose files -- **Managing external hosts**: Update `/opt/stacks/core/traefik/dynamic/external.yml` -- **Configuring Homepage**: Edit `services.yaml`, `bookmarks.yaml` in homepage config -- **Toggling SSO**: Add/remove Authelia middleware labels -- **Adding VPN routing**: Change network_mode and update Gluetun ports -- **Environment management**: Update `.env` (but remind users to manually copy to stacks) - -### What NOT to Do -- Never commit `.env` files to git (contain secrets) -- Don't use `docker run` for persistent services (use compose in `/opt/stacks/`) -- Don't manually request Let's Encrypt certs (Traefik handles via wildcard) -- Don't edit Authelia/Traefik config via web UI (use YAML files) -- Don't expose download clients without VPN (route through Gluetun) - -## Quick Command Reference - -```bash -# View all running containers -docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" - -# Check service logs -cd /opt/stacks/STACK && docker compose logs -f SERVICE - -# Restart specific service -cd /opt/stacks/STACK && docker compose restart SERVICE - -# Update images and redeploy -cd /opt/stacks/STACK && docker compose pull && docker compose up -d - -# Validate compose file -docker compose -f /opt/stacks/STACK/docker-compose.yml config - -# Check Traefik routes -docker logs traefik | grep -i "Creating router\|Adding route" - -# Check network connectivity -docker exec SERVICE ping -c 2 traefik - -# View environment variables -cd /opt/stacks/STACK && docker compose config | grep -A20 "environment:" - -# Test NVIDIA GPU access -docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi -``` - -## User Context Notes - -- **User**: kelin (PUID=1000, PGID=1000) -- **Repository**: `/home/kelin/AI-Homelab/` -- **Testing Phase**: Round 6+ (focus on reliability, error handling, deployment robustness) -- **Recent work**: Script automation, idempotent setup, health checks, automated secret generation - -When interacting with users, prioritize **security** (SSO by default), **consistency** (follow existing patterns), and **stack-awareness** (consider service dependencies). Always explain the "why" behind architectural decisions and reference specific files/paths when describing changes. +When creating/modifying services, prioritize stability, security, and consistency across the homelab. diff --git a/config-templates/homepage/services.yaml b/config-templates/homepage/services.yaml index 6023f08..b75fdf4 100644 --- a/config-templates/homepage/services.yaml +++ b/config-templates/homepage/services.yaml @@ -1,4 +1,18 @@ --- +# Currently Installed Services - Grouped by Stack + +- Dashboards: + - Homepage: + icon: homepage.png + href: https://home.kelin-casa.duckdns.org + description: This Dashboard + container: homepage + + - Homarr: + icon: homarr.png + href: https://homarr.kelin-casa.duckdns.org + description: Alternative Dashboard + container: homarr - Core: - Dockge: @@ -18,26 +32,8 @@ href: https://auth.kelin-casa.duckdns.org description: Authentication Portal container: authelia - - - Dashboards: - - Homepage: - icon: homepage.png - href: https://home.kelin-casa.duckdns.org - description: This Dashboard - container: homepage - - Homarr: - icon: homarr.png - href: https://homarr.kelin-casa.duckdns.org - description: Alternative Dashboard - container: homarr - Infrastructure: - - VS Code Server: - icon: vscode.png - href: https://code.kelin-casa.duckdns.org - description: Browser-based IDE - container: code-server - - Dozzle: icon: dozzle.png href: https://dozzle.kelin-casa.duckdns.org @@ -56,36 +52,7 @@ description: Network-wide Ad Blocking container: pihole -- Monitoring: - - Grafana: - icon: grafana.png - href: https://grafana.kelin-casa.duckdns.org - description: Metrics Dashboard - container: grafana - - - Prometheus: - icon: prometheus.png - href: https://prometheus.kelin-casa.duckdns.org - description: Metrics Collection - container: prometheus - - - Uptime Kuma: - icon: uptime-kuma.png - href: https://uptime-kuma.kelin-casa.duckdns.org - description: Uptime Monitoring - container: uptime-kuma - - - Loki: - icon: loki.png - href: https://loki.kelin-casa.duckdns.org - description: Log Aggregation - container: loki - - - cAdvisor: - icon: cadvisor.png - href: https://cadvisor.kelin-casa.duckdns.org - description: Container Metrics - container: cadvisor +# Available to Install - Grouped by Stack - Media: - Jellyfin: @@ -107,81 +74,54 @@ container: calibre-web - Media Management: - - Lidarr: - icon: lidarr.png - href: https://lidarr.kelin-casa.duckdns.org - description: Music Automation - container: lidarr - - Readarr: - icon: readarr.png - href: https://readarr.kelin-casa.duckdns.org - description: Books Automation - container: readarr - - Radarr: - icon: radarr.png - href: https://radarr.kelin-casa.duckdns.org - description: Movies Automation - container: radarr - Sonarr: icon: sonarr.png href: https://sonarr.kelin-casa.duckdns.org description: TV Shows Automation container: sonarr + + - Radarr: + icon: radarr.png + href: https://radarr.kelin-casa.duckdns.org + description: Movies Automation + container: radarr + - Prowlarr: icon: prowlarr.png href: https://prowlarr.kelin-casa.duckdns.org description: Indexer Manager container: prowlarr + + - Readarr: + icon: readarr.png + href: https://readarr.kelin-casa.duckdns.org + description: Books Automation + container: readarr + + - Lidarr: + icon: lidarr.png + href: https://lidarr.kelin-casa.duckdns.org + description: Music Automation + container: lidarr + - Mylar3: icon: mylar.png href: https://mylar.kelin-casa.duckdns.org description: Comics Manager container: mylar3 -- Productivity: - - Nextcloud: - icon: nextcloud.png - href: https://nextcloud.kelin-casa.duckdns.org - description: Cloud Storage & Collaboration - container: nextcloud + - qBittorrent: + icon: qbittorrent.png + href: https://qbit.kelin-casa.duckdns.org + description: Torrent Client + container: qbittorrent - - Mealie: - icon: mealie.png - href: https://mealie.kelin-casa.duckdns.org - description: Recipe Manager - container: mealie + - Tdarr: + icon: tdarr.png + href: https://tdarr.kelin-casa.duckdns.org + description: Media Transcoding + container: tdarr - - WordPress: - icon: wordpress.png - href: https://wordpress.kelin-casa.duckdns.org - description: CMS Platform - container: wordpress - - - Jupyter: - icon: jupyter.png - href: https://jupyter.kelin-casa.duckdns.org - description: Data Science Notebooks - container: jupyter - - - Gitea: - icon: gitea.png - href: https://gitea.kelin-casa.duckdns.org - description: Git Repository - container: gitea - -- Wiki: - - BookStack: - icon: bookstack.png - href: https://bookstack.kelin-casa.duckdns.org - description: Wiki Platform - container: bookstack - - - DokuWiki: - icon: dokuwiki.png - href: https://dokuwiki.kelin-casa.duckdns.org - description: Simple Wiki - container: dokuwiki - - Home Automation: - Home Assistant: icon: home-assistant.png @@ -213,6 +153,99 @@ description: MQTT Broker container: mosquitto +- Productivity: + - Nextcloud: + icon: nextcloud.png + href: https://nextcloud.kelin-casa.duckdns.org + description: Cloud Storage & Collaboration + container: nextcloud + + - Gitea: + icon: gitea.png + href: https://gitea.kelin-casa.duckdns.org + description: Git Repository + container: gitea + + - Mealie: + icon: mealie.png + href: https://mealie.kelin-casa.duckdns.org + description: Recipe Manager + container: mealie + + - BookStack: + icon: bookstack.png + href: https://bookstack.kelin-casa.duckdns.org + description: Wiki Platform + container: bookstack + + - DokuWiki: + icon: dokuwiki.png + href: https://dokuwiki.kelin-casa.duckdns.org + description: Simple Wiki + container: dokuwiki + + - WordPress: + icon: wordpress.png + href: https://wordpress.kelin-casa.duckdns.org + description: CMS Platform + container: wordpress + +- Monitoring Stack: + - Grafana: + icon: grafana.png + href: https://grafana.kelin-casa.duckdns.org + description: Metrics Dashboard + container: grafana + + - Prometheus: + icon: prometheus.png + href: https://prometheus.kelin-casa.duckdns.org + description: Metrics Collection + container: prometheus + + - Uptime Kuma: + icon: uptime-kuma.png + href: https://uptime-kuma.kelin-casa.duckdns.org + description: Uptime Monitoring + container: uptime-kuma + + - Loki: + icon: loki.png + href: https://loki.kelin-casa.duckdns.org + description: Log Aggregation + container: loki + + - cAdvisor: + icon: cadvisor.png + href: https://cadvisor.kelin-casa.duckdns.org + description: Container Metrics + container: cadvisor + +- Development: + - VS Code Server: + icon: vscode.png + href: https://code.kelin-casa.duckdns.org + description: Browser-based IDE + container: code-server + + - GitLab: + icon: gitlab.png + href: https://gitlab.kelin-casa.duckdns.org + description: DevOps Platform + container: gitea + + - Jupyter: + icon: jupyter.png + href: https://jupyter.kelin-casa.duckdns.org + description: Data Science Notebooks + container: jupyter + + - pgAdmin: + icon: pgadmin.png + href: https://pgadmin.kelin-casa.duckdns.org + description: PostgreSQL Admin + container: pgadmin + - Utilities: - Backrest: icon: mdi-backup-restore @@ -238,31 +271,6 @@ description: Form Builder container: formio -- VPN Protected: - - gluetun: - icon: gluetun.png - href: https://home.kelin-casa.duckdns.org - description: VPN Client - container: gluetun - - Downloaders: - - qBittorrent: - icon: qbittorrent.png - href: https://qbit.kelin-casa.duckdns.org - description: Torrent Client - container: qbittorrent - -- Transcoders: - - Unmanic: - icon: unmanic.png - href: https://unmanic.kelin-casa.duckdns.org - description: Media Transcoding - container: unmanic - - Tdarr: - icon: tdarr.png - href: https://tdarr.kelin-casa.duckdns.org - description: Media Transcoding - container: tdarr-server - - Alternatives: - Portainer: icon: portainer.png @@ -280,4 +288,4 @@ icon: plex.png href: https://plex.kelin-casa.duckdns.org description: Media Server - container: plex + container: plex \ No newline at end of file diff --git a/docker-compose/core.yml b/docker-compose/core.yml index aa3f072..bb7f4bc 100644 --- a/docker-compose/core.yml +++ b/docker-compose/core.yml @@ -3,11 +3,6 @@ # Deploy this stack FIRST before any other services # Place in /opt/stacks/core/docker-compose.yml -# Service Access URLs: -# - DuckDNS: No web UI (updates IP automatically) -# - Traefik: https://traefik.${DOMAIN} -# - Authelia: https://auth.${DOMAIN} - services: # DuckDNS - Dynamic DNS updater # Updates your public IP automatically for Let's Encrypt SSL @@ -23,7 +18,7 @@ services: - TOKEN=${DUCKDNS_TOKEN} # Your DuckDNS token - UPDATE_IP=ipv4 # or ipv6, or both volumes: - - ./duckdns:/config + - /opt/stacks/core/duckdns:/config labels: - "homelab.category=infrastructure" - "homelab.description=Dynamic DNS updater" @@ -45,9 +40,9 @@ services: volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - - ./traefik/traefik.yml:/traefik.yml:ro - - ./traefik/dynamic:/dynamic:ro - - ./traefik/acme.json:/acme.json + - /opt/stacks/core/traefik/traefik.yml:/traefik.yml:ro + - /opt/stacks/core/traefik/dynamic:/dynamic:ro + - /opt/stacks/core/traefik/acme.json:/acme.json environment: - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} # If using Cloudflare DNS challenge - DUCKDNS_TOKEN=${DUCKDNS_TOKEN} # If using DuckDNS @@ -57,8 +52,6 @@ services: - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)" - "traefik.http.routers.traefik.entrypoints=websecure" - "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}" - "traefik.http.routers.traefik.middlewares=authelia@docker" - "traefik.http.routers.traefik.service=api@internal" # Global HTTP to HTTPS redirect @@ -78,29 +71,61 @@ services: networks: - traefik-network volumes: - - ./authelia/configuration.yml:/config/configuration.yml:ro - - ./authelia/users_database.yml:/config/users_database.yml - - authelia-data:/data + - /opt/stacks/core/authelia/configuration.yml:/config/configuration.yml:ro + - /opt/stacks/core/authelia/users_database.yml:/config/users_database.yml + - authelia-data:/config environment: - TZ=${TZ} - AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET} - AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET} - AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY} + - AUTHELIA_NOTIFIER_SMTP_PASSWORD=${SMTP_PASSWORD} # If using email notifications labels: - "traefik.enable=true" - "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)" - "traefik.http.routers.authelia.entrypoints=websecure" - - "traefik.http.routers.authelia.tls=true" + - "traefik.http.routers.authelia.tls.certresolver=letsencrypt" - "traefik.http.services.authelia.loadbalancer.server.port=9091" # Authelia middleware for other services - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.${DOMAIN}" - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true" - "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email" - - "x-dockge.url=https://authelia.${DOMAIN}" - - "x-dockge.url=https://authelia.${DOMAIN}" depends_on: - traefik + # Gluetun - VPN client (Surfshark WireGuard) + # Routes download clients through VPN for security + gluetun: + image: qmcgaw/gluetun:latest + container_name: gluetun + restart: unless-stopped + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + networks: + - homelab-network + - traefik-network + ports: + - "8888:8888/tcp" # HTTP proxy + - "8388:8388/tcp" # Shadowsocks + - "8388:8388/udp" # Shadowsocks + - "8081:8080" # qBittorrent web UI (mapped to 8081 to avoid Traefik conflict) + - "6881:6881" # qBittorrent + - "6881:6881/udp" # qBittorrent + volumes: + - /opt/stacks/core/gluetun:/gluetun + environment: + - VPN_SERVICE_PROVIDER=surfshark + - VPN_TYPE=wireguard + - WIREGUARD_PRIVATE_KEY=${SURFSHARK_PRIVATE_KEY} + - WIREGUARD_ADDRESSES=${SURFSHARK_ADDRESSES} + - SERVER_COUNTRIES=${VPN_SERVER_COUNTRIES:-Netherlands} + - TZ=${TZ} + labels: + - "homelab.category=infrastructure" + - "homelab.description=VPN client for secure downloads" + volumes: authelia-data: driver: local @@ -108,3 +133,5 @@ volumes: networks: traefik-network: external: true + homelab-network: + external: true diff --git a/docker-compose/monitoring.yml b/docker-compose/monitoring.yml index 54f18fa..f7eee27 100644 --- a/docker-compose/monitoring.yml +++ b/docker-compose/monitoring.yml @@ -17,15 +17,6 @@ services: # Access at: http://server-ip:9090 prometheus: image: prom/prometheus:v2.48.1 - deploy: - resources: - limits: - cpus: '0.75' - memory: 512M - pids: 1024 - reservations: - cpus: '0.25' - memory: 256M container_name: prometheus restart: unless-stopped networks: @@ -44,6 +35,7 @@ services: - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' - '--web.enable-lifecycle' + user: "${PUID:-1000}:${PGID:-1000}" labels: - "homelab.category=monitoring" - "homelab.description=Metrics collection and time-series database" @@ -60,15 +52,6 @@ services: # Default credentials: admin / admin (change on first login) grafana: image: grafana/grafana:10.2.3 - deploy: - resources: - limits: - cpus: '0.50' - memory: 256M - pids: 512 - reservations: - cpus: '0.25' - memory: 128M container_name: grafana restart: unless-stopped networks: @@ -153,22 +136,11 @@ services: - "traefik.http.routers.cadvisor.tls.certresolver=letsencrypt" - "traefik.http.routers.cadvisor.middlewares=authelia@docker" - "traefik.http.services.cadvisor.loadbalancer.server.port=8080" - - "x-dockge.url=https://cadvisor.${DOMAIN}" - - "x-dockge.url=https://cadvisor.${DOMAIN}" # Uptime Kuma - Uptime monitoring # Access at: https://uptime-kuma.${DOMAIN} uptime-kuma: image: louislam/uptime-kuma:1 - deploy: - resources: - limits: - cpus: '0.50' - memory: 256M - pids: 512 - reservations: - cpus: '0.25' - memory: 128M container_name: uptime-kuma restart: unless-stopped networks: @@ -193,15 +165,6 @@ services: # Access at: http://server-ip:3100 loki: image: grafana/loki:2.9.3 - deploy: - resources: - limits: - cpus: '0.75' - memory: 512M - pids: 1024 - reservations: - cpus: '0.25' - memory: 256M container_name: loki restart: unless-stopped networks: @@ -214,6 +177,7 @@ services: - ./config/loki:/etc/loki - loki-data:/loki command: -config.file=/etc/loki/loki-config.yml + user: "${PUID:-1000}:${PGID:-1000}" labels: - "homelab.category=monitoring" - "homelab.description=Log aggregation system" diff --git a/docs/service-docs/qbittorrent.md b/docs/service-docs/qbittorrent.md index 7e09797..f2852d2 100644 --- a/docs/service-docs/qbittorrent.md +++ b/docs/service-docs/qbittorrent.md @@ -19,9 +19,9 @@ **Docker Image:** [linuxserver/qbittorrent](https://hub.docker.com/r/linuxserver/qbittorrent) **Default Stack:** `media.yml` **Network Mode:** Via Gluetun (VPN container) -**Web UI:** `http://SERVER_IP:8080` (via Gluetun) +**Web UI:** `http://SERVER_IP:8081` (via Gluetun) **Authentication:** Username/password (default: admin/adminadmin) -**Ports:** 8080 (WebUI via Gluetun), 6881 (incoming connections via Gluetun) +**Ports:** 8081 (WebUI via Gluetun), 6881 (incoming connections via Gluetun) ## What is qBittorrent? @@ -158,7 +158,7 @@ gluetun: devices: - /dev/net/tun ports: - - "8080:8080" # qBittorrent WebUI + - "8081:8080" # qBittorrent WebUI (host:container) - "6881:6881" # qBittorrent incoming - "6881:6881/udp" environment: @@ -181,7 +181,7 @@ qbittorrent: - PUID=1000 - PGID=1000 - TZ=America/New_York - - WEBUI_PORT=8080 + - WEBUI_PORT=8081 volumes: - /opt/stacks/media/qbittorrent/config:/config - /mnt/downloads:/downloads @@ -191,7 +191,7 @@ qbittorrent: - `network_mode: "service:gluetun"` routes all traffic through VPN - Ports exposed on Gluetun, not qBittorrent - No internet access if VPN down (kill switch) -- Access via `http://SERVER_IP:8080` (Gluetun's port) +- Access via `http://SERVER_IP:8081` (Gluetun's port) ### Standalone Configuration (without VPN - NOT RECOMMENDED) @@ -201,7 +201,7 @@ qbittorrent: container_name: qbittorrent restart: unless-stopped ports: - - "8080:8080" + - "8081:8080" - "6881:6881" - "6881:6881/udp" environment: @@ -238,7 +238,7 @@ qbittorrent: ``` 4. **Access Web UI:** - - Navigate to: `http://SERVER_IP:8080` + - Navigate to: `http://SERVER_IP:8081` - Username: `admin` - Password: From logs above @@ -542,7 +542,7 @@ docker logs qbittorrent | tail -20 docker logs gluetun | grep -i "connected" # Test access -curl http://localhost:8080 +curl http://localhost:8081 ``` ### Slow Download Speeds diff --git a/docs/services-overview.md b/docs/services-overview.md index ccf9089..af369cb 100644 --- a/docs/services-overview.md +++ b/docs/services-overview.md @@ -6,48 +6,45 @@ This document provides a comprehensive overview of all 60+ pre-configured servic | Stacks (10) | Services (70 + 6db) | SSO | Storage | Access URLs | |-------|----------|-----|---------|-------------| -| **📦 [core.yaml](../docker-compose/core.yml) (4)** | **Deploy First** | | | | -| ├─ [DuckDNS](service-docs/duckdns.md) | Dynamic DNS updater | - | /opt/stacks/core/duckdns | No UI | -| ├─ [Traefik](service-docs/traefik.md) | Reverse proxy + SSL | ✓ | /opt/stacks/core/traefik | traefik.${DOMAIN} | -| ├─ [Authelia](service-docs/authelia.md) | SSO authentication | - | /opt/stacks/core/authelia | auth.${DOMAIN} | -| └─ [Gluetun](service-docs/gluetun.md) | VPN (Surfshark) | - | /opt/stacks/core/gluetun | No UI | -| **🔧 [infrastructure.yaml](../docker-compose/infrastructure.yml) (8+5)** | **Deployed: 8** | | | | -| ├─ [Dockge](service-docs/dockge.md) | Stack manager (PRIMARY) | ✓ | /opt/stacks/infrastructure | dockge.${DOMAIN} | -| ├─ [Pi-hole](service-docs/pihole.md) | DNS + Ad blocking | ✓ | /opt/stacks/infrastructure | pihole.${DOMAIN} | -| ├─ [Dozzle](service-docs/dozzle.md) | Docker log viewer | ✓ | /opt/stacks/infrastructure | dozzle.${DOMAIN} | -| ├─ [Glances](service-docs/glances.md) | System monitoring | ✓ | /opt/stacks/infrastructure | glances.${DOMAIN} | +| **📦 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} | +| └─ Gluetun | VPN (Surfshark) | - | /opt/stacks/core/gluetun | No UI | +| **🔧 infrastructure.yaml** (12) | | | | | +| ├─ Dockge | Stack manager (PRIMARY) | ✓ | /opt/stacks/infrastructure | dockge.${DOMAIN} | +| ├─ Portainer | Container management | ✓ | /opt/stacks/infrastructure | portainer.${DOMAIN} | +| ├─ Authentik Server | SSO with web UI | ✓ | /opt/stacks/authentik | authentik.${DOMAIN} | +| │ ├─ authentik-worker | Background tasks | - | /opt/stacks/authentik | No UI | +| │ ├─ authentik-db | PostgreSQL | - | /opt/stacks/authentik | No UI | +| │ └─ authentik-redis | Cache/messaging | - | /opt/stacks/authentik | No UI | +| ├─ Pi-hole | DNS + Ad blocking | ✓ | /opt/stacks/infrastructure | pihole.${DOMAIN} | | ├─ Watchtower | Auto container updates | - | /opt/stacks/infrastructure | No UI | -| ├─ Code Server | VS Code in browser | ✓ | /opt/stacks/infrastructure | code.${DOMAIN} | -| └─ [Docker Proxy](service-docs/docker-proxy.md) | Secure socket access | - | /opt/stacks/infrastructure | No UI | -| **📦 [alternatives.yaml](../docker-compose/alternatives.yml) (6)** | **Not deployed** | | | | -| ├─ Plex | Media server (Alt) | ✗ | /mnt/media, /mnt/transcode | plex.${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 | -| **📊 [dashboards.yaml](../docker-compose/dashboards.yml)** (2) | | | | | +| ├─ Dozzle | Docker log viewer | ✓ | /opt/stacks/infrastructure | dozzle.${DOMAIN} | +| ├─ Glances | System monitoring | ✓ | /opt/stacks/infrastructure | glances.${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.yml](../docker-compose/media.yml)** (3) | | | | | +| **🎬 media** (6) | | | | | +| ├─ Plex | Media server | ✗ | /mnt/media, /mnt/transcode | plex.${DOMAIN} | | ├─ Jellyfin | Media server (OSS) | ✗ | /mnt/media, /mnt/transcode | jellyfin.${DOMAIN} | -| ├─ Calibre-Web | Ebook reader | ✓ | /opt/stacks/media, /mnt/media | calibre.${DOMAIN} | +| ├─ Sonarr | TV automation | ✓ | /opt/stacks/media, /mnt/media | sonarr.${DOMAIN} | +| ├─ Radarr | Movie automation | ✓ | /opt/stacks/media, /mnt/media | radarr.${DOMAIN} | +| ├─ Prowlarr | Indexer manager | ✓ | /opt/stacks/media | prowlarr.${DOMAIN} | | └─ qBittorrent | Torrent (via VPN) | ✓ | /mnt/downloads | qbit.${DOMAIN} | -| **📚 [media-management.yml](../docker-compose/media-management.yml)** (10) | | | | | -| ├─ Sonarr | TV automation | ✓ | /opt/stacks/media-mgmt, /mnt/media | sonarr.${DOMAIN} | -| ├─ Radarr | Movie automation | ✓ | /opt/stacks/media-mgmt, /mnt/media | radarr.${DOMAIN} | -| ├─ Prowlarr | Indexer manager | ✓ | /opt/stacks/media-mgmt | prowlarr.${DOMAIN} | -| ├─ Readarr | Ebooks/Audiobooks | ✓ | /opt/stacks/media-mgmt, /mnt/media | readarr.${DOMAIN} | -| ├─ Lidarr | Music manager | ✓ | /opt/stacks/media-mgmt, /mnt/media | lidarr.${DOMAIN} | -| ├─ Lazy Librarian | Book automation | ✓ | /opt/stacks/media-mgmt, /mnt/media | lazylibrarian.${DOMAIN} | -| ├─ Mylar3 | Comic manager | ✓ | /opt/stacks/media-mgmt, /mnt/media | mylar.${DOMAIN} | -| ├─ Jellyseerr | Media requests | ✓ | /opt/stacks/media-mgmt | jellyseerr.${DOMAIN} | -| ├─ FlareSolverr | Cloudflare bypass | - | /opt/stacks/media-mgmt | No UI | -| ├─ Tdarr Server | Transcoding server | ✓ | /opt/stacks/media-mgmt, /mnt/transcode | tdarr.${DOMAIN} | +| **📚 media-extended.yaml** (10) | | | | | +| ├─ Readarr | Ebooks/Audiobooks | ✓ | /opt/stacks/media-ext, /mnt/media | readarr.${DOMAIN} | +| ├─ Lidarr | Music manager | ✓ | /opt/stacks/media-ext, /mnt/media | lidarr.${DOMAIN} | +| ├─ Lazy Librarian | Book automation | ✓ | /opt/stacks/media-ext, /mnt/media | lazylibrarian.${DOMAIN} | +| ├─ Mylar3 | Comic manager | ✓ | /opt/stacks/media-ext, /mnt/media | mylar.${DOMAIN} | +| ├─ Calibre-Web | Ebook reader | ✓ | /opt/stacks/media-ext, /mnt/media | calibre.${DOMAIN} | +| ├─ Jellyseerr | Media requests | ✓ | /opt/stacks/media-ext | jellyseerr.${DOMAIN} | +| ├─ FlareSolverr | Cloudflare bypass | - | /opt/stacks/media-ext | No UI | +| ├─ Tdarr Server | Transcoding server | ✓ | /opt/stacks/media-ext, /mnt/transcode | tdarr.${DOMAIN} | | ├─ Tdarr Node | Transcoding worker | - | /mnt/transcode-cache | No UI | -| ├─ Unmanic | Library optimizer | ✓ | /opt/stacks/media-mgmt, /mnt/transcode | unmanic.${DOMAIN} | -| └─ Bazarr | Subtitle automation | ✓ | /opt/stacks/media-mgmt, /mnt/media | bazarr.${DOMAIN} | -| **🏠 [homeassistant.yaml](../docker-compose/homeassistant.yml)** (7) | | | | | +| └─ Unmanic | Library optimizer | ✓ | /opt/stacks/media-ext, /mnt/transcode | unmanic.${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} | @@ -55,7 +52,7 @@ This document provides a comprehensive overview of all 60+ pre-configured servic | ├─ 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](../docker-compose/productivity.yml)** (8 + 6 DBs) | | | | | +| **💼 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} | @@ -70,14 +67,15 @@ This document provides a comprehensive overview of all 60+ pre-configured servic | │ └─ 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](../docker-compose/utilities.yml)** (6) | | | | | +| **🛠️ 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](../docker-compose/monitoring.yml)** (8) | | | | | +| **📈 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 | @@ -85,7 +83,7 @@ This document provides a comprehensive overview of all 60+ pre-configured servic | ├─ 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](../docker-compose/development.yml)** (6) | | | | | +| **👨‍💻 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 | @@ -95,6 +93,42 @@ This document provides a comprehensive overview of all 60+ pre-configured servic **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/` + - Extended Media: `/opt/stacks/media-extended/` + - Home Automation: `/opt/stacks/homeassistant/` + - Productivity: `/opt/stacks/productivity/` + - Utilities: `/opt/stacks/utilities/` + - Monitoring: `/opt/stacks/monitoring/` + - Development: `/opt/stacks/development/` + ## 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. @@ -156,6 +190,137 @@ docker compose -f /opt/stacks/stack-name/docker-compose.yml down - **Gradual Exposure**: Comment out SSO only when ready to expose a service - **Quick Toggle**: AI assistant can modify these labels automatically when you ask +## Authelia Customization + +### Available Customization Options + +**1. Branding and Appearance** +Edit `/opt/stacks/core/authelia/configuration.yml`: + +```yaml +# Custom logo and branding +theme: dark # Options: light, dark, grey, auto + +# No built-in web UI for configuration +# All settings managed via YAML files +``` + +**2. User Management** +Users are managed in `/opt/stacks/core/authelia/users_database.yml`: + +```yaml +users: + username: + displayname: "Display Name" + password: "$argon2id$v=19$m=65536..." # Generated with authelia hash-password + email: user@example.com + groups: + - admins + - users +``` + +Generate password hash: +```bash +docker run --rm authelia/authelia:4.37 authelia hash-password 'yourpassword' +``` + +**3. Access Control Rules** +Customize who can access what in `configuration.yml`: + +```yaml +access_control: + default_policy: deny + + rules: + # Public services (no auth) + - domain: + - "jellyfin.yourdomain.com" + - "plex.yourdomain.com" + policy: bypass + + # Admin only services + - domain: + - "dockge.yourdomain.com" + - "portainer.yourdomain.com" + policy: two_factor + subject: + - "group:admins" + + # All authenticated users + - domain: "*.yourdomain.com" + policy: one_factor +``` + +**4. Two-Factor Authentication (2FA)** +- TOTP (Time-based One-Time Password) via apps like Google Authenticator, Authy +- Configure in `configuration.yml` under `totp:` section +- Per-user enrollment via Authelia UI at `https://auth.${DOMAIN}` + +**5. Session Management** +Edit `configuration.yml`: + +```yaml +session: + name: authelia_session + expiration: 1h # How long before re-login required + inactivity: 5m # Timeout after inactivity + remember_me_duration: 1M # "Remember me" checkbox duration +``` + +**6. Notification Settings** +Email notifications for password resets, 2FA enrollment: + +```yaml +notifier: + smtp: + host: smtp.gmail.com + port: 587 + username: your-email@gmail.com + password: app-password + sender: authelia@yourdomain.com +``` + +### No Web UI for Configuration + +⚠️ **Important**: Authelia does **not** have a configuration web UI. All configuration is done via YAML files: +- `/opt/stacks/core/authelia/configuration.yml` - Main settings +- `/opt/stacks/core/authelia/users_database.yml` - User accounts + +This is **by design** and makes Authelia perfect for AI management and security-first approach: +- AI can read and modify YAML files +- Version control friendly +- No UI clicks required +- Infrastructure as code +- Secure by default + +**Web UI Available For:** +- Login page: `https://auth.${DOMAIN}` +- User profile: Change password, enroll 2FA +- Device enrollment: Manage trusted devices + +**Alternative with Web UI: Authentik** +If you need a web UI for user management, Authentik is included in the infrastructure stack: +- **Authentik**: Full-featured SSO with web UI for user/group management +- Access at: `https://authentik.${DOMAIN}` +- Includes PostgreSQL database and Redis cache +- More complex but offers GUI-based configuration +- Deploy only if you need web-based user management + +**Other Alternatives:** +- **Keycloak**: Enterprise-grade SSO with web UI +- **Authelia + LDAP**: Use LDAP with web management (phpLDAPadmin, etc.) + +### Quick Configuration with AI + +Since all Authelia configuration is file-based, you can use the AI assistant to: +- Add/remove users +- Modify access rules +- Change session settings +- Update branding +- Enable/disable features + +Just ask: "Add a new user to Authelia" or "Change session timeout to 2 hours" + ## Storage Recommendations | Data Type | Recommended Location | Reason | diff --git a/scripts/deploy-homelab.sh b/scripts/deploy-homelab.sh index 21630bd..6f8472d 100755 --- a/scripts/deploy-homelab.sh +++ b/scripts/deploy-homelab.sh @@ -30,511 +30,10 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1" } -#========================================== -# VALIDATION FUNCTIONS -#========================================== - -validate_prerequisites() { - # Check if .env file exists - if [ ! -f "$REPO_DIR/.env" ]; then - log_error ".env file not found!" - log_info "Please create and configure your .env file first:" - echo " cd $REPO_DIR" - echo " cp .env.example .env" - echo " nano .env" - exit 1 - fi - - # Check if Docker is installed and running - log_info "Validating Docker installation..." - - if ! command -v docker &> /dev/null; then - log_error "Docker is not installed" - log_info "Please run the setup script first:" - log_info " cd $REPO_DIR" - log_info " sudo ./scripts/setup-homelab.sh" - exit 1 - fi - - if ! docker info &> /dev/null 2>&1; then - log_error "Docker daemon is not running or not accessible" - echo "" - log_info "Troubleshooting steps:" - log_info " 1. Start Docker: sudo systemctl start docker" - log_info " 2. Enable Docker on boot: sudo systemctl enable docker" - log_info " 3. Check Docker status: sudo systemctl status docker" - log_info " 4. If recently added to docker group, log out and back in" - log_info " 5. Test access: docker ps" - echo "" - log_info "Current user: $ACTUAL_USER" - log_info "Docker group membership: $(groups $ACTUAL_USER | grep -o docker || echo 'NOT IN DOCKER GROUP')" - exit 1 - fi - - log_success "Docker is available and running" - log_info "Docker version: $(docker --version | cut -d' ' -f3 | tr -d ',')" - echo "" - - # Load environment variables for domain check - source "$REPO_DIR/.env" - - if [ -z "$DOMAIN" ]; then - log_error "DOMAIN is not set in .env file" - log_info "Please edit .env and set your DuckDNS domain" - exit 1 - fi - - log_info "Using domain: $DOMAIN" - echo "" -} - -#========================================== -# DEPLOYMENT STEP FUNCTIONS -#========================================== - -step_1_create_directories() { - log_info "Step 1/7: Creating required directories..." - mkdir -p /opt/stacks/core - mkdir -p /opt/stacks/infrastructure - mkdir -p /opt/dockge/data - log_success "Directories created" - echo "" -} - -step_2_create_networks() { - log_info "Step 2/7: Creating Docker networks..." - docker network create homelab-network 2>/dev/null && log_success "Created homelab-network" || log_info "homelab-network already exists" - docker network create traefik-network 2>/dev/null && log_success "Created traefik-network" || log_info "traefik-network already exists" - docker network create dockerproxy-network 2>/dev/null && log_success "Created dockerproxy-network" || log_info "dockerproxy-network already exists" - docker network create media-network 2>/dev/null && log_success "Created media-network" || log_info "media-network already exists" - echo "" -} - -configure_authelia() { - # Configure Authelia admin user from setup script - if [ -f /opt/stacks/.setup-temp/authelia_admin_credentials.tmp ] && [ -f /opt/stacks/.setup-temp/authelia_password_hash.tmp ]; then - log_info "Loading Authelia admin credentials from setup temp files..." - source /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - elif [ -n "${AUTHELIA_ADMIN_USER}" ] && [ -n "${AUTHELIA_ADMIN_EMAIL}" ] && [ -n "${AUTHELIA_ADMIN_PASSWORD}" ]; then - log_info "Loading Authelia admin credentials from .env file..." - ADMIN_USER="${AUTHELIA_ADMIN_USER}" - ADMIN_EMAIL="${AUTHELIA_ADMIN_EMAIL}" - ADMIN_PASSWORD="${AUTHELIA_ADMIN_PASSWORD}" - - # Generate password hash from the password in .env - log_info "Generating password hash from .env credentials..." - docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" > /tmp/authelia_password_hash_from_env.tmp 2>/dev/null - - if [ $? -eq 0 ]; then - # Create temp directory and files for the rest of the script - mkdir -p /opt/stacks/.setup-temp - echo "ADMIN_USER=$ADMIN_USER" > /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - echo "ADMIN_EMAIL=$ADMIN_EMAIL" >> /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - chmod 600 /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - - # Extract just the hash (remove "Digest: " prefix if present) - sed 's/^Digest: //' /tmp/authelia_password_hash_from_env.tmp | grep '^\$argon2' > /opt/stacks/.setup-temp/authelia_password_hash.tmp - chmod 600 /opt/stacks/.setup-temp/authelia_password_hash.tmp - rm -f /tmp/authelia_password_hash_from_env.tmp - - log_success "Credentials loaded from .env file" - else - log_error "Failed to generate password hash from .env credentials" - ADMIN_USER="" - ADMIN_EMAIL="" - fi - fi - - if [ -f /opt/stacks/.setup-temp/authelia_admin_credentials.tmp ] && [ -f /opt/stacks/.setup-temp/authelia_password_hash.tmp ]; then - source /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - - if [ -n "$ADMIN_USER" ] && [ -n "$ADMIN_EMAIL" ]; then - log_success "Using credentials: $ADMIN_USER ($ADMIN_EMAIL)" - - # Create users_database.yml with credentials from setup - # Use single quotes in heredoc to prevent variable expansion issues with $ in hash - cat > /opt/stacks/core/authelia/users_database.yml << 'EOF' -############################################################### -# Users Database # -############################################################### - -users: - ADMIN_USER_PLACEHOLDER: - displayname: "Admin User" - password: "PASSWORD_HASH_PLACEHOLDER" - email: ADMIN_EMAIL_PLACEHOLDER - groups: - - admins - - users -EOF - # Now safely replace placeholders - # Read hash from file (not bash variable) to avoid shell expansion - # The hash file was written directly from Docker output in setup script - export ADMIN_USER - export ADMIN_EMAIL - python3 << 'PYTHON_EOF' -# Read password hash from file to completely avoid bash variable expansion -with open('/opt/stacks/.setup-temp/authelia_password_hash.tmp', 'r') as f: - password_hash = f.read().strip() - -import os -admin_user = os.environ['ADMIN_USER'] -admin_email = os.environ['ADMIN_EMAIL'] - -content = f"""############################################################### -# Users Database # -############################################################### - -users: - {admin_user}: - displayname: "Admin User" - password: "{password_hash}" - email: {admin_email} - groups: - - admins - - users -""" - -with open('/opt/stacks/core/authelia/users_database.yml', 'w') as f: - f.write(content) -PYTHON_EOF - - log_success "Authelia admin user configured from setup script" - echo "" - echo "===========================================" - log_info "Authelia Login Credentials:" - echo " Username: $ADMIN_USER" - echo " Password: $ADMIN_PASSWORD" - echo " Email: $ADMIN_EMAIL" - echo "===========================================" - echo "" - log_warning "SAVE THESE CREDENTIALS!" - - # Save full credentials for later reference - { - echo "Username: $ADMIN_USER" - echo "Password: $ADMIN_PASSWORD" - echo "Email: $ADMIN_EMAIL" - } > /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt - chmod 600 /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt - chown $ACTUAL_USER:$ACTUAL_USER /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt - echo "" - - # Clean up credentials files from setup script - rm -f /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - rm -f /opt/stacks/.setup-temp/authelia_password_hash.tmp - rm -f /opt/stacks/core/authelia/ADMIN_PASSWORD.txt - rm -f /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt - rmdir /opt/stacks/.setup-temp 2>/dev/null || true - log_info "Cleaned up temporary setup files" - else - log_warning "Incomplete credentials from setup script" - log_info "Using template users_database.yml - please configure manually" - fi - else - log_warning "No credentials file found from setup script" - log_info "Using template users_database.yml from config-templates" - log_info "Please run setup-homelab.sh first or configure manually" - fi -} - -step_3_deploy_core() { - log_info "Step 3/7: Deploying core infrastructure stack..." - log_info " - DuckDNS (Dynamic DNS)" - log_info " - Traefik (Reverse Proxy with SSL)" - log_info " - Authelia (Single Sign-On)" - log_info " - Gluetun (VPN Client)" - echo "" - - # Copy core stack files - log_info "Preparing core stack configuration files..." - - # Safety: Stop existing core stack if running (prevents file conflicts) - if [ -f "/opt/stacks/core/docker-compose.yml" ]; then - log_info "Stopping existing core stack for safe reconfiguration..." - cd /opt/stacks/core && docker compose down 2>/dev/null || true - sleep 2 - fi - - # Clean up any incorrect directory structure from previous runs - if [ -d "/opt/stacks/core/traefik/acme.json" ]; then - log_warning "Removing incorrectly created acme.json directory" - rm -rf /opt/stacks/core/traefik/acme.json - fi - if [ -d "/opt/stacks/core/traefik/traefik.yml" ]; then - log_warning "Removing incorrectly created traefik.yml directory" - rm -rf /opt/stacks/core/traefik/traefik.yml - fi - - # Copy compose file - cp "$REPO_DIR/docker-compose/core.yml" /opt/stacks/core/docker-compose.yml - - # Safely remove and replace config directories - if [ -d "/opt/stacks/core/traefik" ]; then - rm -rf /opt/stacks/core/traefik - fi - if [ -d "/opt/stacks/core/authelia" ]; then - rm -rf /opt/stacks/core/authelia - fi - - cp -r "$REPO_DIR/config-templates/traefik" /opt/stacks/core/ - cp -r "$REPO_DIR/config-templates/authelia" /opt/stacks/core/ - cp "$REPO_DIR/.env" /opt/stacks/core/.env - - # Create acme.json as a file (not directory) with correct permissions - log_info "Creating acme.json for SSL certificates..." - touch /opt/stacks/core/traefik/acme.json - chmod 600 /opt/stacks/core/traefik/acme.json - log_success "acme.json created with correct permissions" - - # Replace email placeholder in traefik.yml - log_info "Configuring Traefik with email: $ACME_EMAIL..." - sed -i "s/ACME_EMAIL_PLACEHOLDER/${ACME_EMAIL}/g" /opt/stacks/core/traefik/traefik.yml - log_success "Traefik email configured" - - # Replace domain placeholder in authelia configuration - log_info "Configuring Authelia for domain: $DOMAIN..." - sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml - - # Configure Authelia admin user - configure_authelia - - # Clean up old Authelia database if encryption key changed - # This prevents "encryption key does not appear to be valid" errors - if [ -d "/var/lib/docker/volumes/core_authelia-data/_data" ]; then - log_info "Checking for existing Authelia database..." - # Check if database exists and might have encryption key mismatch - if [ -f "/var/lib/docker/volumes/core_authelia-data/_data/db.sqlite3" ]; then - log_warning "Existing Authelia database found from previous deployment" - log_info "If deployment fails with encryption key errors, run: sudo ./scripts/reset-test-environment.sh" - fi - fi - - # Deploy core stack - cd /opt/stacks/core - docker compose up -d - - log_success "Core infrastructure deployed" - echo "" - - # Wait for Traefik to be ready - log_info "Waiting for Traefik to initialize..." - sleep 10 - - # Check if Traefik is healthy - if docker ps | grep -q "traefik.*Up"; then - log_success "Traefik is running" - else - log_warning "Traefik container check inconclusive, continuing..." - fi - echo "" -} - -step_4_deploy_infrastructure() { - log_info "Step 4/7: Deploying infrastructure stack..." - log_info " - Dockge (Docker Compose Manager)" - log_info " - Pi-hole (DNS Ad Blocker)" - log_info " - Dozzle (Log Viewer)" - log_info " - Glances (System Monitor)" - log_info " - Docker Proxy (Security)" - log_info " - Watchtower (Automatic Updates)" - echo "" - - # Copy infrastructure stack - cp "$REPO_DIR/docker-compose/infrastructure.yml" /opt/stacks/infrastructure/docker-compose.yml - cp "$REPO_DIR/.env" /opt/stacks/infrastructure/.env - - # Deploy infrastructure stack - cd /opt/stacks/infrastructure - docker compose up -d - - log_success "Infrastructure stack deployed" - echo "" -} - -step_5_deploy_dashboards() { - log_info "Step 5/7: Deploying dashboards stack..." - log_info " - Homepage (AI-configurable Dashboard)" - log_info " - Homarr (Modern Dashboard)" - echo "" - - # Copy dashboards stack - mkdir -p /opt/stacks/dashboards - cp "$REPO_DIR/docker-compose/dashboards.yml" /opt/stacks/dashboards/docker-compose.yml - cp "$REPO_DIR/.env" /opt/stacks/dashboards/.env - - # Copy and configure homepage templates - if [ -d "$REPO_DIR/config-templates/homepage" ]; then - cp -r "$REPO_DIR/config-templates/homepage" /opt/stacks/dashboards/ - - # Replace HOMEPAGE_VAR_DOMAIN with actual domain in all homepage config files - # Homepage doesn't support environment variables in configs - find /opt/stacks/dashboards/homepage -type f \( -name "*.yaml" -o -name "*.yml" \) -exec sed -i "s/{{HOMEPAGE_VAR_DOMAIN}}/${DOMAIN}/g" {} \; - - log_info "Homepage configuration templates copied and configured" - fi - - # Deploy dashboards stack - cd /opt/stacks/dashboards - docker compose up -d - - log_success "Dashboards stack deployed" - echo "" -} - -step_6_prepare_additional_stacks() { - log_info "Step 6/7: Preparing additional stacks for Dockge..." - echo "" - log_info "The following stacks can be deployed through Dockge's web UI:" - log_info " - media.yml (Jellyfin, Calibre-web, qBittorrent)" - log_info " - media-management.yml (Sonarr, Radarr, *arr apps)" - log_info " - homeassistant.yml (Home Assistant and accessories)" - log_info " - productivity.yml (Nextcloud, Gitea, wikis)" - log_info " - monitoring.yml (Grafana, Prometheus, etc.)" - log_info " - utilities.yml (Backups, code editors, etc.)" - log_info " - alternatives.yml (Portainer, Authentik)" - log_info " - development.yml (VS Code Server, GitLab, Jupyter)" - echo "" - - # Ask user if they want to pre-pull images for additional stacks - read -p "Pre-pull Docker images for additional stacks? This will take time but speeds up first deployment (y/N): " PULL_IMAGES - PULL_IMAGES=${PULL_IMAGES:-n} - - # Copy additional stacks to Dockge directory - ADDITIONAL_STACKS=("media" "media-management" "homeassistant" "productivity" "monitoring" "utilities" "alternatives" "development") - - for stack in "${ADDITIONAL_STACKS[@]}"; do - mkdir -p "/opt/stacks/$stack" - cp "$REPO_DIR/docker-compose/${stack}.yml" "/opt/stacks/$stack/docker-compose.yml" - cp "$REPO_DIR/.env" "/opt/stacks/$stack/.env" - - # Pre-pull images if requested - if [[ "$PULL_IMAGES" =~ ^[Yy]$ ]]; then - log_info "Pulling images for $stack stack..." - cd "/opt/stacks/$stack" - docker compose pull 2>&1 | grep -E '(Pulling|Downloaded|Already exists|up to date)' || true - fi - done - - log_success "Additional stacks prepared in Dockge" - log_info "These stacks are NOT started - deploy them via Dockge web UI as needed" - echo "" -} - -step_7_wait_for_dockge() { - log_info "Step 7/7: Waiting for Dockge web UI to be ready..." - - DOCKGE_URL="https://dockge.${DOMAIN}" - MAX_WAIT=60 # Maximum wait time in seconds - WAITED=0 - - # Function to check if Dockge is accessible - check_dockge() { - # Try to connect to Dockge (ignore SSL cert warnings for self-signed during startup) - curl -k -s -o /dev/null -w "%{http_code}" "$DOCKGE_URL" 2>/dev/null - } - - # Wait for Dockge to respond - while [ $WAITED -lt $MAX_WAIT ]; do - HTTP_CODE=$(check_dockge) - if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "401" ]; then - log_success "Dockge web UI is ready!" - break - fi - echo -n "." - sleep 2 - WAITED=$((WAITED + 2)) - done - - echo "" - echo "" - - if [ $WAITED -ge $MAX_WAIT ]; then - log_warning "Dockge did not respond within ${MAX_WAIT} seconds" - log_info "It may still be starting up. Check manually at: $DOCKGE_URL" - else - # Try to open browser - log_info "Opening Dockge in your browser..." - - # Detect and use available browser - if command -v xdg-open &> /dev/null; then - xdg-open "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - elif command -v gnome-open &> /dev/null; then - gnome-open "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - elif command -v firefox &> /dev/null; then - firefox "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - elif command -v google-chrome &> /dev/null; then - google-chrome "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - else - log_warning "No browser detected. Please manually open: $DOCKGE_URL" - fi - fi - echo "" -} - -show_final_summary() { - echo "==========================================" - log_success "Deployment completed successfully!" - echo "==========================================" - echo "" - log_info "Access your services:" - echo "" - echo " 🚀 Dockge: https://dockge.${DOMAIN}" - echo " 🔒 Authelia: https://auth.${DOMAIN}" - echo " 🔀 Traefik: https://traefik.${DOMAIN}" - echo "" - log_info "SSL Certificates:" - echo " 📝 Let's Encrypt certificates will be acquired automatically within 2-5 minutes" - echo " ⚠️ Initial access uses self-signed certs (browser warning is normal)" - echo " 🔓 Ensure ports 80/443 are accessible from internet for Let's Encrypt" - echo " 💾 Admin credentials saved to: /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt" - echo "" - log_info "Next steps:" - echo "" - echo " 1. Log in to Dockge using your Authelia credentials" - echo " (saved in /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt)" - echo "" - echo " 2. Deploy additional stacks through Dockge's web UI:" - echo " - media.yml (Jellyfin, Calibre-web, qBittorrent)" - echo " - media-management.yml (Sonarr, Radarr, *arr apps)" - echo " - homeassistant.yml (Home Assistant and accessories)" - echo " - productivity.yml (Nextcloud, Gitea, wikis)" - echo " - monitoring.yml (Grafana, Prometheus, etc.)" - echo " - utilities.yml (Backups, code editors, etc.)" - echo " - alternatives.yml (Portainer, Authentik - optional)" - echo "" - echo " 3. Access your dashboards:" - echo " 🏠 Homepage: https://home.${DOMAIN}" - echo " 📊 Homarr: https://homarr.${DOMAIN}" - echo "" - echo " 4. Configure services via the AI assistant in VS Code" - echo "" - echo "==========================================" - echo "" - log_info "For documentation, see: $REPO_DIR/docs/" - log_info "For troubleshooting, see: $REPO_DIR/docs/quick-reference.md" - echo "" -} - -#========================================== -# MAIN EXECUTION -#========================================== - # Check if running as root -if [ "$EUID" -ne 0 ]; then - log_error "Please run as root (use: sudo ./deploy-homelab.sh)" - exit 1 -fi - -# Get the actual user who invoked sudo -ACTUAL_USER="${SUDO_USER:-$USER}" -if [ "$ACTUAL_USER" = "root" ]; then - log_error "Please run this script with sudo, not as root user" +if [ "$EUID" -eq 0 ]; then + log_error "Please do NOT run this script as root or with sudo" + log_info "Run as: ./deploy-homelab.sh" exit 1 fi @@ -543,16 +42,221 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" log_info "AI-Homelab Deployment Script" -log_info "Running as user: $ACTUAL_USER" echo "" -# Execute deployment steps -validate_prerequisites -step_1_create_directories -step_2_create_networks -step_3_deploy_core -step_4_deploy_infrastructure -step_5_deploy_dashboards -step_6_prepare_additional_stacks -step_7_wait_for_dockge -show_final_summary +# Check if .env file exists +if [ ! -f "$REPO_DIR/.env" ]; then + log_error ".env file not found!" + log_info "Please create and configure your .env file first:" + echo " cd $REPO_DIR" + echo " cp .env.example .env" + echo " nano .env" + exit 1 +fi + +# Check if Docker is installed and running +if ! command -v docker &> /dev/null; then + log_error "Docker is not installed. Please run setup-homelab.sh first." + exit 1 +fi + +if ! docker info &> /dev/null; then + log_error "Docker daemon is not running or you don't have permission." + log_info "Try: sudo systemctl start docker" + log_info "Or log out and log back in for group changes to take effect" + exit 1 +fi + +log_success "Docker is available and running" +echo "" + +# Load environment variables for domain check +source "$REPO_DIR/.env" + +if [ -z "$DOMAIN" ]; then + log_error "DOMAIN is not set in .env file" + log_info "Please edit .env and set your DuckDNS domain" + exit 1 +fi + +log_info "Using domain: $DOMAIN" +echo "" + +# Step 1: Create required directories +log_info "Step 1/5: Creating required directories..." +mkdir -p /opt/stacks/core +mkdir -p /opt/stacks/infrastructure +mkdir -p /opt/dockge/data +log_success "Directories created" +echo "" + +# Step 2: Create Docker networks (if they don't exist) +log_info "Step 2/5: Creating Docker networks..." +docker network create homelab-network 2>/dev/null && log_success "Created homelab-network" || log_info "homelab-network already exists" +docker network create traefik-network 2>/dev/null && log_success "Created traefik-network" || log_info "traefik-network already exists" +docker network create media-network 2>/dev/null && log_success "Created media-network" || log_info "media-network already exists" +echo "" + +# Step 3: Deploy core infrastructure (DuckDNS, Traefik, Authelia, Gluetun) +log_info "Step 3/5: Deploying core infrastructure stack..." +log_info " - DuckDNS (Dynamic DNS)" +log_info " - Traefik (Reverse Proxy with SSL)" +log_info " - Authelia (Single Sign-On)" +log_info " - Gluetun (VPN Client)" +echo "" + +# Copy core stack files with overwrite checks +if [ -f "/opt/stacks/core/docker-compose.yml" ]; then + log_warning "docker-compose.yml already exists in /opt/stacks/core/" + log_info "Creating backup: docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S)" + cp /opt/stacks/core/docker-compose.yml /opt/stacks/core/docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S) +fi +cp "$REPO_DIR/docker-compose/core.yml" /opt/stacks/core/docker-compose.yml + +if [ -d "/opt/stacks/core/traefik" ]; then + log_warning "Traefik configuration already exists in /opt/stacks/core/" + log_info "Creating backup: traefik.backup.$(date +%Y%m%d_%H%M%S)" + mv /opt/stacks/core/traefik /opt/stacks/core/traefik.backup.$(date +%Y%m%d_%H%M%S) +fi +cp -r "$REPO_DIR/config-templates/traefik" /opt/stacks/core/ + +if [ -d "/opt/stacks/core/authelia" ]; then + log_warning "Authelia configuration already exists in /opt/stacks/core/" + log_info "Creating backup: authelia.backup.$(date +%Y%m%d_%H%M%S)" + mv /opt/stacks/core/authelia /opt/stacks/core/authelia.backup.$(date +%Y%m%d_%H%M%S) +fi +cp -r "$REPO_DIR/config-templates/authelia" /opt/stacks/core/ + +if [ -f "/opt/stacks/core/.env" ]; then + log_warning ".env already exists in /opt/stacks/core/" + log_info "Creating backup: .env.backup.$(date +%Y%m%d_%H%M%S)" + cp /opt/stacks/core/.env /opt/stacks/core/.env.backup.$(date +%Y%m%d_%H%M%S) +fi +cp "$REPO_DIR/.env" /opt/stacks/core/.env + +# Deploy core stack +cd /opt/stacks/core +docker compose up -d + +log_success "Core infrastructure deployed" +echo "" + +# Wait for Traefik to be ready +log_info "Waiting for Traefik to initialize..." +sleep 10 + +# Check if Traefik is healthy +if docker ps | grep -q "traefik.*Up"; then + log_success "Traefik is running" +else + log_warning "Traefik container check inconclusive, continuing..." +fi +echo "" + +# Step 4: Deploy infrastructure stack (Dockge and monitoring tools) +log_info "Step 4/5: Deploying infrastructure stack..." +log_info " - Dockge (Docker Compose Manager)" +log_info " - Portainer (Alternative Docker UI)" +log_info " - Pi-hole (DNS Ad Blocker)" +log_info " - Watchtower (Container Updates)" +log_info " - Dozzle (Log Viewer)" +log_info " - Glances (System Monitor)" +log_info " - Docker Proxy (Security)" +echo "" + +# Copy infrastructure stack +cp "$REPO_DIR/docker-compose/infrastructure.yml" /opt/stacks/infrastructure/docker-compose.yml +cp "$REPO_DIR/.env" /opt/stacks/infrastructure/.env + +# Deploy infrastructure stack +cd /opt/stacks/infrastructure +docker compose up -d + +log_success "Infrastructure stack deployed" +echo "" + +# Step 5: Wait for Dockge to be ready and open browser +log_info "Step 5/5: Waiting for Dockge web UI to be ready..." + +DOCKGE_URL="https://dockge.${DOMAIN}" +MAX_WAIT=60 # Maximum wait time in seconds +WAITED=0 + +# Function to check if Dockge is accessible +check_dockge() { + # Try to connect to Dockge (ignore SSL cert warnings for self-signed during startup) + curl -k -s -o /dev/null -w "%{http_code}" "$DOCKGE_URL" 2>/dev/null +} + +# Wait for Dockge to respond +while [ $WAITED -lt $MAX_WAIT ]; do + HTTP_CODE=$(check_dockge) + if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "401" ]; then + log_success "Dockge web UI is ready!" + break + fi + echo -n "." + sleep 2 + WAITED=$((WAITED + 2)) +done + +echo "" +echo "" + +if [ $WAITED -ge $MAX_WAIT ]; then + log_warning "Dockge did not respond within ${MAX_WAIT} seconds" + log_info "It may still be starting up. Check manually at: $DOCKGE_URL" +else + # Try to open browser + log_info "Opening Dockge in your browser..." + + # Detect and use available browser + if command -v xdg-open &> /dev/null; then + xdg-open "$DOCKGE_URL" &> /dev/null & + log_success "Browser opened" + elif command -v gnome-open &> /dev/null; then + gnome-open "$DOCKGE_URL" &> /dev/null & + log_success "Browser opened" + elif command -v firefox &> /dev/null; then + firefox "$DOCKGE_URL" &> /dev/null & + log_success "Browser opened" + elif command -v google-chrome &> /dev/null; then + google-chrome "$DOCKGE_URL" &> /dev/null & + log_success "Browser opened" + else + log_warning "No browser detected. Please manually open: $DOCKGE_URL" + fi +fi + +echo "" +echo "==========================================" +log_success "Deployment completed successfully!" +echo "==========================================" +echo "" +log_info "Access your services:" +echo "" +echo " 🚀 Dockge: $DOCKGE_URL" +echo " 🔒 Authelia: https://auth.${DOMAIN}" +echo " 🔀 Traefik: https://traefik.${DOMAIN}" +echo "" +log_info "Next steps:" +echo "" +echo " 1. Log in to Dockge using your Authelia credentials" +echo " (configured in /opt/stacks/core/authelia/users_database.yml)" +echo "" +echo " 2. Deploy additional stacks through Dockge's web UI:" +echo " - dashboards.yml (Homepage, Homarr)" +echo " - media.yml (Plex, Jellyfin, Sonarr, Radarr, etc.)" +echo " - media-extended.yml (Readarr, Lidarr, etc.)" +echo " - homeassistant.yml (Home Assistant and accessories)" +echo " - productivity.yml (Nextcloud, Gitea, wikis)" +echo " - monitoring.yml (Grafana, Prometheus, etc.)" +echo " - utilities.yml (Backups, code editors, etc.)" +echo "" +echo " 3. Configure services via the AI assistant in VS Code" +echo "" +echo "==========================================" +echo "" +log_info "For documentation, see: $REPO_DIR/docs/" +log_info "For troubleshooting, see: $REPO_DIR/docs/quick-reference.md" +echo "" diff --git a/scripts/setup-homelab.sh b/scripts/setup-homelab.sh index 7fddbf0..a0113e1 100755 --- a/scripts/setup-homelab.sh +++ b/scripts/setup-homelab.sh @@ -1,44 +1,15 @@ #!/bin/bash # AI-Homelab First-Run Setup Script # This script prepares a fresh Debian installation for homelab deployment -# Run as: sudo ./setup-homelab.sh [--yes] -# Options: -# --yes Skip all confirmation prompts (for automated deployments) +# Run as: sudo ./setup-homelab.sh set -e # Exit on error -# Parse command line arguments -AUTO_YES=false -for arg in "$@"; do - case $arg in - --yes|-y) - AUTO_YES=true - shift - ;; - --help|-h) - echo "Usage: sudo ./setup-homelab.sh [OPTIONS]" - echo "" - echo "Options:" - echo " --yes, -y Skip all confirmation prompts" - echo " --help, -h Show this help message" - exit 0 - ;; - *) - echo "Unknown option: $arg" - echo "Run with --help for usage information" - exit 1 - ;; - esac -done - # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' -CYAN='\033[0;36m' -MAGENTA='\033[0;35m' -BOLD='\033[1m' NC='\033[0m' # No Color # Log functions @@ -58,888 +29,268 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1" } -log_progress() { - echo -e "${CYAN}[PROGRESS]${NC} $1" -} - -# Colorized prompt function -prompt_user() { - local prompt_text="$1" - local default="${2:-}" - echo -e "${MAGENTA}${BOLD}[PROMPT]${NC} ${prompt_text}" - if [ -n "$default" ]; then - echo -e "${CYAN}(default: $default)${NC}" - fi -} - -# Confirmation function that respects --yes flag -confirm() { - local prompt="$1" - if [ "$AUTO_YES" = true ]; then - log_info "Auto-confirmed: $prompt" - return 0 - fi - prompt_user "$prompt" - read -p "Continue? (y/N): " -n 1 -r - echo "" - [[ $REPLY =~ ^[Yy]$ ]] -} - -# Check if running with elevated privileges +# Check if running as root if [ "$EUID" -ne 0 ]; then - log_error "Please run with sudo: sudo ./setup-homelab.sh" + log_error "Please run as root (use: sudo ./setup-homelab.sh)" exit 1 fi # Get the actual user who invoked sudo ACTUAL_USER="${SUDO_USER:-$USER}" - -#========================================== -# STEP FUNCTIONS -#========================================== - -# Handle root user scenario - grant sudo and exit -handle_root_user() { - log_info "Running as root - checking for non-root users..." - echo "" - - # Find non-root users with home directories and UID >= 1000 - AVAILABLE_USERS=$(awk -F: '$3 >= 1000 && $3 < 65534 && $1 != "nobody" {print $1}' /etc/passwd) - - if [ -z "$AVAILABLE_USERS" ]; then - log_error "No non-root user found on this system" - log_info "Please create a user first:" - log_info " adduser " - exit 1 - fi - - # If only one user, use that one - USER_COUNT=$(echo "$AVAILABLE_USERS" | wc -l) - if [ "$USER_COUNT" -eq 1 ]; then - TARGET_USER="$AVAILABLE_USERS" - log_info "Found user: $TARGET_USER" - else - # Multiple users found, let root choose - log_info "Multiple users found:" - echo "$AVAILABLE_USERS" | nl - echo "" - read -p "Enter username to grant sudo access: " TARGET_USER - - # Validate the entered username - if ! echo "$AVAILABLE_USERS" | grep -q "^${TARGET_USER}$"; then - log_error "Invalid username: $TARGET_USER" - exit 1 - fi - fi - - # Check if user already has sudo - if groups "$TARGET_USER" | grep -q '\bsudo\b'; then - log_success "User $TARGET_USER already has sudo access" - else - # Confirm before granting sudo - echo "" - read -p "Grant sudo access to $TARGET_USER? (y/N): " CONFIRM - if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then - usermod -aG sudo "$TARGET_USER" - log_success "Sudo access granted to $TARGET_USER" - else - log_error "Cannot proceed without sudo access" - exit 1 - fi - fi - - # Exit with instructions - echo "" - echo "==========================================" - log_success "Setup preparation complete!" - echo "==========================================" - echo "" - log_info "Next steps:" - echo "" - log_info " 1. Log in as $TARGET_USER:" - echo " su - $TARGET_USER" - echo "" - log_info " 2. Navigate to the AI-Homelab directory and run setup again:" - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - REPO_DIR="$(dirname "$SCRIPT_DIR")" - echo " cd $REPO_DIR" - echo " sudo ./scripts/setup-homelab.sh" - echo "" - log_warning "Note: You may need to log out and back in for sudo access to take effect" - echo "" - exit 0 -} - -step_0_preflight_checks() { - log_info "Step 0/$STEPS_TOTAL: Running pre-flight checks..." - log_progress "Starting setup process" - - # Check internet connectivity - if ! ping -c 1 -W 2 8.8.8.8 &> /dev/null && ! ping -c 1 -W 2 1.1.1.1 &> /dev/null; then - log_error "No internet connectivity detected" - log_info "Internet access is required for:" - log_info " - Installing packages" - log_info " - Downloading Docker images" - log_info " - Accessing Docker Hub" - exit 1 - fi - - # Check disk space (require at least 5GB free) - AVAILABLE_SPACE=$(df / | tail -1 | awk '{print $4}') - REQUIRED_SPACE=5000000 # 5GB in KB - if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then - log_error "Insufficient disk space on root partition" - log_info "Available: $(($AVAILABLE_SPACE / 1024 / 1024))GB" - log_info "Required: $(($REQUIRED_SPACE / 1024 / 1024))GB" - exit 1 - fi - - log_success "Pre-flight checks passed" - log_info "Internet: Connected" - log_info "Disk space: $(($AVAILABLE_SPACE / 1024 / 1024))GB available" - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_1_update_system() { - log_info "Step 1/$STEPS_TOTAL: Updating system packages..." - apt-get update && apt-get upgrade -y - log_success "System updated successfully" - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_2_install_packages() { - log_info "Step 2/$STEPS_TOTAL: Installing required packages..." - apt-get install -y \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg \ - lsb-release \ - software-properties-common \ - git \ - openssh-server \ - sudo \ - pciutils \ - net-tools \ - ufw - - log_success "Required packages installed" - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_3_install_docker() { - log_info "Step 3/$STEPS_TOTAL: Installing Docker..." - if command -v docker &> /dev/null && docker --version &> /dev/null && docker compose version &> /dev/null 2>&1; then - log_warning "Docker and Docker Compose are already installed ($(docker --version), $(docker compose version))" - else - # Add Docker's official GPG key - install -m 0755 -d /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc - chmod a+r /etc/apt/keyrings/docker.asc - - # Add the repository to Apt sources - echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ - tee /etc/apt/sources.list.d/docker.list > /dev/null - - # Update and install Docker - apt-get update - apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - - log_success "Docker installed successfully ($(docker --version), $(docker compose version))" - fi - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_4_configure_user_groups() { - log_info "Step 4/$STEPS_TOTAL: Configuring user groups..." - - # Add user to sudo group if not already - if groups "$ACTUAL_USER" | grep -q '\bsudo\b'; then - log_warning "User $ACTUAL_USER is already in sudo group" - else - usermod -aG sudo "$ACTUAL_USER" - log_success "User $ACTUAL_USER added to sudo group" - fi - - # Add user to docker group - if groups "$ACTUAL_USER" | grep -q '\bdocker\b'; then - log_warning "User $ACTUAL_USER is already in docker group" - else - usermod -aG docker "$ACTUAL_USER" - log_success "User $ACTUAL_USER added to docker group" - fi - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_5_configure_firewall() { - log_info "Step 5/$STEPS_TOTAL: Configuring firewall..." - # Enable UFW if not already enabled - if ufw status | grep -q "Status: active"; then - log_warning "Firewall is already active" - else - ufw --force enable - log_success "Firewall enabled" - fi - - # Allow SSH if not already allowed - if ufw status | grep -q "22/tcp"; then - log_warning "SSH port 22 is already allowed" - else - ufw allow ssh - log_success "SSH port allowed in firewall" - fi - - # Allow HTTP/HTTPS for web services - ufw allow 80/tcp - ufw allow 443/tcp - log_success "HTTP/HTTPS ports allowed in firewall" - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_6_configure_ssh() { - log_info "Step 6/$STEPS_TOTAL: Configuring SSH server..." - systemctl enable ssh - systemctl start ssh - - # Check if SSH is running - if systemctl is-active --quiet ssh; then - SSH_PORT=$(grep "^Port" /etc/ssh/sshd_config | awk '{print $2}') - SSH_PORT=${SSH_PORT:-22} - log_success "SSH server is running on port $SSH_PORT" - else - log_warning "SSH server failed to start, check configuration" - fi - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_7_generate_authelia_secrets() { - log_info "Step 7/$STEPS_TOTAL: Generating Authelia authentication secrets..." - echo "" - - # Validate Docker is available for password hash generation - if ! docker info &> /dev/null 2>&1; then - log_error "Docker is not available for password hash generation" - log_info "Docker must be running to generate Authelia password hashes." - log_info "Please ensure:" - log_info " 1. Docker daemon is started: sudo systemctl start docker" - log_info " 2. User can access Docker: docker ps" - log_info " 3. Log out and log back in if recently added to docker group" - echo "" - log_info "After fixing, re-run: sudo ./setup-homelab.sh" - exit 1 - fi - - log_success "Docker is available for password operations" - echo "" - - # Check if .env file exists in the repo - REPO_ENV_FILE="$REPO_DIR/.env" - if [ ! -f "$REPO_ENV_FILE" ]; then - log_error ".env file not found at $REPO_ENV_FILE" - log_info "Please create .env file from .env.example first" - exit 1 - fi - - # Load and validate essential environment variables - log_info "Validating environment variables..." - DOMAIN=$(get_env_value "DOMAIN" "") - echo "DEBUG: DOMAIN='$DOMAIN'" - if is_placeholder "$DOMAIN" || [ -z "$DOMAIN" ]; then - if [ "$AUTO_YES" = true ]; then - log_error "DOMAIN not set in .env and running in --yes mode" - log_info "Please set DOMAIN in .env file" - exit 1 - else - prompt_user "Enter your DuckDNS domain (e.g., yourname.duckdns.org)" - read -p "> " DOMAIN - fi - escaped_domain=$(printf '%s\n' "$DOMAIN" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^DOMAIN=.*|DOMAIN=$escaped_domain|" "$REPO_ENV_FILE" - fi - - SERVER_IP=$(get_env_value "SERVER_IP" "") - if is_placeholder "$SERVER_IP" || [ -z "$SERVER_IP" ]; then - # Try to detect server IP - DETECTED_IP=$(hostname -I | awk '{print $1}') - if [ -n "$DETECTED_IP" ]; then - SERVER_IP="$DETECTED_IP" - log_info "Detected server IP: $SERVER_IP" - else - if [ "$AUTO_YES" = true ]; then - log_error "SERVER_IP not set and could not detect" - exit 1 - else - prompt_user "Enter your server IP address" - read -p "> " SERVER_IP - fi - fi - escaped_ip=$(printf '%s\n' "$SERVER_IP" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^SERVER_IP=.*|SERVER_IP=$escaped_ip|" "$REPO_ENV_FILE" - fi - -DUCKDNS_TOKEN=$(get_env_value "DUCKDNS_TOKEN" "") -echo "DEBUG: DUCKDNS_TOKEN='$DUCKDNS_TOKEN'" -if is_placeholder "$DUCKDNS_TOKEN" || [ -z "$DUCKDNS_TOKEN" ]; then - if [ "$AUTO_YES" = true ]; then - log_error "DUCKDNS_TOKEN not set in .env and running in --yes mode" - log_info "Please set DUCKDNS_TOKEN in .env file" - exit 1 - else - prompt_user "Enter your DuckDNS token" - read -p "> " DUCKDNS_TOKEN - fi - escaped_token=$(printf '%s\n' "$DUCKDNS_TOKEN" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^DUCKDNS_TOKEN=.*|DUCKDNS_TOKEN=$escaped_token|" "$REPO_ENV_FILE" -fi - -DUCKDNS_SUBDOMAINS=$(get_env_value "DUCKDNS_SUBDOMAINS" "") -if is_placeholder "$DUCKDNS_SUBDOMAINS" || [ -z "$DUCKDNS_SUBDOMAINS" ]; then - if [ "$AUTO_YES" = true ]; then - log_error "DUCKDNS_SUBDOMAINS not set in .env and running in --yes mode" - log_info "Please set DUCKDNS_SUBDOMAINS in .env file" - exit 1 - else - prompt_user "Enter your DuckDNS subdomain (without .duckdns.org)" - read -p "> " DUCKDNS_SUBDOMAINS - fi - escaped_subdomains=$(printf '%s\n' "$DUCKDNS_SUBDOMAINS" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^DUCKDNS_SUBDOMAINS=.*|DUCKDNS_SUBDOMAINS=$escaped_subdomains|" "$REPO_ENV_FILE" -fi - -# Load other variables with defaults -PUID=$(get_env_value "PUID" "1000") -PGID=$(get_env_value "PGID" "1000") -TZ=$(get_env_value "TZ" "America/New_York") - -# Prompt for default credentials if placeholders -DEFAULT_USER_VALUE=$(get_env_value "DEFAULT_USER" "admin") -if is_placeholder "$DEFAULT_USER_VALUE"; then - if [ "$AUTO_YES" = true ]; then - DEFAULT_USER_VALUE="admin" - else - prompt_user "Enter default username" "admin" - read -p "> " DEFAULT_USER_VALUE - DEFAULT_USER_VALUE=${DEFAULT_USER_VALUE:-admin} - fi - escaped_default_user=$(printf '%s\n' "$DEFAULT_USER_VALUE" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^DEFAULT_USER=.*|DEFAULT_USER=$escaped_default_user|" "$REPO_ENV_FILE" -fi - -DEFAULT_EMAIL_VALUE=$(get_env_value "DEFAULT_EMAIL" "your-email@example.com") -if is_placeholder "$DEFAULT_EMAIL_VALUE"; then - if [ "$AUTO_YES" = true ]; then - log_error "DEFAULT_EMAIL not set in .env and running in --yes mode" - exit 1 - else - prompt_user "Enter default email address" - read -p "> " DEFAULT_EMAIL_VALUE - fi - escaped_default_email=$(printf '%s\n' "$DEFAULT_EMAIL_VALUE" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^DEFAULT_EMAIL=.*|DEFAULT_EMAIL=$escaped_default_email|" "$REPO_ENV_FILE" -fi - -DEFAULT_PASSWORD_VALUE=$(get_env_value "DEFAULT_PASSWORD" "YourStrongPassword123!") -if is_placeholder "$DEFAULT_PASSWORD_VALUE"; then - if [ "$AUTO_YES" = true ]; then - log_warning "Default password not set in .env, generating random password" - DEFAULT_PASSWORD_VALUE=$(openssl rand -base64 12) - log_info "Generated password: $DEFAULT_PASSWORD_VALUE" - else - while true; do - read -sp "Enter default password: " DEFAULT_PASSWORD_VALUE - echo "" - read -sp "Confirm default password: " DEFAULT_PASSWORD_CONFIRM - echo "" - - if [ "$DEFAULT_PASSWORD_VALUE" = "$DEFAULT_PASSWORD_CONFIRM" ]; then - if [ ${#DEFAULT_PASSWORD_VALUE} -lt 8 ]; then - log_warning "Password should be at least 8 characters long" - continue - fi - break - else - log_warning "Passwords do not match, please try again" - fi - done - fi - escaped_default_password=$(printf '%s\n' "$DEFAULT_PASSWORD_VALUE" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^DEFAULT_PASSWORD=.*|DEFAULT_PASSWORD=$escaped_default_password|" "$REPO_ENV_FILE" -fi - - # Get admin user from .env or default - ADMIN_USER=$(get_env_value "AUTHELIA_ADMIN_USER" "admin") - if is_placeholder "$ADMIN_USER"; then - ADMIN_USER="$DEFAULT_USER_VALUE" - log_info "Using default username for Authelia: $ADMIN_USER" - fi - - # Get admin email from .env or default - ADMIN_EMAIL=$(get_env_value "AUTHELIA_ADMIN_EMAIL" "your-email@example.com") - if is_placeholder "$ADMIN_EMAIL"; then - ADMIN_EMAIL="$DEFAULT_EMAIL_VALUE" - log_info "Using default email for Authelia: $ADMIN_EMAIL" - fi - - ADMIN_PASSWORD=$(get_env_value "AUTHELIA_ADMIN_PASSWORD" "YourStrongPassword123!") - if is_placeholder "$ADMIN_PASSWORD"; then - ADMIN_PASSWORD="$DEFAULT_PASSWORD_VALUE" - log_info "Using default password for Authelia" - fi - - # Generate password hash using Docker - log_info "Generating password hash (this may take 30-60 seconds)..." - log_info "Pulling Authelia image if not already present..." - - # Pull image first to show progress - if ! docker pull authelia/authelia:4.37 2>&1 | grep -E '(Pulling|Downloaded|Already exists|Status)'; then - log_error "Failed to pull Authelia Docker image" - log_info "Please check:" - log_info " 1. Internet connectivity: ping docker.io" - log_info " 2. Docker Hub access: docker search authelia" - log_info " 3. Disk space: df -h" - exit 1 - fi - - echo "" - log_info "Generating password hash..." - - # Generate hash and write DIRECTLY to file - timeout 60 docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | \ - grep -oP 'Digest: \K\$argon2.*' > /tmp/authelia_password_hash.tmp - - HASH_EXIT_CODE=$? - - if [ $HASH_EXIT_CODE -eq 124 ]; then - log_error "Password hash generation timed out after 60 seconds" - exit 1 - elif [ $HASH_EXIT_CODE -ne 0 ] || [ ! -s /tmp/authelia_password_hash.tmp ]; then - log_error "Failed to generate password hash" - exit 1 - fi - - chmod 600 /tmp/authelia_password_hash.tmp - log_success "Password hash generated successfully" - - log_success "Admin user configured: $ADMIN_USER" - log_success "Password hash generated and will be applied during deployment" - - # Store credentials - mkdir -p /opt/stacks/.setup-temp - { - echo "ADMIN_USER=$ADMIN_USER" - echo "ADMIN_EMAIL=$ADMIN_EMAIL" - echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" - } > /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - chmod 600 /opt/stacks/.setup-temp/authelia_admin_credentials.tmp - - cp /tmp/authelia_password_hash.tmp /opt/stacks/.setup-temp/authelia_password_hash.tmp - chmod 600 /opt/stacks/.setup-temp/authelia_password_hash.tmp - - # Save to .env file for persistence - log_info "Saving credentials to .env file for persistence..." - # Escape | in variables for sed and remove newlines - escaped_user=$(printf '%s\n' "$ADMIN_USER" | sed 's/|/\\|/g' | tr -d '\n') - escaped_email=$(printf '%s\n' "$ADMIN_EMAIL" | sed 's/|/\\|/g' | tr -d '\n') - escaped_password=$(printf '%s\n' "$ADMIN_PASSWORD" | sed 's/|/\\|/g' | tr -d '\n') - sed -i "s|^AUTHELIA_ADMIN_USER=.*|AUTHELIA_ADMIN_USER=$escaped_user|" "$REPO_ENV_FILE" - sed -i "s|^AUTHELIA_ADMIN_EMAIL=.*|AUTHELIA_ADMIN_EMAIL=$escaped_email|" "$REPO_ENV_FILE" - sed -i "s|^AUTHELIA_ADMIN_PASSWORD=.*|AUTHELIA_ADMIN_PASSWORD=$escaped_password|" "$REPO_ENV_FILE" - log_success "Credentials saved to .env file" - - # Check and generate Authelia secrets if needed - log_info "Checking Authelia secrets..." - CURRENT_JWT=$(get_env_value "AUTHELIA_JWT_SECRET" "") - CURRENT_SESSION=$(get_env_value "AUTHELIA_SESSION_SECRET" "") - CURRENT_ENCRYPTION=$(get_env_value "AUTHELIA_STORAGE_ENCRYPTION_KEY" "") - - if is_placeholder "$CURRENT_JWT" || [ -z "$CURRENT_JWT" ] || \ - is_placeholder "$CURRENT_SESSION" || [ -z "$CURRENT_SESSION" ] || \ - is_placeholder "$CURRENT_ENCRYPTION" || [ -z "$CURRENT_ENCRYPTION" ]; then - log_info "Authelia secrets not found or are placeholders, generating new ones..." - generate_new_secrets - else - log_info "Authelia secrets already configured" - fi - - log_info "Credentials saved for deployment script" - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_8_create_directories() { - log_info "Step 8/$STEPS_TOTAL: Creating directory structure..." - mkdir -p /opt/stacks - mkdir -p /opt/dockge/data - mkdir -p /mnt/media/{movies,tv,music,books,photos} - mkdir -p /mnt/downloads/{complete,incomplete} - mkdir -p /mnt/backups - mkdir -p /mnt/surveillance - mkdir -p /mnt/git - - # Set ownership - chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks - chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge - chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/media - chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/downloads - chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/backups - chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/surveillance - chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/git - - log_success "Directory structure created" - - # Restore SSL certificates if available - if [ -f "$REPO_DIR/acme.json" ]; then - mkdir -p /opt/stacks/core/traefik - cp "$REPO_DIR/acme.json" /opt/stacks/core/traefik/acme.json - chmod 600 /opt/stacks/core/traefik/acme.json - chown "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/traefik/acme.json - log_success "SSL certificates restored from repository" - else - log_info "No SSL certificates found in repository (first-time setup)" - fi - - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps" - echo "" -} - -step_9_create_networks() { - log_info "Step 9/$STEPS_TOTAL: Creating Docker networks..." - su - "$ACTUAL_USER" -c "docker network create homelab-network 2>/dev/null || true" - su - "$ACTUAL_USER" -c "docker network create traefik-network 2>/dev/null || true" - su - "$ACTUAL_USER" -c "docker network create media-network 2>/dev/null || true" - su - "$ACTUAL_USER" -c "docker network create dockerproxy-network 2>/dev/null || true" - log_success "Docker networks created" - STEPS_COMPLETED=$((STEPS_COMPLETED + 1)) - log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps (core setup)" - echo "" -} - -step_10_nvidia_drivers() { - log_info "Step 10/$STEPS_TOTAL (Optional): Checking for NVIDIA GPU..." - - # Detect NVIDIA GPU - if lspci | grep -i nvidia > /dev/null; then - log_info "NVIDIA GPU detected:" - GPU_INFO=$(lspci | grep -i nvidia) - echo "$GPU_INFO" - echo "" - - # Check if NVIDIA drivers are already installed - if nvidia-smi &> /dev/null; then - log_warning "NVIDIA drivers are already installed" - nvidia-smi - NVIDIA_INSTALLED=true - NVIDIA_REBOOT_NEEDED=false - else - log_warning "NVIDIA GPU detected but drivers not installed" - echo "" - - if confirm "Install NVIDIA drivers now?"; then - install_nvidia_drivers "$GPU_INFO" - else - log_info "Skipping NVIDIA driver installation" - log_info "To install later, visit: https://www.nvidia.com/Download/index.aspx" - NVIDIA_INSTALLED=false - NVIDIA_REBOOT_NEEDED=false - fi - fi - echo "" - else - log_info "No NVIDIA GPU detected, skipping driver installation" - NVIDIA_REBOOT_NEEDED=false - echo "" - fi -} - -show_final_summary() { - echo "" - echo "==========================================" - log_success "AI-Homelab setup completed successfully!" - log_progress "All $STEPS_TOTAL core steps completed!" - echo "==========================================" - echo "" - - if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then - log_warning "⚠️ REBOOT REQUIRED FOR NVIDIA DRIVERS ⚠️" - echo "" - log_info "NVIDIA drivers were installed and require a system reboot." - log_info "Please reboot before running the deployment script." - echo "" - echo " After reboot, verify drivers with: nvidia-smi" - echo "" - echo "==========================================" - echo "" - fi - - log_info "Next steps:" - echo "" - if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then - echo " 1. REBOOT YOUR SYSTEM for NVIDIA drivers to load" - echo " Run: sudo reboot" - echo "" - echo " 2. After reboot, deploy your homelab services" - else - echo " 1. Deploy your homelab services" - fi - echo "" - echo "==========================================" - echo "" - log_info "Setup complete!" - echo "" - - # Instructions for deployment - if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then - log_info "Please reboot your system for NVIDIA drivers, then run:" - else - log_info "Next step - deploy your homelab services:" - fi - echo "" - echo " cd ~/AI-Homelab" - echo " sudo ./scripts/deploy-homelab.sh" - echo "" -} - -# Helper function to check if a value is a placeholder -is_placeholder() { - local value="$1" - case "$value" in - "your-generated-key"|"your-jwt-secret-here"|"generate-with-openssl-rand-hex-64"|"YourStrongPassword123!"|"your-email@example.com"|"your-subdomain.duckdns.org"|"192.168.x.x"|"admin"|"postgres"|"your-username"|"your-duckdns-token"|"your-subdomain"|"") - return 0 # true, it's a placeholder - ;; - *) - return 1 # false, it's a real value - ;; - esac -} - -# Helper function to get value from .env, using default if placeholder -get_env_value() { - local var_name="$1" - local default_value="$2" - local value - value=$(grep "^${var_name}=" "$REPO_ENV_FILE" 2>/dev/null | cut -d'=' -f2- | sed 's/#.*//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - if [ -n "$value" ] && ! is_placeholder "$value"; then - echo "$value" - else - echo "$default_value" - fi -} - -# Helper function to generate secrets -generate_secret() { - openssl rand -hex 64 -} - -# Helper function to generate new Authelia secrets -generate_new_secrets() { - echo "DEBUG: Starting generate_new_secrets" - log_info "Generating new JWT secret..." - JWT_SECRET=$(generate_secret) - echo "DEBUG: JWT_SECRET generated" - log_info "Generating new session secret..." - SESSION_SECRET=$(generate_secret) - echo "DEBUG: SESSION_SECRET generated" - log_info "Generating new storage encryption key..." - ENCRYPTION_KEY=$(generate_secret) - echo "DEBUG: ENCRYPTION_KEY generated" - - # Update .env file - escaped_jwt=$(printf '%s\n' "$JWT_SECRET" | sed 's/|/\\|/g') - escaped_session=$(printf '%s\n' "$SESSION_SECRET" | sed 's/|/\\|/g') - escaped_encryption=$(printf '%s\n' "$ENCRYPTION_KEY" | sed 's/|/\\|/g') - sed -i "s|^AUTHELIA_JWT_SECRET=.*|AUTHELIA_JWT_SECRET=$escaped_jwt|" "$REPO_ENV_FILE" - sed -i "s|^AUTHELIA_SESSION_SECRET=.*|AUTHELIA_SESSION_SECRET=$escaped_session|" "$REPO_ENV_FILE" - sed -i "s|^AUTHELIA_STORAGE_ENCRYPTION_KEY=.*|AUTHELIA_STORAGE_ENCRYPTION_KEY=$escaped_encryption|" "$REPO_ENV_FILE" - - log_success "New secrets generated and saved to .env" -} - -# NVIDIA Driver Installation Function -install_nvidia_drivers() { - local GPU_INFO="$1" - - log_info "Installing NVIDIA drivers using official installer..." - echo "" - - # Extract GPU model for driver selection - GPU_MODEL=$(echo "$GPU_INFO" | grep -oP 'NVIDIA.*' | head -1) - log_info "GPU Model: $GPU_MODEL" - - # Determine recommended driver version - log_info "Determining recommended driver version..." - - # Install prerequisites for NVIDIA installer - log_info "Installing build prerequisites..." - apt-get install -y build-essential linux-headers-$(uname -r) pkg-config libglvnd-dev 2>&1 | tee /tmp/nvidia-prereq.log - - # Disable nouveau driver - log_info "Disabling nouveau driver..." - if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then - cat > /etc/modprobe.d/blacklist-nouveau.conf << EOF -blacklist nouveau -options nouveau modeset=0 -EOF - update-initramfs -u - log_success "Nouveau driver blacklisted (reboot required)" - fi - - # Download latest NVIDIA driver - NVIDIA_VERSION="550.127.05" # Latest Production Branch as of script creation - DRIVER_URL="https://us.download.nvidia.com/XFree86/Linux-x86_64/${NVIDIA_VERSION}/NVIDIA-Linux-x86_64-${NVIDIA_VERSION}.run" - DRIVER_FILE="/tmp/NVIDIA-Linux-x86_64-${NVIDIA_VERSION}.run" - - log_info "Downloading NVIDIA driver version ${NVIDIA_VERSION}..." - log_info "URL: $DRIVER_URL" - - if curl -fL -o "$DRIVER_FILE" "$DRIVER_URL" 2>&1 | tee /tmp/nvidia-download.log; then - log_success "Driver downloaded successfully" - chmod +x "$DRIVER_FILE" - - log_info "Running NVIDIA installer (this may take several minutes)..." - log_info "The installer will compile kernel modules and configure the driver." - echo "" - - # Run NVIDIA installer with appropriate flags - if "$DRIVER_FILE" --silent --dkms --no-questions 2>&1 | tee /tmp/nvidia-install.log; then - log_success "NVIDIA drivers installed successfully" - NVIDIA_INSTALLED=true - NVIDIA_REBOOT_NEEDED=true - else - log_error "NVIDIA driver installation failed" - log_info "Installation log saved to: /tmp/nvidia-install.log" - log_info "Common issues:" - log_info " - Secure Boot may need to be disabled in BIOS" - log_info " - You may need to sign the kernel modules for Secure Boot" - log_info " - Try running installer manually: sudo $DRIVER_FILE" - log_info "" - log_info "For latest driver, visit: https://www.nvidia.com/Download/index.aspx" - NVIDIA_INSTALLED=false - NVIDIA_REBOOT_NEEDED=true # Still need reboot for nouveau blacklist - fi - - # Cleanup installer - rm -f "$DRIVER_FILE" - else - log_error "Failed to download NVIDIA driver" - log_info "Download log saved to: /tmp/nvidia-download.log" - log_info "You can download and install manually from:" - log_info " https://www.nvidia.com/Download/index.aspx" - NVIDIA_INSTALLED=false - NVIDIA_REBOOT_NEEDED=false - fi - - # Install NVIDIA Container Toolkit if drivers installed successfully - if [ "$NVIDIA_INSTALLED" = true ]; then - if command -v nvidia-container-runtime &> /dev/null; then - log_warning "NVIDIA Container Toolkit is already installed" - else - log_info "Installing NVIDIA Container Toolkit..." - - # Install NVIDIA Container Toolkit (with error handling) - { - curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg && \ - curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ - sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ - tee /etc/apt/sources.list.d/nvidia-container-toolkit.list && \ - apt-get update && \ - apt-get install -y nvidia-container-toolkit && \ - nvidia-ctk runtime configure --runtime=docker && \ - systemctl restart docker - } 2>&1 | tee /tmp/nvidia-toolkit-install.log - - if [ ${PIPESTATUS[0]} -eq 0 ]; then - log_success "NVIDIA Container Toolkit installed and configured" - else - log_error "NVIDIA Container Toolkit installation failed" - log_info "Installation log saved to: /tmp/nvidia-toolkit-install.log" - log_info "Docker will work without GPU support" - log_info "You can try installing manually later from:" - log_info " https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html" - fi - fi - fi -} - -#========================================== -# MAIN EXECUTION -#========================================== - -# Add trap for cleanup on error -cleanup_on_error() { - local exit_code=$? - if [ $exit_code -ne 0 ]; then - log_error "Script failed with exit code: $exit_code" - echo "" - log_info "Partial setup may have occurred. To resume:" - log_info " 1. Review error messages above" - log_info " 2. Fix the issue if possible" - log_info " 3. Re-run: sudo ./setup-homelab.sh" - echo "" - log_info "The script is designed to be idempotent (safe to re-run)" - fi -} - -trap cleanup_on_error EXIT - -# Validate script is running from correct location -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(dirname "$SCRIPT_DIR")" -if [ ! -f "$REPO_DIR/.env.example" ] || [ ! -d "$REPO_DIR/docker-compose" ]; then - log_error "This script must be run from the AI-Homelab repository" - log_info "Expected location: AI-Homelab/scripts/setup-homelab.sh" - log_info "Current location: $SCRIPT_DIR" - echo "" - log_info "Please:" - log_info " 1. Clone the repository: git clone https://github.com/kelinfoxy/AI-Homelab.git" - log_info " 2. Enter the directory: cd AI-Homelab" - log_info " 3. Run: sudo ./scripts/setup-homelab.sh" +if [ "$ACTUAL_USER" = "root" ]; then + log_error "Please run this script with sudo, not as root user" exit 1 fi -# If running as root (not via sudo), handle sudo grant and exit -if [ "$ACTUAL_USER" = "root" ]; then - handle_root_user +log_info "Setting up AI-Homelab for user: $ACTUAL_USER" +echo "" + +# Step 1: System Update +log_info "Step 1/9: Updating system packages..." +apt-get update && apt-get upgrade -y +log_success "System updated successfully" +echo "" + +# Step 2: Install Required Packages +log_info "Step 2/9: Installing required packages..." +# Update package list first to avoid issues +apt-get update + +# Install packages with error handling +if ! apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release \ + git \ + openssh-server \ + sudo \ + pciutils \ + net-tools \ + ufw; then + log_warning "Some packages may have failed to install. Attempting software-properties-common separately..." + apt-get install -y software-properties-common || log_warning "software-properties-common installation failed - may need manual intervention" +else + # Try to install software-properties-common separately as it sometimes causes issues + apt-get install -y software-properties-common || log_warning "software-properties-common installation failed - continuing anyway" fi -log_info "Setting up AI-Homelab for user: $ACTUAL_USER" -if [ "$AUTO_YES" = true ]; then - log_info "Running in automated mode (--yes flag enabled)" +log_success "Required packages installed" +echo "" + +# Step 3: Install Docker +log_info "Step 3/9: Installing Docker..." +if command -v docker &> /dev/null && docker --version &> /dev/null; then + log_warning "Docker is already installed ($(docker --version))" +else + # Add Docker's official GPG key + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + + # Add the repository to Apt sources + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + + # Update and install Docker + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + log_success "Docker installed successfully ($(docker --version))" fi echo "" -# Progress tracking -STEPS_TOTAL=9 -STEPS_COMPLETED=0 +# Step 4: Configure User Groups +log_info "Step 4/9: Configuring user groups..." -# Execute setup steps in order -step_0_preflight_checks -step_1_update_system -step_2_install_packages -step_3_install_docker -step_4_configure_user_groups -step_5_configure_firewall -step_6_configure_ssh -step_7_generate_authelia_secrets -step_8_create_directories -step_9_create_networks -step_10_nvidia_drivers -show_final_summary +# Add user to sudo group if not already +if groups "$ACTUAL_USER" | grep -q '\bsudo\b'; then + log_warning "User $ACTUAL_USER is already in sudo group" +else + usermod -aG sudo "$ACTUAL_USER" + log_success "User $ACTUAL_USER added to sudo group" +fi +# Add user to docker group +if groups "$ACTUAL_USER" | grep -q '\bdocker\b'; then + log_warning "User $ACTUAL_USER is already in docker group" +else + usermod -aG docker "$ACTUAL_USER" + log_success "User $ACTUAL_USER added to docker group" +fi +echo "" + +# Step 5: Configure Firewall +log_info "Step 5/9: Configuring firewall..." +# Enable UFW if not already enabled +if ufw status | grep -q "Status: active"; then + log_warning "Firewall is already active" +else + ufw --force enable + log_success "Firewall enabled" +fi + +# Allow SSH if not already allowed +if ufw status | grep -q "22/tcp"; then + log_warning "SSH port 22 is already allowed" +else + ufw allow ssh + log_success "SSH port allowed in firewall" +fi + +# Allow HTTP/HTTPS for web services +ufw allow 80/tcp +ufw allow 443/tcp +log_success "HTTP/HTTPS ports allowed in firewall" +echo "" + +# Step 6: Configure SSH +log_info "Step 6/9: Configuring SSH server..." +systemctl enable ssh +systemctl start ssh + +# Check if SSH is running +if systemctl is-active --quiet ssh; then + SSH_PORT=$(grep "^Port" /etc/ssh/sshd_config | awk '{print $2}') + SSH_PORT=${SSH_PORT:-22} + log_success "SSH server is running on port $SSH_PORT" +else + log_warning "SSH server failed to start, check configuration" +fi +echo "" + +# Step 7: Detect and Install NVIDIA Drivers (if applicable) +log_info "Step 7/9: Checking for NVIDIA GPU..." + +# Detect NVIDIA GPU +if lspci | grep -i nvidia > /dev/null; then + log_info "NVIDIA GPU detected:" + lspci | grep -i nvidia + echo "" + + # Check if NVIDIA drivers are already installed + if nvidia-smi &> /dev/null; then + log_warning "NVIDIA drivers are already installed" + NVIDIA_INSTALLED=true + else + log_info "Installing NVIDIA drivers..." + # Install NVIDIA driver (non-interactive) + apt-get install -y nvidia-driver + log_success "NVIDIA drivers installed" + NVIDIA_INSTALLED=false + fi + + # Check if NVIDIA Container Toolkit is installed + if command -v nvidia-container-runtime &> /dev/null; then + log_warning "NVIDIA Container Toolkit is already installed" + else + log_info "Installing NVIDIA Container Toolkit..." + # Install NVIDIA Container Toolkit + curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg + curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ + sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ + tee /etc/apt/sources.list.d/nvidia-container-toolkit.list + + apt-get update + apt-get install -y nvidia-container-toolkit + + # Configure Docker to use NVIDIA runtime + nvidia-ctk runtime configure --runtime=docker + systemctl restart docker + + log_success "NVIDIA Container Toolkit installed and configured" + fi + + if [ "$NVIDIA_INSTALLED" = false ]; then + log_warning "NVIDIA drivers were installed. A reboot may be required for changes to take effect." + fi + echo "" +else + log_info "No NVIDIA GPU detected, skipping driver installation" + echo "" +fi + +# Step 8: Create Directory Structure +log_info "Step 8/9: Creating directory structure..." +mkdir -p /opt/stacks +mkdir -p /opt/dockge/data +mkdir -p /mnt/media/{movies,tv,music,books,photos} +mkdir -p /mnt/downloads/{complete,incomplete} +mkdir -p /mnt/backups +mkdir -p /mnt/surveillance +mkdir -p /mnt/git + +# Set ownership with error handling +log_info "Setting directory ownership to user: $ACTUAL_USER..." +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks 2>/dev/null; then + log_warning "Failed to set ownership for /opt/stacks - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /opt/stacks" +fi +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge 2>/dev/null; then + log_warning "Failed to set ownership for /opt/dockge - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /opt/dockge" +fi +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/media 2>/dev/null; then + log_warning "Failed to set ownership for /mnt/media - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /mnt/media" +fi +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/downloads 2>/dev/null; then + log_warning "Failed to set ownership for /mnt/downloads - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /mnt/downloads" +fi +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/backups 2>/dev/null; then + log_warning "Failed to set ownership for /mnt/backups - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /mnt/backups" +fi +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/surveillance 2>/dev/null; then + log_warning "Failed to set ownership for /mnt/surveillance - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /mnt/surveillance" +fi +if ! chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/git 2>/dev/null; then + log_warning "Failed to set ownership for /mnt/git - you may need to run: sudo chown -R $ACTUAL_USER:$ACTUAL_USER /mnt/git" +fi + +log_success "Directory structure created" +echo "" + +# Step 9: Create Docker Networks +log_info "Step 9/9: Creating Docker networks..." +su - "$ACTUAL_USER" -c "docker network create homelab-network 2>/dev/null || true" +su - "$ACTUAL_USER" -c "docker network create traefik-network 2>/dev/null || true" +su - "$ACTUAL_USER" -c "docker network create media-network 2>/dev/null || true" +su - "$ACTUAL_USER" -c "docker network create dockerproxy-network 2>/dev/null || true" +log_success "Docker networks created" +echo "" + +# Final Summary +echo "" +echo "==========================================" +log_success "AI-Homelab setup completed successfully!" +echo "==========================================" +echo "" +log_info "Next steps:" +echo "" +echo " 1. Log out and log back in for group changes to take effect" +echo " (or run: newgrp docker)" +echo "" +echo " 2. Navigate to your AI-Homelab repository:" +echo " cd ~/AI-Homelab" +echo "" +echo " 3. Edit the .env file with your configuration:" +echo " cp .env.example .env" +echo " nano .env" +echo "" +echo " 4. Run the deployment script:" +echo " ./scripts/deploy-homelab.sh" +echo "" +echo " 5. Access Dockge at: https://dockge.yourdomain.duckdns.org" +echo " (Use your configured domain and Authelia credentials)" +echo "" +echo "==========================================" + +if lspci | grep -i nvidia > /dev/null && [ "$NVIDIA_INSTALLED" = false ]; then + echo "" + log_warning "REMINDER: Reboot required for NVIDIA driver changes" + echo " Run: sudo reboot" + echo "==========================================" +fi + +echo "" +log_info "Setup complete! Please log out and log back in." diff --git a/services to proxy.yaml b/services to proxy.yaml new file mode 100644 index 0000000..bec7f7c --- /dev/null +++ b/services to proxy.yaml @@ -0,0 +1,33 @@ +# This is just a list of services by ip:port that should be +# proxied via traefik on 192.168.4.4 (Raspberry Pi 4 4GB) + +bookstack: 192.168.4.11:6875 +mealie: 192.168.4.11:9000 +wordpress: 192.168.4.11:8088 Set Proxy Host to knot-u.kelinreij.duckdns.org +backrest: 192.168.4.11:9898 +motioneye: 192.168.4.11:8081 (main web UI; additional ports 8765,9901-9904 for streams/cameras) +dokuwiki: 192.168.4.11:8087 Set Proxy Host to wiki.kelinreij.duckdns.org +formio: 192.168.4.11:3002 set Proxy Host to forms.kelinreij.duckdns.org +glances: 192.168.4.11:61208 +dozzle: 192.168.4.11:8085 +dockge: 192.168.4.11:5001 set Proxy Host to jarvis.kelinreij.duckdns.org +nextcloud: 192.168.4.11:8089 Set Proxy Host to cloud.kelinreij.duckdns.org +kopia: 192.168.4.11:51515 +openkm: 192.168.4.11:18080 +unmanic: 192.168.4.11:8888 +gitea: 192.168.4.11:3010 (web UI; SSH on 2222) +bitwarden: 192.168.4.11:8000 +duplicati: 192.168.4.11:8200 + +jellyfin: 192.168.4.11:8096 +jellyseerr: 192.168.4.11:5055 +prowlarr: 192.168.4.11:9696 +radarr: 192.168.4.11:7878 +sonarr: 192.168.4.11:8989 +lidarr: 192.168.4.11:8686 +readarr: 192.168.4.11:8787 +calibre-web: 192.168.4.11:8083 +mylar3: 192.168.4.11:8090 +qbittorrent: 192.168.4.11:8080 (behind VPN) +tdarr-server: 192.168.4.11:8265 (web UI; API on 8266) +