diff --git a/.env.example b/.env.example index d6cd746..1f9fb32 100644 --- a/.env.example +++ b/.env.example @@ -60,7 +60,7 @@ ACME_EMAIL=${DEFAULT_EMAIL} ADMIN_EMAIL=${DEFAULT_EMAIL} # Used for admin user account AUTHELIA_ADMIN_USER=${DEFAULT_USER} AUTHELIA_ADMIN_EMAIL=${DEFAULT_EMAIL} -AUTHELIA_ADMIN_PASSWORD=generate-with-openssl-rand-hex-64 +AUTHELIA_ADMIN_PASSWORD_HASH=generate-with-openssl-rand-hex-64 # SMTP for Authelia Notifications SMTP_USERNAME=${SMTP_EMAIL_FROM} diff --git a/TASKS.md b/TASKS.md new file mode 100644 index 0000000..f3c574b --- /dev/null +++ b/TASKS.md @@ -0,0 +1,73 @@ +# EZ-Homelab Script Refactoring Tasks + +## Overview +This document outlines the updated plan for refactoring `ez-homelab.sh` based on user requirements. Tasks are prioritized by impact and dependencies. All files are in the same repo. Dry-run output should be user-friendly. + +## Task Categories + +### 1. Menu and Workflow Updates (High Priority) +- **1.1: Create `install-prerequisites.sh`** + Extract `system_setup()` into a standalone script that must run as root/sudo. Update Option 1 to launch it with sudo if needed. + *Effort*: 2-3 hours. *Files*: New `install-prerequisites.sh`, modify `ez-homelab.sh`. + +- **1.2: Update Menu Option 3 Prompts** + For Option 3, check if default values are valid. If yes, prompt to use defaults or not. If not, prompt for all REQUIRED_VARS to ensure easy deployment. Reword prompts to clarify REMOTE_SERVER_* vars are for core server cert copying. + *Effort*: 1-2 hours. *Files*: `ez-homelab.sh` (`validate_and_prompt_variables()`, `prompt_for_variable()`). + +- **1.3: Implement Menu Option 4 (NVIDIA Installation)** + Use `nvidia-detect` to determine GPU and official installer. Install NVIDIA drivers and Container Toolkit. Handle no-GPU gracefully. + *Effort*: 3-4 hours. *Files*: `ez-homelab.sh` or `install-prerequisites.sh`. + +### 2. Bug Fixes (High Priority) +- **2.1: Remove Hardcoded Values** + Replace "kelin", "kelinreij", etc., with variables like `${DOMAIN}`, `${SERVER_IP}` in completion messages and examples. + *Effort*: 1 hour. *Files*: `ez-homelab.sh`. + +- **2.2: Fix HOMEPAGE_ALLOWED_HOSTS** + Instead of hardcoding port (3003), extract the proper port from the Homepage compose file. Ensure line is `HOMEPAGE_ALLOWED_HOSTS="homepage.${DOMAIN},${SERVER_IP}:"`. + *Effort*: 30 minutes. *Files*: `ez-homelab.sh` (`save_env_file()`). + +### 3. New Features and Enhancements (Medium Priority) +- **3.1: Add Argument Parsing** + Implement CLI args (e.g., `--deploy-core`, `--dry-run`, `--verbose`) using `getopts` to bypass menu. + *Effort*: 2-3 hours. *Files*: `ez-homelab.sh` (`main()`). + +- **3.2: Add Dry-Run Mode** + `--dry-run` simulates deployment: validate configs, show actions, log verbosely without executing. Output user-friendly summaries. + *Effort*: 2 hours. *Files*: `ez-homelab.sh` (`perform_deployment()`). + +- **3.3: Enhance Console Logging for Verbose Mode** + Update `log_*` functions to output to console when `VERBOSE=true`. + *Effort*: 1 hour. *Files*: `ez-homelab.sh`. + +- **3.4: Improve Error Handling** + Remove `set -e`; log errors but continue where possible. Use `||` for non-critical failures. + *Effort*: 2 hours. *Files*: `ez-homelab.sh`. + +### 4. TLS and Multi-Server Logic Refinements (Medium Priority) +- **4.1: Clarify Variable Usage** + Ensure prompts distinguish: `SERVER_IP` for local machine, `REMOTE_SERVER_*` for core server. `${DOMAIN}` prompted even for additional servers (needed for configs). + *Effort*: 1-2 hours. *Files*: `ez-homelab.sh`. + +### 5. Function Organization and Code Quality (Low Priority) +- **5.1: Audit and Improve Placeholder/Env Functions** + Rename `replace_env_placeholders()` to `localize_yml_file()` and `enhance_placeholder_replacement()` to `localize_deployment()`. Add error aggregation in bulk function. Make single-file function robust (permissions, backups only for existing targets, no repo modifications). Add post-replacement validation for Traefik labels. Handle special characters in values (passwords, hashes). + *Effort*: 2-3 hours. *Files*: `ez-homelab.sh`. + +- **5.2: Modularize Code with More Functions** + Break `main()` into `parse_args()`, `handle_menu_choice()`, `prepare_deployment()`. Extract repeated logic (env copying, dir creation). + *Effort*: 3-4 hours. *Files*: `ez-homelab.sh`. + +- **5.3: Fix Deployment Flow** + Streamline `perform_deployment()`: consistent step numbering, better recovery, dry-run integration. + *Effort*: 1 hour. *Files*: `ez-homelab.sh`. + +## Implementation Order +1. Start with Bug Fixes (2.1-2.2) and Menu Option 1 (1.1). +2. Then New Features (3.1-3.4) and Menu Options (1.2-1.3). +3. Refinements (4.1, 5.1-5.3). + +## Notes +- Test after each task: interactive menu, args, dry-run, multi-server. +- Dependencies: NVIDIA tasks require `nvidia-detect`; dry-run depends on args. +- Risks: Error handling changes may mask issues; validate thoroughly. \ No newline at end of file diff --git a/config-templates/authelia/users_database.yml b/config-templates/authelia/users_database.yml index 3bb6855..a69ca71 100644 --- a/config-templates/authelia/users_database.yml +++ b/config-templates/authelia/users_database.yml @@ -4,9 +4,9 @@ users: ${AUTHELIA_ADMIN_USER}: - displayname: "${AUTHELIA_ADMIN_USER}" - password: "${AUTHELIA_ADMIN_PASSWORD}" - email: "${AUTHELIA_ADMIN_EMAIL}" + displayname: ${AUTHELIA_ADMIN_USER} + password: ${AUTHELIA_ADMIN_PASSWORD_HASH} + email: ${AUTHELIA_ADMIN_EMAIL} groups: - admins - users diff --git a/config-templates/homepage/services.yaml b/config-templates/homepage/services.yaml index 18c2ef1..73eb849 100644 --- a/config-templates/homepage/services.yaml +++ b/config-templates/homepage/services.yaml @@ -7,29 +7,29 @@ href: https://homepage.kelinreij.duckdns.org description: Hosted on Raspberry Pi - - Homepage - ${REMOTE_SERVER_HOSTNAME}: + - Homepage - your-remote-server : icon: homepage.png - href: https://homepage.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Application Dashboard + href: https://homepage.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Application Dashboard - Homarr: icon: homarr.png href: https://homarr.kelinreij.duckdns.org description: Alternative Dashboard - - Homarr - ${REMOTE_SERVER_HOSTNAME}: + - Homarr - your-remote-server : icon: homarr.png - href: https://homarr.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Alternative Dashboard + href: https://homarr.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Alternative Dashboard - Dockge - jasper: icon: dockge.png href: https://jasper.kelinreij.duckdns.org description: Main Server - - Dockge - ${REMOTE_SERVER_HOSTNAME}: + - Dockge - your-remote-server : icon: dockge.png - href: https://${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org + href: https://your-remote-server .kelinreij.duckdns.org description: Raspberry Pi Authentication Server - Core: @@ -56,38 +56,38 @@ - Dozzle: icon: dozzle.png - href: https://dozzle.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Real-time Log Viewer + href: https://dozzle.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Real-time Log Viewer - Glances - jasper: icon: glances.png href: https://glances.jasper.kelinreij.duckdns.org description: jasper - System Monitoring - - Glances - ${REMOTE_SERVER_HOSTNAME}: + - Glances - your-remote-server : icon: glances.png - href: https://glances.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - System Monitoring + href: https://glances.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - System Monitoring - Uptime Kuma: icon: uptime-kuma.png href: https://uptime-kuma.kelinreij.duckdns.org description: Uptime Monitoring - - Grafana - ${REMOTE_SERVER_HOSTNAME}: + - Grafana - your-remote-server : icon: grafana.png - href: https://grafana.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Metrics Dashboard + href: https://grafana.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Metrics Dashboard - - Prometheus - ${REMOTE_SERVER_HOSTNAME}: + - Prometheus - your-remote-server : icon: prometheus.png - href: https://prometheus.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Metrics Collection + href: https://prometheus.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Metrics Collection - - Uptime Kuma - ${REMOTE_SERVER_HOSTNAME}: + - Uptime Kuma - your-remote-server : icon: uptime-kuma.png - href: https://status.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Uptime Monitoring + href: https://status.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Uptime Monitoring - Media: - Jellyfin: @@ -243,20 +243,20 @@ href: https://backrest.kelinreij.duckdns.org description: Backup Solution - - Backrest - ${REMOTE_SERVER_HOSTNAME}: + - Backrest - your-remote-server : icon: mdi-backup-restore - href: https://backrest.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Backup Solution + href: https://backrest.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Backup Solution - Duplicati: icon: duplicati.png href: https://duplicati.kelinreij.duckdns.org description: Backup Software - - Duplicati - ${REMOTE_SERVER_HOSTNAME}: + - Duplicati - your-remote-server : icon: duplicati.png - href: https://duplicati.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Backup Software + href: https://duplicati.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Backup Software - Metrics: - Grafana: diff --git a/docker-compose/alternatives/docker-compose.yml b/docker-compose/alternatives/docker-compose.yml index bf440de..ddf2ed2 100644 --- a/docker-compose/alternatives/docker-compose.yml +++ b/docker-compose/alternatives/docker-compose.yml @@ -1,8 +1,5 @@ # Alternative Services Stack # This stack contains alternative/optional services that are not deployed by default -# Deploy manually through Dockge if you want to use these alternatives -# Place in /opt/stacks/alternatives/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) @@ -10,8 +7,6 @@ services: # Portainer - Docker management UI (Alternative to Dockge) - # Access at: https://portainer.kelinreij.duckdns.org - # NOTE: Dockge is the default Docker management UI. Deploy Portainer only if you prefer its interface # Docker management interface should always run when deployed portainer: image: portainer/portainer-ce:2.19.4 @@ -35,14 +30,14 @@ services: - "homelab.description=Docker container management UI (Alternative to Dockge)" - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.portainer.rule=Host(`portainer.kelinreij.duckdns.org`)" + - "traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN}`)" - "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" - "traefik.http.routers.portainer.middlewares=authelia@docker" - "traefik.http.services.portainer.loadbalancer.server.port=9000" # Authentik - Alternative SSO/Identity Provider with Web UI - # Access at: https://authentik.kelinreij.duckdns.org + # Access at: https://authentik.${DOMAIN} # NOTE: Authelia is the default SSO. Deploy Authentik only if you need a web UI for user management # WARNING: Do not run both Authelia and Authentik at the same time # SSO service should always run when deployed as alternative to Authelia @@ -75,7 +70,7 @@ services: - "homelab.description=SSO/Identity provider with web UI (Alternative to Authelia)" - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.authentik.rule=Host(`authentik.kelinreij.duckdns.org`)" + - "traefik.http.routers.authentik.rule=Host(`authentik.${DOMAIN}`)" - "traefik.http.routers.authentik.entrypoints=websecure" - "traefik.http.routers.authentik.tls.certresolver=letsencrypt" - "traefik.http.routers.authentik.middlewares=authelia@docker" @@ -165,9 +160,7 @@ services: retries: 5 # Plex Media Server - Alternative to Jellyfin - # Access at: https://plex.yourdomain.duckdns.org # NOTE: No Authelia - allows app access from Roku, Fire TV, mobile, etc. - # Media server should always run when deployed as alternative to Jellyfin plex: image: plexinc/pms-docker:1.40.0.7998-f68041501 container_name: plex @@ -214,12 +207,12 @@ services: # Traefik labels - NO Authelia for app access - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.plex.rule=Host(`plex.kelinreij.duckdns.org`)" + - "traefik.http.routers.plex.rule=Host(`plex.${DOMAIN}`)" - "traefik.http.routers.plex.entrypoints=websecure" - "traefik.http.routers.plex.tls.certresolver=letsencrypt" - "traefik.http.services.plex.loadbalancer.server.port=32400" - - "x-dockge.url=https://plex.kelinreij.duckdns.org" - - "x-dockge.url=https://plex.kelinreij.duckdns.org" + - "x-dockge.url=https://plex.${DOMAIN}" + - "x-dockge.url=https://plex.${DOMAIN}" volumes: portainer-data: diff --git a/docker-compose/core/authelia/users_database.yml b/docker-compose/core/authelia/users_database.yml index e6972e1..cbfa1af 100644 --- a/docker-compose/core/authelia/users_database.yml +++ b/docker-compose/core/authelia/users_database.yml @@ -3,10 +3,10 @@ ############################################################### users: - kelin: + ${DEFAULT_USER}: displayname: "Admin User" - password: "$argon2id$v=19$m=65536,t=3,p=4$a+3pIrywP/li9wy9J6UkMA$+3THyJiAnS/gNYnLaYtlsRCaYfgnnxsUyGZ4D3xGnUg" - email: kelinshomelab@gmail.com + password: "${AUTHELIA_ADMIN_PASSWORD_HASH}" + email: ${DEFAULT_EMAIL} groups: - admins - users diff --git a/docker-compose/core/docker-compose.yml b/docker-compose/core/docker-compose.yml index 74d0a68..6f31cb1 100644 --- a/docker-compose/core/docker-compose.yml +++ b/docker-compose/core/docker-compose.yml @@ -1,7 +1,4 @@ # Core Infrastructure Services -# These services form the foundation of the homelab and should always be running -# Place in /opt/stacks/core/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) @@ -50,11 +47,8 @@ services: # Service metadata - "homelab.category=core" - "homelab.description=Reverse proxy and SSL termination" - # Traefik reverse proxy (comment/uncomment to disable/enable) - # If Traefik is on a remote server: these labels are NOT USED; - # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - - "traefik.http.routers.traefik.rule=Host(`traefik.kelinreij.duckdns.org`)" + - "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.middlewares=authelia@docker" @@ -86,13 +80,13 @@ services: # If Traefik is on a remote server: these labels are NOT USED; # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - - "traefik.http.routers.authelia.rule=Host(`auth.kelinreij.duckdns.org`)" + - "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)" - "traefik.http.routers.authelia.entrypoints=websecure" - "traefik.http.routers.authelia.tls.certresolver=letsencrypt" - "traefik.http.routers.authelia.service=authelia" - "traefik.http.services.authelia.loadbalancer.server.port=9091" # Authelia forward auth middleware configuration - - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.kelinreij.duckdns.org/" + - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.${DOMAIN}/" - "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=X-Secret" - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true" @@ -133,7 +127,7 @@ networks: x-dockge: urls: - - https://auth.kelinreij.duckdns.org + - https://auth.${DOMAIN} - http://192.168.4.11:9091 - - https://traefik.kelinreij.duckdns.org + - https://traefik.${DOMAIN} - http://192.168.4.11:8080 \ No newline at end of file diff --git a/docker-compose/dashboards/docker-compose.yml b/docker-compose/dashboards/docker-compose.yml index 2115446..b1f125a 100644 --- a/docker-compose/dashboards/docker-compose.yml +++ b/docker-compose/dashboards/docker-compose.yml @@ -1,11 +1,9 @@ # Dashboard Services -# Homepage and Homarr for homelab dashboards - # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml - -# Service Access URLs: -# - Homepage: https://homepage.${DOMAIN} -# - Homarr: https://homarr.${DOMAIN} +# RESTART POLICY GUIDE: +# - unless-stopped: Core infrastructure services that should always run +# - no: Services with Sablier lazy loading (start on-demand) +# - See individual service comments for specific reasoning services: # Homepage - Default Application Dashboard @@ -61,7 +59,6 @@ services: # Homarr - Modern dashboard # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity - homarr: image: ghcr.io/ajnart/homarr:latest deploy: diff --git a/docker-compose/dashboards/homepage/services.yaml b/docker-compose/dashboards/homepage/services.yaml index c035ed7..f9d9f4e 100644 --- a/docker-compose/dashboards/homepage/services.yaml +++ b/docker-compose/dashboards/homepage/services.yaml @@ -17,9 +17,9 @@ href: https://jasper.kelinreij.duckdns.org description: Main Server - - Dockge - ${REMOTE_SERVER_HOSTNAME}: + - Dockge - your-remote-server : icon: dockge.png - href: https://${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org + href: https://your-remote-server .kelinreij.duckdns.org description: Raspberry Pi Authentication Server - Core: @@ -46,18 +46,18 @@ - Dozzle: icon: dozzle.png - href: https://dozzle.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - Real-time Log Viewer + href: https://dozzle.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - Real-time Log Viewer - Glances - jasper: icon: glances.png href: https://glances.jasper.kelinreij.duckdns.org description: jasper - System Monitoring - - Glances - ${REMOTE_SERVER_HOSTNAME}: + - Glances - your-remote-server : icon: glances.png - href: https://glances.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org - description: ${REMOTE_SERVER_HOSTNAME} - System Monitoring + href: https://glances.your-remote-server .kelinreij.duckdns.org + description: your-remote-server - System Monitoring - Uptime Kuma: icon: uptime-kuma.png diff --git a/docker-compose/dockge/docker-compose.yml b/docker-compose/dockge/docker-compose.yml index b87ce07..69405b1 100644 --- a/docker-compose/dockge/docker-compose.yml +++ b/docker-compose/dockge/docker-compose.yml @@ -1,18 +1,11 @@ # Dockge Stack -# Docker Compose Stack Manager -# Place in /opt/dockge/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) # - See individual service comments for specific reasoning -# Service Access URLs: -# - Dockge: https://dockge.kelinreij.duckdns.org - services: - # Dockge - Docker Compose Stack Manager (PRIMARY - preferred over Portainer) - # Access at: https://dockge.kelinreij.duckdns.org + # Dockge - Docker Compose Stack Manager # Stack management interface should always run for container management dockge: image: louislam/dockge:1 @@ -51,7 +44,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.dockge.rule=Host(`dockge.kelinreij.duckdns.org`)" + - "traefik.http.routers.dockge.rule=Host(`dockge.${DOMAIN}`)" - "traefik.http.routers.dockge.entrypoints=websecure" - "traefik.http.routers.dockge.tls.certresolver=letsencrypt" - "traefik.http.routers.dockge.middlewares=authelia@docker" diff --git a/docker-compose/homeassistant/docker-compose.yml b/docker-compose/homeassistant/docker-compose.yml index 681b5dc..9fa1802 100644 --- a/docker-compose/homeassistant/docker-compose.yml +++ b/docker-compose/homeassistant/docker-compose.yml @@ -1,17 +1,11 @@ # Home Assistant and IoT Services -# Home automation platform and related tools -# Place in /opt/stacks/homeassistant/docker-compose.yml - -# Service Access URLs: -# - Home Assistant: https://ha.kelinreij.duckdns.org (configure via Traefik file provider - uses host network) -# - ESPHome: https://esphome.kelinreij.duckdns.org -# - Node-RED: https://nodered.kelinreij.duckdns.org -# - Mosquitto MQTT: mqtt://server-ip:1883 (no web UI) -# - Zigbee2MQTT: https://zigbee2mqtt.kelinreij.duckdns.org (requires USB adapter) +# RESTART POLICY GUIDE: +# - unless-stopped: Core infrastructure services that should always run +# - no: Services with Sablier lazy loading (start on-demand) +# - See individual service comments for specific reasoning services: # Home Assistant - Home automation platform - # Access at: https://ha.kelinreij.duckdns.org # NOTE: No Authelia - HA has its own authentication homeassistant: image: ghcr.io/home-assistant/home-assistant:2024.1 @@ -40,7 +34,6 @@ services: # Use Traefik's file provider or external host routing # ESPHome - ESP8266/ESP32 firmware manager - # Access at: https://esphome.kelinreij.duckdns.org esphome: image: ghcr.io/esphome/esphome:latest deploy: @@ -77,14 +70,13 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.esphome.rule=Host(`esphome.kelinreij.duckdns.org`)" + - "traefik.http.routers.esphome.rule=Host(`esphome.${DOMAIN}`)" - "traefik.http.routers.esphome.entrypoints=websecure" - "traefik.http.routers.esphome.tls.certresolver=letsencrypt" - "traefik.http.routers.esphome.middlewares=authelia@docker" - "traefik.http.services.esphome.loadbalancer.server.port=6052" # TasmoAdmin - Tasmota device manager - # Access at: https://tasmoadmin.kelinreij.duckdns.org tasmoadmin: image: ghcr.io/tasmoadmin/tasmoadmin:latest container_name: tasmoadmin @@ -109,14 +101,13 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.tasmoadmin.rule=Host(`tasmoadmin.kelinreij.duckdns.org`)" + - "traefik.http.routers.tasmoadmin.rule=Host(`tasmoadmin.${DOMAIN}`)" - "traefik.http.routers.tasmoadmin.entrypoints=websecure" - "traefik.http.routers.tasmoadmin.tls.certresolver=letsencrypt" - "traefik.http.routers.tasmoadmin.middlewares=authelia@docker" - "traefik.http.services.tasmoadmin.loadbalancer.server.port=80" # MotionEye - Video surveillance - # Access at: https://motioneye.kelinreij.duckdns.org motioneye: image: ccrisan/motioneye:master-amd64 container_name: motioneye @@ -125,7 +116,7 @@ services: - homelab-network - traefik-network ports: - - "8765:8765" # Optional: direct access + - "8765:8765" volumes: - ./$(basename $file .yml)/config:/etc/motioneye - /mnt/surveillance:/var/lib/motioneye # Large video files on separate drive @@ -142,14 +133,13 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.motioneye.rule=Host(`motioneye.kelinreij.duckdns.org`)" + - "traefik.http.routers.motioneye.rule=Host(`motioneye.${DOMAIN}`)" - "traefik.http.routers.motioneye.entrypoints=websecure" - "traefik.http.routers.motioneye.tls.certresolver=letsencrypt" - "traefik.http.routers.motioneye.middlewares=authelia@docker" - "traefik.http.services.motioneye.loadbalancer.server.port=8765" # Node-RED - Flow-based automation (Home Assistant addon alternative) - # Access at: https://nodered.kelinreij.duckdns.org nodered: image: nodered/node-red:latest deploy: @@ -183,7 +173,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.nodered.rule=Host(`nodered.kelinreij.duckdns.org`)" + - "traefik.http.routers.nodered.rule=Host(`nodered.${DOMAIN}`)" - "traefik.http.routers.nodered.entrypoints=websecure" - "traefik.http.routers.nodered.tls.certresolver=letsencrypt" - "traefik.http.routers.nodered.middlewares=authelia@docker" @@ -209,7 +199,6 @@ services: - "homelab.description=MQTT message broker" # Zigbee2MQTT - Zigbee to MQTT bridge (DISABLED - requires USB adapter) - # Access at: https://zigbee2mqtt.kelinreij.duckdns.org # NOTE: Requires USB Zigbee adapter (e.g., ConBee II, Sonoff ZBDongle) # Uncomment after connecting adapter # zigbee2mqtt: @@ -233,7 +222,7 @@ services: # - "homelab.category=iot" # - "homelab.description=Zigbee to MQTT bridge" # - "traefik.enable=true" - # - "traefik.http.routers.zigbee2mqtt.rule=Host(`zigbee2mqtt.kelinreij.duckdns.org`)" + # - "traefik.http.routers.zigbee2mqtt.rule=Host(`zigbee2mqtt.${DOMAIN}`)" # - "traefik.http.routers.zigbee2mqtt.entrypoints=websecure" # - "traefik.http.routers.zigbee2mqtt.tls.certresolver=letsencrypt" # - "traefik.http.routers.zigbee2mqtt.middlewares=authelia@docker" @@ -248,15 +237,15 @@ networks: x-dockge: urls: # Proxied URLs (through Traefik) - - https://ha.kelinreij.duckdns.org + - https://ha.${DOMAIN} - http://192.168.4.4:8123 - - https://esphome.kelinreij.duckdns.org + - https://esphome.${DOMAIN} - http://192.168.4.4:6052 - - https://tasmoadmin.kelinreij.duckdns.org + - https://tasmoadmin.${DOMAIN} - http://192.168.4.4:8084 - - https://motioneye.kelinreij.duckdns.org + - https://motioneye.${DOMAIN} - http://192.168.4.4:8765 - - https://nodered.kelinreij.duckdns.org + - https://nodered.${DOMAIN} - http://192.168.4.4:1880 - mqtt://192.168.4.4:1883 - - https://zigbee2mqtt.kelinreij.duckdns.org \ No newline at end of file + - https://zigbee2mqtt.${DOMAIN} \ No newline at end of file diff --git a/docker-compose/infrastructure/docker-compose.yml b/docker-compose/infrastructure/docker-compose.yml index 8a1f58b..b468f15 100644 --- a/docker-compose/infrastructure/docker-compose.yml +++ b/docker-compose/infrastructure/docker-compose.yml @@ -1,9 +1,5 @@ # Infrastructure Services -# Core services that other services depend on -# Place in /opt/stacks/infrastructure/docker-compose.yml - # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) @@ -41,7 +37,6 @@ services: - homelab.description=Docker socket proxy for security # Pi-hole - Network-wide ad blocker and DNS server - # Access at: https://pihole.kelinreij.duckdns.org # DNS service must always run for network-wide ad blocking pihole: image: pihole/pihole:2024.01.0 @@ -87,15 +82,13 @@ services: # - This prevents conflicts between Docker labels and file provider - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.pihole.rule=Host(`pihole.kelinreij.duckdns.org`)" + - "traefik.http.routers.pihole.rule=Host(`pihole.${DOMAIN}`)" - "traefik.http.routers.pihole.entrypoints=websecure" - "traefik.http.routers.pihole.tls.certresolver=letsencrypt" - "traefik.http.routers.pihole.middlewares=authelia@docker" - "traefik.http.services.pihole.loadbalancer.server.port=80" # Watchtower - Automatic container updates - # Monitors and updates Docker containers to latest versions - # Runs daily at 4 AM watchtower: image: containrrr/watchtower:latest container_name: watchtower @@ -116,7 +109,6 @@ services: - "homelab.description=Automatic Docker container updates" # Dozzle - Real-time Docker log viewer - # Access at: https://dozzle.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity dozzle: image: amir20/dozzle:latest @@ -157,7 +149,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.dozzle.rule=Host(`dozzle.jasper.kelinreij.duckdns.org`)" + - "traefik.http.routers.dozzle.rule=Host(`dozzle.jasper.${DOMAIN}`)" - "traefik.http.routers.dozzle.entrypoints=websecure" - "traefik.http.routers.dozzle.tls=true" - "traefik.http.routers.dozzle.middlewares=authelia@docker" @@ -169,7 +161,6 @@ services: - "sablier.start-on-demand=true" # Glances - System monitoring - # Access at: https://glances.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 30min inactivity glances: image: nicolargo/glances:latest-full @@ -210,7 +201,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.glances.rule=Host(`glances.jasper.kelinreij.duckdns.org`)" + - "traefik.http.routers.glances.rule=Host(`glances.jasper.${DOMAIN}`)" - "traefik.http.routers.glances.entrypoints=websecure" - "traefik.http.routers.glances.tls=true" - "traefik.http.routers.glances.middlewares=authelia@docker" @@ -222,7 +213,6 @@ services: - "sablier.start-on-demand=true" # Code Server - VS Code in browser - # Access at: https://code.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 30min inactivity code-server: image: lscr.io/linuxserver/code-server:latest @@ -267,7 +257,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.code-server.rule=Host(`code.kelinreij.duckdns.org`)" + - "traefik.http.routers.code-server.rule=Host(`code.${DOMAIN}`)" - "traefik.http.routers.code-server.entrypoints=websecure" - "traefik.http.routers.code-server.tls.certresolver=letsencrypt" - "traefik.http.routers.code-server.middlewares=authelia@docker" @@ -280,13 +270,13 @@ services: x-dockge: urls: - - https://pihole.kelinreij.duckdns.org + - https://pihole.${DOMAIN} - https://192.168.4.4:53 - - https://dozzle.kelinreij.duckdns.org + - https://dozzle.${DOMAIN} - https://192.168.4.4:8085 - - https://glances.kelinreij.duckdns.org + - https://glances.${DOMAIN} - https://192.168.4.4:61208 - - https://code.kelinreij.duckdns.org + - https://code.${DOMAIN} - https://192.168.4.4:8079 - http://192.168.4.4:2375 # Docker Proxy - http://192.168.4.4:19999 # Netdata diff --git a/docker-compose/media-management/docker-compose.yml b/docker-compose/media-management/docker-compose.yml index 0b2a00f..457c9a2 100644 --- a/docker-compose/media-management/docker-compose.yml +++ b/docker-compose/media-management/docker-compose.yml @@ -1,16 +1,12 @@ # Media Management Services -# Content automation and library management (*arr apps, transcoders, etc.) -# Place in /opt/stacks/media-management/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) # - See individual service comments for specific reasoning services: - # Sonarr - TV show automation - # Access at: https://sonarr.yourdomain.duckdns.org sonarr: + # Sonarr - TV show management and automation image: linuxserver/sonarr:4.0.0 container_name: sonarr restart: no @@ -45,7 +41,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.sonarr.rule=Host(`sonarr.kelinreij.duckdns.org`)" + - "traefik.http.routers.sonarr.rule=Host(`sonarr.${DOMAIN}`)" - "traefik.http.routers.sonarr.entrypoints=websecure" - "traefik.http.routers.sonarr.tls.certresolver=letsencrypt" - "traefik.http.routers.sonarr.middlewares=authelia@docker" @@ -55,7 +51,6 @@ services: - "sablier.start-on-demand=true" # Radarr - Movie automation - # Access at: https://radarr.yourdomain.duckdns.org radarr: image: linuxserver/radarr:5.2.6 container_name: radarr @@ -91,7 +86,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.radarr.rule=Host(`radarr.kelinreij.duckdns.org`)" + - "traefik.http.routers.radarr.rule=Host(`radarr.${DOMAIN}`)" - "traefik.http.routers.radarr.entrypoints=websecure" - "traefik.http.routers.radarr.tls.certresolver=letsencrypt" - "traefik.http.routers.radarr.middlewares=authelia@docker" @@ -135,7 +130,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.prowlarr.rule=Host(`prowlarr.kelinreij.duckdns.org`)" + - "traefik.http.routers.prowlarr.rule=Host(`prowlarr.${DOMAIN}`)" - "traefik.http.routers.prowlarr.entrypoints=websecure" - "traefik.http.routers.prowlarr.tls.certresolver=letsencrypt" - "traefik.http.routers.prowlarr.middlewares=authelia@docker" @@ -145,7 +140,6 @@ services: - "sablier.start-on-demand=true" # Readarr - Ebook and audiobook management - # Access at: https://readarr.kelinreij.duckdns.org readarr: image: linuxserver/readarr:0.4.19-nightly container_name: readarr @@ -175,7 +169,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.readarr.rule=Host(`readarr.kelinreij.duckdns.org`)" + - "traefik.http.routers.readarr.rule=Host(`readarr.${DOMAIN}`)" - "traefik.http.routers.readarr.entrypoints=websecure" - "traefik.http.routers.readarr.tls.certresolver=letsencrypt" - "traefik.http.routers.readarr.middlewares=authelia@docker" @@ -185,7 +179,6 @@ services: - "sablier.start-on-demand=true" # Lidarr - Music collection manager - # Access at: https://lidarr.kelinreij.duckdns.org lidarr: image: linuxserver/lidarr:2.0.7 container_name: lidarr @@ -215,7 +208,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.lidarr.rule=Host(`lidarr.kelinreij.duckdns.org`)" + - "traefik.http.routers.lidarr.rule=Host(`lidarr.${DOMAIN}`)" - "traefik.http.routers.lidarr.entrypoints=websecure" - "traefik.http.routers.lidarr.tls.certresolver=letsencrypt" - "traefik.http.routers.lidarr.middlewares=authelia@docker" @@ -225,7 +218,6 @@ services: - "sablier.start-on-demand=true" # Lazy Librarian - Book manager - # Access at: https://lazylibrarian.kelinreij.duckdns.org lazylibrarian: image: linuxserver/lazylibrarian:latest container_name: lazylibrarian @@ -256,7 +248,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.lazylibrarian.rule=Host(`lazylibrarian.kelinreij.duckdns.org`)" + - "traefik.http.routers.lazylibrarian.rule=Host(`lazylibrarian.${DOMAIN}`)" - "traefik.http.routers.lazylibrarian.entrypoints=websecure" - "traefik.http.routers.lazylibrarian.tls.certresolver=letsencrypt" - "traefik.http.routers.lazylibrarian.middlewares=authelia@docker" @@ -266,7 +258,6 @@ services: - "sablier.start-on-demand=true" # Mylar3 - Comic book manager - # Access at: https://mylar.kelinreij.duckdns.org mylar3: image: linuxserver/mylar3:latest container_name: mylar3 @@ -296,7 +287,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.mylar.rule=Host(`mylar.kelinreij.duckdns.org`)" + - "traefik.http.routers.mylar.rule=Host(`mylar.${DOMAIN}`)" - "traefik.http.routers.mylar.entrypoints=websecure" - "traefik.http.routers.mylar.tls.certresolver=letsencrypt" - "traefik.http.routers.mylar.middlewares=authelia@docker" @@ -306,7 +297,6 @@ services: - "sablier.start-on-demand=true" # Jellyseerr - Request management for Jellyfin/Plex - # Access at: https://jellyseerr.kelinreij.duckdns.org jellyseerr: image: fallenbagel/jellyseerr:latest container_name: jellyseerr @@ -339,7 +329,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.jellyseerr.rule=Host(`jellyseerr.kelinreij.duckdns.org`)" + - "traefik.http.routers.jellyseerr.rule=Host(`jellyseerr.${DOMAIN}`)" - "traefik.http.routers.jellyseerr.entrypoints=websecure" - "traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt" - "traefik.http.routers.jellyseerr.middlewares=authelia@docker" @@ -368,21 +358,21 @@ services: x-dockge: urls: - - https://sonarr.kelinreij.duckdns.org + - https://sonarr.${DOMAIN} - http://192.168.4.4:8989 - - https://radarr.kelinreij.duckdns.org + - https://radarr.${DOMAIN} - http://192.168.4.4:7878 - - https://prowlarr.kelinreij.duckdns.org + - https://prowlarr.${DOMAIN} - http://192.168.4.4:9696 - - https://readarr.kelinreij.duckdns.org + - https://readarr.${DOMAIN} - http://192.168.4.4:8787 - - https://lidarr.kelinreij.duckdns.org + - https://lidarr.${DOMAIN} - http://192.168.4.4:8686 - - https://lazylibrarian.kelinreij.duckdns.org + - https://lazylibrarian.${DOMAIN} - http://192.168.4.4:5299 - - https://mylar.kelinreij.duckdns.org + - https://mylar.${DOMAIN} - http://192.168.4.4:8090 - - https://jellyseerr.kelinreij.duckdns.org + - https://jellyseerr.${DOMAIN} - http://192.168.4.4:5055 networks: diff --git a/docker-compose/media/docker-compose.yml b/docker-compose/media/docker-compose.yml index 32fe09c..4b094f6 100644 --- a/docker-compose/media/docker-compose.yml +++ b/docker-compose/media/docker-compose.yml @@ -1,22 +1,14 @@ # Media Services -# Default Services for media management and streaming -# Place in /opt/stacks/media/docker-compose.yml - # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) # - See individual service comments for specific reasoning -# Service Access URLs: -# - Jellyfin: https://jellyfin.kelinreij.duckdns.org (no SSO - app access) -# - Plex: https://plex.kelinreij.duckdns.org (no SSO - app access) -# - qBittorrent: https://qbit.kelinreij.duckdns.org (routed through Gluetun VPN) + services: # Jellyfin - Open-source media streaming server - # Access at: https://jellyfin.yourdomain.duckdns.org # NOTE: No Authelia - allows app access from Roku, Fire TV, mobile, etc. # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity jellyfin: @@ -63,7 +55,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.kelinreij.duckdns.org`)" + - "traefik.http.routers.jellyfin.rule=Host(`jellyfin.${DOMAIN}`)" - "traefik.http.routers.jellyfin.entrypoints=websecure" - "traefik.http.routers.jellyfin.tls=true" - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" @@ -76,7 +68,7 @@ services: - "sablier.theme=hacker-terminal" # Calibre-Web - Ebook reader and server - # Access at: https://calibre.kelinreij.duckdns.org + # Access at: https://calibre.${DOMAIN} calibre-web: image: lscr.io/linuxserver/calibre-web:latest deploy: @@ -112,7 +104,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.calibre.rule=Host(`calibre.kelinreij.duckdns.org`)" + - "traefik.http.routers.calibre.rule=Host(`calibre.${DOMAIN}`)" - "traefik.http.routers.calibre.entrypoints=websecure" - "traefik.http.routers.calibre.tls.certresolver=letsencrypt" - "traefik.http.routers.calibre.middlewares=authelia@docker" @@ -123,15 +115,12 @@ services: - "sablier.group=jasper-calibre-web" - "sablier.start-on-demand=true" -# ========================================== -# DOCKGE URL CONFIGURATION -# ========================================== x-dockge: urls: # Proxied URLs (through Traefik) - - https://jellyfin.kelinreij.duckdns.org + - https://jellyfin.${DOMAIN} - http://192.168.4.4:8096 - - https://calibre.kelinreij.duckdns.org + - https://calibre.${DOMAIN} - http://192.168.4.4:8083 networks: diff --git a/docker-compose/monitoring/docker-compose.yml b/docker-compose/monitoring/docker-compose.yml index 3ec3f5d..fec036c 100644 --- a/docker-compose/monitoring/docker-compose.yml +++ b/docker-compose/monitoring/docker-compose.yml @@ -1,25 +1,11 @@ # Monitoring and Observability Services -# Services for monitoring your homelab infrastructure -# Place in /opt/stacks/monitoring/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) # - See individual service comments for specific reasoning -# Service Access URLs: -# - Prometheus: http://192.168.4.4:9090 (or configure Traefik) -# - Grafana: http://192.168.4.4:3000 (or configure Traefik) -# - Uptime Kuma: https://status.kelinreij.duckdns.org -# - Node Exporter: http://192.168.4.4:9100/metrics -# - cAdvisor: http://192.168.4.4:8082 -# - Loki: http://192.168.4.4:3100 -# NOTE: Prometheus, Grafana, Loki use ports because they need to be accessible to other services -# Add Traefik labels if you want https://prometheus.kelinreij.duckdns.org access - services: # Prometheus - Metrics collection and storage - # Access at: http://192.168.4.4:9090 prometheus: image: prom/prometheus:v2.48.1 deploy: @@ -59,7 +45,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.prometheus.rule=Host(`prometheus.kelinreij.duckdns.org`)" + - "traefik.http.routers.prometheus.rule=Host(`prometheus.${DOMAIN}`)" - "traefik.http.routers.prometheus.entrypoints=websecure" - "traefik.http.routers.prometheus.tls=true" - "traefik.http.routers.prometheus.tls.certresolver=letsencrypt" @@ -67,7 +53,6 @@ services: - "traefik.http.services.prometheus.loadbalancer.server.port=9090" # Grafana - Metrics visualization - # Access at: http://192.168.4.4:3000 # Default credentials: admin / admin (change on first login) grafana: image: grafana/grafana:10.2.3 @@ -93,7 +78,7 @@ services: environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} - GF_USERS_ALLOW_SIGN_UP=false - - GF_SERVER_ROOT_URL=https://grafana.kelinreij.duckdns.org + - GF_SERVER_ROOT_URL=https://grafana.${DOMAIN} - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource,grafana-piechart-panel user: "1000:1000" depends_on: @@ -109,7 +94,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.grafana.rule=Host(`grafana.kelinreij.duckdns.org`)" + - "traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN}`)" - "traefik.http.routers.grafana.entrypoints=websecure" - "traefik.http.routers.grafana.tls=true" - "traefik.http.routers.grafana.tls.certresolver=letsencrypt" @@ -170,7 +155,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.cadvisor.rule=Host(`cadvisor.kelinreij.duckdns.org`)" + - "traefik.http.routers.cadvisor.rule=Host(`cadvisor.${DOMAIN}`)" - "traefik.http.routers.cadvisor.entrypoints=websecure" - "traefik.http.routers.cadvisor.tls=true" - "traefik.http.routers.cadvisor.tls.certresolver=letsencrypt" @@ -178,7 +163,6 @@ services: - "traefik.http.services.cadvisor.loadbalancer.server.port=8080" # Uptime Kuma - Uptime monitoring - # Access at: https://uptime-kuma.kelinreij.duckdns.org uptime-kuma: image: louislam/uptime-kuma:1 deploy: @@ -211,7 +195,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.uptime-kuma.rule=Host(`uptime-kuma.kelinreij.duckdns.org`)" + - "traefik.http.routers.uptime-kuma.rule=Host(`uptime-kuma.${DOMAIN}`)" - "traefik.http.routers.uptime-kuma.entrypoints=websecure" - "traefik.http.routers.uptime-kuma.tls=true" - "traefik.http.routers.uptime-kuma.tls.certresolver=letsencrypt" @@ -253,7 +237,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.loki.rule=Host(`loki.kelinreij.duckdns.org`)" + - "traefik.http.routers.loki.rule=Host(`loki.${DOMAIN}`)" - "traefik.http.routers.loki.entrypoints=websecure" - "traefik.http.routers.loki.tls=true" - "traefik.http.routers.loki.tls.certresolver=letsencrypt" @@ -300,7 +284,7 @@ x-dockge: # Proxied URLs (through Traefik) - http://192.168.4.4:9090 - http://192.168.4.4:3000 - - https://uptime-kuma.kelinreij.duckdns.org + - https://uptime-kuma.${DOMAIN} - http://192.168.4.4:9100/metrics - http://192.168.4.4:8082 - http://192.168.4.4:3100 diff --git a/docker-compose/productivity/docker-compose.yml b/docker-compose/productivity/docker-compose.yml index 1f56f8f..b365eab 100644 --- a/docker-compose/productivity/docker-compose.yml +++ b/docker-compose/productivity/docker-compose.yml @@ -1,8 +1,5 @@ # Productivity and Content Management Services -# Place in /opt/stacks/productivity/docker-compose.yml - # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) @@ -10,7 +7,6 @@ services: # Nextcloud - File sync and collaboration - # Access at: https://nextcloud.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity nextcloud: image: nextcloud:28 @@ -40,10 +36,10 @@ services: - MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD} - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.kelinreij.duckdns.org + - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.${DOMAIN} - TRUSTED_PROXIES=172.18.0.0/16 - OVERWRITEPROTOCOL=https - - OVERWRITEHOST=nextcloud.kelinreij.duckdns.org + - OVERWRITEHOST=nextcloud.${DOMAIN} healthcheck: test: ["CMD", "curl", "-f", "http://localhost/status.php"] interval: 30s @@ -61,7 +57,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.kelinreij.duckdns.org`)" + - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.${DOMAIN}`)" - "traefik.http.routers.nextcloud.entrypoints=websecure" - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" - "traefik.http.routers.nextcloud.middlewares=authelia@docker" @@ -91,7 +87,6 @@ services: - "homelab.description=Nextcloud database" # Mealie - Recipe manager - # Access at: https://mealie.kelinreij.duckdns.org mealie: image: ghcr.io/mealie-recipes/mealie:latest container_name: mealie @@ -107,7 +102,7 @@ services: - PUID=1000 - PGID=1000 - TZ=America/New_York - - BASE_URL=https://mealie.kelinreij.duckdns.org + - BASE_URL=https://mealie.${DOMAIN} - DB_ENGINE=sqlite labels: # TRAEFIK CONFIGURATION @@ -118,7 +113,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.mealie.rule=Host(`mealie.kelinreij.duckdns.org`)" + - "traefik.http.routers.mealie.rule=Host(`mealie.${DOMAIN}`)" - "traefik.http.routers.mealie.entrypoints=websecure" - "traefik.http.routers.mealie.tls.certresolver=letsencrypt" - "traefik.http.routers.mealie.middlewares=authelia@docker" @@ -130,7 +125,6 @@ services: - "sablier.start-on-demand=true" # WordPress - Blog/website platform - # Access at: https://blog.kelinreij.duckdns.org wordpress: image: wordpress:latest container_name: wordpress @@ -164,7 +158,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.wordpress.rule=Host(`wordpress.kelinreij.duckdns.org`)" + - "traefik.http.routers.wordpress.rule=Host(`wordpress.${DOMAIN}`)" - "traefik.http.routers.wordpress.entrypoints=websecure" - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt" - "traefik.http.routers.wordpress.middlewares=authelia@docker" @@ -193,7 +187,6 @@ services: - "homelab.description=WordPress database" # Gitea - Self-hosted Git service - # Access at: https://git.kelinreij.duckdns.org gitea: image: gitea/gitea:latest deploy: @@ -241,7 +234,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.gitea.rule=Host(`gitea.kelinreij.duckdns.org`)" + - "traefik.http.routers.gitea.rule=Host(`gitea.${DOMAIN}`)" - "traefik.http.routers.gitea.entrypoints=websecure" - "traefik.http.routers.gitea.tls.certresolver=letsencrypt" - "traefik.http.routers.gitea.middlewares=authelia@docker" @@ -270,7 +263,6 @@ services: # Jupyter Lab - Interactive computing notebooks - # Access at: https://jupyter.kelinreij.duckdns.org # Token displayed in logs on first start jupyter: image: jupyter/scipy-notebook:latest @@ -307,7 +299,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.jupyter.rule=Host(`jupyter.kelinreij.duckdns.org`)" + - "traefik.http.routers.jupyter.rule=Host(`jupyter.${DOMAIN}`)" - "traefik.http.routers.jupyter.entrypoints=websecure" - "traefik.http.routers.jupyter.tls.certresolver=letsencrypt" - "traefik.http.routers.jupyter.middlewares=authelia@docker" @@ -331,13 +323,13 @@ networks: x-dockge: urls: # Proxied URLs (through Traefik) - - https://nextcloud.kelinreij.duckdns.org + - https://nextcloud.${DOMAIN} - https://192.168.4.4:8089 - - https://mealie.kelinreij.duckdns.org + - https://mealie.${DOMAIN} - https://192.168.4.4:9000 - - https://wordpress.kelinreij.duckdns.org + - https://wordpress.${DOMAIN} - https://192.168.4.4:8088 - - https://gitea.kelinreij.duckdns.org + - https://gitea.${DOMAIN} - https://192.168.4.4:3010 - - https://jupyter.kelinreij.duckdns.org + - https://jupyter.${DOMAIN} - https://192.168.4.4:8890 diff --git a/docker-compose/transcoders/docker-compose.yml b/docker-compose/transcoders/docker-compose.yml index c23a08e..fca0427 100644 --- a/docker-compose/transcoders/docker-compose.yml +++ b/docker-compose/transcoders/docker-compose.yml @@ -1,6 +1,11 @@ +# Transcoder Services +# RESTART POLICY GUIDE: +# - unless-stopped: Core infrastructure services that should always run +# - no: Services with Sablier lazy loading (start on-demand) +# - See individual service comments for specific reasoning + services: # Tdarr Server - Distributed transcoding server - # Access at: https://tdarr.kelinreij.duckdns.org tdarr-server: image: ghcr.io/haveagitgat/tdarr:latest container_name: tdarr-server @@ -36,7 +41,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.tdarr.rule=Host(`tdarr.kelinreij.duckdns.org`)" + - "traefik.http.routers.tdarr.rule=Host(`tdarr.${DOMAIN}`)" - "traefik.http.routers.tdarr.entrypoints=websecure" - "traefik.http.routers.tdarr.tls.certresolver=letsencrypt" - "traefik.http.routers.tdarr.middlewares=authelia@docker" @@ -75,7 +80,6 @@ services: - "sablier.start-on-demand=true" # Unmanic - Another transcoding option - # Access at: https://unmanic.kelinreij.duckdns.org unmanic: image: josh5/unmanic:latest container_name: unmanic @@ -105,7 +109,7 @@ services: # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - "traefik.docker.network=traefik-network" - - "traefik.http.routers.unmanic.rule=Host(`unmanic.kelinreij.duckdns.org`)" + - "traefik.http.routers.unmanic.rule=Host(`unmanic.${DOMAIN}`)" - "traefik.http.routers.unmanic.entrypoints=websecure" - "traefik.http.routers.unmanic.tls.certresolver=letsencrypt" - "traefik.http.routers.unmanic.middlewares=authelia@docker" @@ -122,7 +126,7 @@ networks: x-dockge: urls: - - https://tdarr.kelinreij.duckdns.org + - https://tdarr.${DOMAIN} - http://192.168.4.4:8265 - - https://unmanic.kelinreij.duckdns.org + - https://unmanic.${DOMAIN} - http://192.168.4.4:8888 \ No newline at end of file diff --git a/docker-compose/utilities/docker-compose.yml b/docker-compose/utilities/docker-compose.yml index 6829093..73a7e8e 100644 --- a/docker-compose/utilities/docker-compose.yml +++ b/docker-compose/utilities/docker-compose.yml @@ -1,6 +1,4 @@ # Backup and Utility Services -# Place in /opt/stacks/utilities/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) @@ -8,7 +6,6 @@ services: # Backrest - Backup solution for restic - # Access at: https://backrest.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity backrest: image: garethgeorge/backrest:latest @@ -44,7 +41,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.backrest.rule=Host(`backrest.kelinreij.duckdns.org`)" + - "traefik.http.routers.backrest.rule=Host(`backrest.${DOMAIN}`)" - "traefik.http.routers.backrest.entrypoints=websecure" - "traefik.http.routers.backrest.tls.certresolver=letsencrypt" - "traefik.http.routers.backrest.middlewares=authelia@docker" @@ -56,7 +53,6 @@ services: - "sablier.start-on-demand=true" # Duplicati - Backup solution - # Access at: https://duplicati.kelinreij.duckdns.org duplicati: image: lscr.io/linuxserver/duplicati:2.0.7 container_name: duplicati @@ -90,7 +86,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.duplicati.rule=Host(`duplicati.kelinreij.duckdns.org`)" + - "traefik.http.routers.duplicati.rule=Host(`duplicati.${DOMAIN}`)" - "traefik.http.routers.duplicati.entrypoints=websecure" - "traefik.http.routers.duplicati.tls.certresolver=letsencrypt" - "traefik.http.routers.duplicati.middlewares=authelia@docker" @@ -134,7 +130,7 @@ services: # Traefik labels - "traefik.enable=true" # Router configuration - - "traefik.http.routers.formio.rule=Host(`forms.kelinreij.duckdns.org`)" + - "traefik.http.routers.formio.rule=Host(`forms.${DOMAIN}`)" - "traefik.http.routers.formio.entrypoints=websecure" - "traefik.http.routers.formio.tls.certresolver=letsencrypt" - "traefik.http.routers.formio.middlewares=authelia@docker" @@ -156,7 +152,6 @@ services: - "homelab.description=Form.io database" # Bitwarden (Vaultwarden) - Password manager - # Access at: https://vault.kelinreij.duckdns.org # Note: SSO disabled for browser extension and mobile app compatibility vaultwarden: @@ -171,7 +166,7 @@ services: volumes: - ./vaultwarden/data:/data environment: - - DOMAIN=https://vault.kelinreij.duckdns.org + - DOMAIN=https://vault.${DOMAIN} - SIGNUPS_ALLOWED=${BITWARDEN_SIGNUPS_ALLOWED} - INVITATIONS_ALLOWED=${BITWARDEN_INVITATIONS_ALLOWED} - ADMIN_TOKEN=${BITWARDEN_ADMIN_TOKEN} @@ -198,7 +193,7 @@ services: # If Traefik is on a remote server: these labels are NOT USED; # configure external yml files in /traefik/dynamic folder instead. - "traefik.enable=true" - - "traefik.http.routers.vaultwarden.rule=Host(`vault.kelinreij.duckdns.org`)" + - "traefik.http.routers.vaultwarden.rule=Host(`vault.${DOMAIN}`)" - "traefik.http.routers.vaultwarden.entrypoints=websecure" - "traefik.http.routers.vaultwarden.tls=true" - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt" @@ -238,11 +233,11 @@ networks: x-dockge: urls: - - https://backrest.kelinreij.duckdns.org + - https://backrest.${DOMAIN} - https://192.168.4.4:9898 - - https://duplicati.kelinreij.duckdns.org + - https://duplicati.${DOMAIN} - https://192.168.4.4:8200 - - https://forms.kelinreij.duckdns.org + - https://forms.${DOMAIN} - https://192.168.4.4:3002 - - https://vault.kelinreij.duckdns.org + - https://vault.${DOMAIN} - https://192.168.4.4:8091 \ No newline at end of file diff --git a/docker-compose/vpn/docker-compose.yml b/docker-compose/vpn/docker-compose.yml index a3a137e..9669800 100644 --- a/docker-compose/vpn/docker-compose.yml +++ b/docker-compose/vpn/docker-compose.yml @@ -1,7 +1,4 @@ # VPN Stack -# VPN client and VPN-routed download clients -# Place in /opt/stacks/vpn/docker-compose.yml - # RESTART POLICY GUIDE: # - unless-stopped: Core infrastructure services that should always run # - no: Services with Sablier lazy loading (start on-demand) @@ -10,7 +7,6 @@ services: # Gluetun - VPN client (Surfshark) # Routes download clients through VPN for security - # VPN service should always run to maintain secure connections gluetun: image: qmcgaw/gluetun:latest container_name: gluetun @@ -47,7 +43,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.qbittorrent.rule=Host(`qbit.kelinreij.duckdns.org`)" + - "traefik.http.routers.qbittorrent.rule=Host(`qbit.${DOMAIN}`)" - "traefik.http.routers.qbittorrent.entrypoints=websecure" - "traefik.http.routers.qbittorrent.tls=true" - "traefik.http.routers.qbittorrent.middlewares=authelia@docker" @@ -59,7 +55,6 @@ services: - "sablier.sessionDuration=1h" # qBittorrent - Torrent client - # Routes through Gluetun VPN qbittorrent: image: lscr.io/linuxserver/qbittorrent:latest deploy: @@ -93,5 +88,5 @@ networks: x-dockge: urls: - - https://qbit.kelinreij.duckdns.org + - https://qbit.${DOMAIN} - https://192.168.4.4:8081 \ No newline at end of file diff --git a/docker-compose/wikis/docker-compose.yml b/docker-compose/wikis/docker-compose.yml index 92470d5..7898d7e 100644 --- a/docker-compose/wikis/docker-compose.yml +++ b/docker-compose/wikis/docker-compose.yml @@ -1,6 +1,11 @@ +# Wiki Services +# RESTART POLICY GUIDE: +# - unless-stopped: Core infrastructure services that should always run +# - no: Services with Sablier lazy loading (start on-demand) +# - See individual service comments for specific reasoning + services: # DokuWiki - Wiki without database - # Access at: https://wiki.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity dokuwiki: image: lscr.io/linuxserver/dokuwiki:latest @@ -26,7 +31,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.dokuwiki.rule=Host(`dokuwiki.kelinreij.duckdns.org`)" + - "traefik.http.routers.dokuwiki.rule=Host(`dokuwiki.${DOMAIN}`)" - "traefik.http.routers.dokuwiki.entrypoints=websecure" - "traefik.http.routers.dokuwiki.tls.certresolver=letsencrypt" - "traefik.http.routers.dokuwiki.middlewares=authelia@docker" @@ -38,7 +43,6 @@ services: - "sablier.start-on-demand=true" # BookStack - Documentation platform - # Access at: https://docs.kelinreij.duckdns.org # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity bookstack: image: lscr.io/linuxserver/bookstack:latest @@ -54,7 +58,7 @@ services: environment: - PUID=1000 - PGID=1000 - - APP_URL=https://bookstack.kelinreij.duckdns.org + - APP_URL=https://bookstack.${DOMAIN} - DB_HOST=bookstack-db - DB_PORT=3306 - DB_DATABASE=bookstack @@ -78,7 +82,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.bookstack.rule=Host(`bookstack.kelinreij.duckdns.org`)" + - "traefik.http.routers.bookstack.rule=Host(`bookstack.${DOMAIN}`)" - "traefik.http.routers.bookstack.entrypoints=websecure" - "traefik.http.routers.bookstack.tls.certresolver=letsencrypt" - "traefik.http.routers.bookstack.middlewares=authelia@docker" @@ -107,7 +111,6 @@ services: - "homelab.description=BookStack database" # MediaWiki - Wiki platform - # Access at: https://mediawiki.kelinreij.duckdns.org mediawiki: image: mediawiki:latest container_name: mediawiki @@ -142,7 +145,7 @@ services: - "traefik.enable=true" - "traefik.docker.network=traefik-network" # Router configuration - - "traefik.http.routers.mediawiki.rule=Host(`mediawiki.kelinreij.duckdns.org`)" + - "traefik.http.routers.mediawiki.rule=Host(`mediawiki.${DOMAIN}`)" - "traefik.http.routers.mediawiki.entrypoints=websecure" - "traefik.http.routers.mediawiki.tls.certresolver=letsencrypt" - "traefik.http.routers.mediawiki.middlewares=authelia@docker" @@ -183,9 +186,9 @@ networks: x-dockge: urls: # Proxied URLs (through Traefik) - - https://bookstack.kelinreij.duckdns.org + - https://bookstack.${DOMAIN} - https://192.168.4.4:6875 - - https://dokuwiki.kelinreij.duckdns.org + - https://dokuwiki.${DOMAIN} - https://192.168.4.4:8087 - - https://mediawiki.kelinreij.duckdns.org + - https://mediawiki.${DOMAIN} - https://192.168.4.4:8086 diff --git a/ez-homelab-script-updates.md b/ez-homelab-script-updates.md new file mode 100644 index 0000000..1620667 --- /dev/null +++ b/ez-homelab-script-updates.md @@ -0,0 +1,146 @@ +I've reviewed the full script and compared it against the show_main_menu() function and the overall logic flow. The script appears to be a comprehensive setup and deployment tool for the EZ-Homelab project, but there are several inconsistencies, bugs, and areas that need updating to align with the new menu options and intended behavior. Below is a detailed audit, focusing on the menu-related changes and broader issues. + +1. Menu Structure and Logic Alignment +Current Menu Options (from show_main_menu()): +Install Prerequisites +Deploy Core Server +Deploy Additional Server +Install NVIDIA Drivers +The menu is clean and logical, with clear separation between system setup, core deployment, additional server setup, and optional NVIDIA support. +Code Handling: +Option 1: Sets FORCE_SYSTEM_SETUP=true, skips deployment, calls system_setup(), and exits. This is consistent. +Option 2: Sets DEPLOY_CORE=true, DEPLOY_INFRASTRUCTURE=true, DEPLOY_DASHBOARDS=true, SETUP_STACKS=true. Deploys everything for a core server. Consistent. +Option 3: Sets DEPLOY_CORE=false, DEPLOY_INFRASTRUCTURE=true, DEPLOY_DASHBOARDS=false, SETUP_STACKS=true. Deploys infrastructure and stacks (no core/dashboards) for additional servers. This makes sense for multi-server setups. +Option 4: Sets INSTALL_NVIDIA=true, skips deployment, logs a warning ("not yet implemented"), and exits. Placeholder only. +Issues: +The menu assumes Option 3 ("Deploy Additional Server") is for multi-server scenarios, but the script still prompts for all REQUIRED_VARS (e.g., DOMAIN, SERVER_IP) even for additional servers. For additional servers, some variables (like DOMAIN if it's core-specific) might not be needed, or SERVER_IP should be clarified as the additional server's IP. The code doesn't differentiate prompts based on deployment type, which could confuse users. +No command-line argument parsing (e.g., --debug, --verbose, or direct flags for deployment modes). The script logs args but doesn't use them, forcing interactive menu use. +The menu loop (while true) correctly handles invalid inputs and quits, but there's no way to bypass the menu (e.g., via env vars or args) for automated/scripted runs. +2. Bugs and Inconsistencies +Hardcoded Values in Completion Message: +Lines ~1520-1530: The success message hardcodes URLs like https://dockge.kelinreij.duckdns.org and https://homepage.kelinreij.duckdns.org. These should use ${DOMAIN} and ${SERVER_IP} for dynamic replacement. This is a critical bug as it won't work for other users/domains. +Double Setting of HOMEPAGE_ALLOWED_HOSTS: +In save_env_file() (lines ~630-633 and ~647-649): First sets HOMEPAGE_ALLOWED_HOSTS="${HOMEPAGE_SUBDOMAIN:-homepage}.${DOMAIN},${SERVER_IP}:3003", then overwrites it with HOMEPAGE_ALLOWED_HOSTS="homepage.${DOMAIN},${SERVER_IP}:3003". The second line ignores HOMEPAGE_SUBDOMAIN, causing inconsistency. Remove or merge the logic. +NVIDIA Driver Installation: +Option 4 is a placeholder with no implementation. The script should either implement basic NVIDIA setup (e.g., install drivers, CUDA) or remove the option to avoid user confusion. +Environment Variable Handling: +DEBUG and VERBOSE are set from env but not from command-line args. Add arg parsing (e.g., using getopts) to allow --debug, --verbose, etc. +In load_env_file_safely(), it exports vars but doesn't handle arrays or complex values well. No major issues, but could be improved for security (e.g., avoid logging secrets even in debug). +TLS and Multi-Server Logic: +For Option 3, setup_multi_server_tls() is called, which prompts for CORE_SERVER_IP if unset. This is good, but the script assumes SERVER_IP is the additional server's IP for TLS certs. Clarify in prompts or docs. +REQUIRED_VARS includes DOMAIN, but for additional servers, the domain might be inherited from core. The code doesn't skip prompts intelligently. +3. Code Structure and Messiness +Function Organization: +The script is long (1542 lines) and could be modularized (e.g., extract menu logic, validation, and deployment into separate functions or files). main() is bloated with inline logic. +Repeated patterns (e.g., copying .env files, replacing placeholders, chowning dirs) could be refactored into reusable functions. +Error Handling and Logging: +Good use of log_* functions, but debug_log only writes to file if DEBUG=true. Add console debug output for VERBOSE=true. +set -e is good, but some commands (e.g., docker network create) use || to ignore failures, which is inconsistent. +Deployment Flow: +After menu selection, the flow is: check system, prompt vars, save, validate, deploy. For Option 1/4, it exits early, which is fine. But for Options 2/3, it always runs enhance_placeholder_replacement() and validate_secrets(), even if not deploying core. validate_secrets() skips if DEPLOY_CORE=false, which is correct. +perform_deployment() has dynamic step numbering, but it's minor. +Security and Best Practices: +Uses sudo appropriately, but ACTUAL_USER logic is solid. +Secrets (e.g., passwords) are handled carefully, but AUTHELIA_ADMIN_PASSWORD generation could fail silently if Docker isn't available. +No input sanitization beyond basic validation in validate_variable(). +4. Recommendations for Updates +Align with Menu Changes: +Update prompts in validate_and_prompt_variables() to skip irrelevant vars for Option 3 (e.g., don't prompt for DOMAIN if deploying additional server, or clarify it's for core). +Add command-line support: Parse args to set flags directly (e.g., .[ez-homelab.sh](http://_vscodecontentref_/1) --deploy-core --verbose to bypass menu). +Implement Option 4: Add NVIDIA driver installation logic in system_setup() or a new function (check GPU, install drivers, reboot if needed). +Fix Bugs: +Replace hardcoded URLs with variables (e.g., https://dockge.${DOMAIN}). +Fix HOMEPAGE_ALLOWED_HOSTS duplication. +Add validation to ensure DOMAIN and SERVER_IP are set before deployment. +General Cleanup: +Break main() into smaller functions (e.g., handle_menu_choice(), prepare_deployment()). +Add a --help option and better error messages. +Test multi-server flow thoroughly, as setup_multi_server_tls() has complex SSH logic. +Testing: +The script lacks automated tests. Add checks for each menu path (e.g., dry-run mode). +Validate YAML syntax in perform_deployment() before deploying. +Overall, the script is functional but messy, with the menu changes exposing inconsistencies in variable prompting and hardcoded values. The core logic aligns with the menu, but updates are needed for robustness, especially for multi-server setups. If you provide more details on what specific "menu changes" broke, I can refine this audit. + + + + + + +Overview of Requested Changes +Based on your notes, this is a comprehensive refactoring of ez-homelab.sh to improve usability, fix bugs, and enhance functionality. The changes focus on modularizing the script, improving user experience (e.g., better prompts, error handling), and adding new features like argument parsing and dry-run mode. Key themes include separating concerns (e.g., moving system setup to a new script), clarifying multi-server logic, and making the script more robust and user-friendly. + +Scope and Impact: + +Lines of Code: The script is ~1542 lines; changes will add/modify ~200-400 lines, potentially splitting into multiple files. +Breaking Changes: Menu Option 1 now requires a separate script; variable prompts and logic for Option 3/multi-server will change; hardcoded values will be removed. +Testing Needs: High—test all menu options, argument parsing, dry-run, error scenarios, and multi-server TLS. Validate on different systems (with/without NVIDIA, core vs. additional servers). +Dependencies: Requires nvidia-detect (for Option 4), access to NVIDIA repos, and ensuring the new install-prerequisites.sh works across distros. +Estimated Effort: 10-15 hours for implementation, plus testing. Prioritize bugs and core menu fixes first. +Risks: Error handling changes could mask real issues; multi-server logic is complex—ensure backward compatibility. +The tasks are broken down into categories below, with priorities (High/Medium/Low) based on impact and dependencies. + +Task Breakdown +1. Menu and Workflow Updates (High Priority - Core User Experience) +Task 1.1: Create install-prerequisites.sh +Extract system_setup() and related logic into a new standalone script. Ensure it must run as root/sudo. Update Option 1 in ez-homelab.sh to detect if running as root; if not, use sudo to launch the new script. Handle cases where sudo isn't available (e.g., log error and exit). +Effort: 2-3 hours. Dependencies: None. Files: New install-prerequisites.sh, modify ez-homelab.sh. + +Task 1.2: Update Menu Option 3 Prompts +Ensure Option 3 prompts for all REQUIRED_VARS (no skipping). Reword prompts to clarify that variables like DOMAIN and SERVER_IP are for the local (additional) server, while REMOTE_SERVER_* vars are for the core server (e.g., "Enter the IP of the core server where Traefik is running"). Keep existing variable names but improve descriptions. +Effort: 1-2 hours. Dependencies: Review .env.example for REMOTE_SERVER context. Files: Modify validate_and_prompt_variables() and prompt_for_variable() in ez-homelab.sh. + +Task 1.3: Implement Menu Option 4 (NVIDIA Installation) +Use nvidia-detect to identify the GPU and determine the official NVIDIA installer. Install NVIDIA drivers and the NVIDIA Container Toolkit (for Docker GPU support). Handle errors gracefully (e.g., if no GPU detected, skip). Integrate into system_setup() or a new function. +Effort: 3-4 hours. Dependencies: Install nvidia-detect if needed; test on systems with/without NVIDIA GPUs. Files: Modify/add functions in ez-homelab.sh or install-prerequisites.sh. + +2. Bug Fixes (High Priority - Prevents User Errors) +Task 2.1: Remove Hardcoded Values +Replace all instances of "kelin", "kelinreij", "kelin-casa", etc., with appropriate variables (e.g., ${DOMAIN}, ${SERVER_IP}). Update completion messages, URLs, and any examples. +Effort: 1 hour. Dependencies: Search the script for these strings. Files: ez-homelab.sh. + +Task 2.2: Fix HOMEPAGE_ALLOWED_HOSTS +Remove HOMEPAGE_SUBDOMAIN references. Ensure the line is exactly HOMEPAGE_ALLOWED_HOSTS="homepage.${DOMAIN},${SERVER_IP}:3003" (no duplication or overwrites). +Effort: 30 minutes. Dependencies: Check save_env_file(). Files: ez-homelab.sh. + +3. New Features and Enhancements (Medium Priority - Improves Script Flexibility) +Task 3.1: Add Argument Parsing +Implement command-line argument support (e.g., using getopts or argparse if Bash allows). Support flags like --deploy-core, --deploy-additional, --install-nvidia, --dry-run, --verbose, --debug, --help. Allow bypassing the menu for automated runs. +Effort: 2-3 hours. Dependencies: None. Files: Add to main() in ez-homelab.sh. + +Task 3.2: Add Dry-Run Mode +Implement --dry-run flag: Simulate deployment without making changes (e.g., validate configs, show what would be done, but don't run docker compose up). Log actions verbosely. +Effort: 2 hours. Dependencies: Argument parsing. Files: Modify perform_deployment() and related functions in ez-homelab.sh. + +Task 3.3: Enhance Console Logging for Verbose Mode +Update log_* functions to output to console when VERBOSE=true (not just debug file). Ensure debug logs go to both console and file. +Effort: 1 hour. Dependencies: None. Files: ez-homelab.sh. + +Task 3.4: Improve Error Handling +Change set -e to allow continuation on errors (log warnings/errors but proceed). Use || consistently for non-critical failures (e.g., network creation). Add try-catch-like logic where possible. +Effort: 2 hours. Dependencies: Review all exit 1 points. Files: ez-homelab.sh. + +4. TLS and Multi-Server Logic Refinements (Medium Priority - Complex but Critical for Multi-Server) +Task 4.1: Clarify Variable Usage +Update prompts and logic to distinguish: IP_ADDRESS/SERVER_IP for local machine; REMOTE_SERVER_* for core server. Ensure ${DOMAIN} is prompted even for additional servers (as it's needed for configs). Reword setup_multi_server_tls() to emphasize cert copying from core. +Effort: 1-2 hours. Dependencies: Task 1.2. Files: ez-homelab.sh. +5. Function Organization and Code Quality (Low Priority - Refactoring for Maintainability) +Task 5.1: Audit and Improve Placeholder/Env Functions +Review replace_env_placeholders() and enhance_placeholder_replacement() for quality (e.g., handle edge cases, improve performance). Refactor if needed. +Effort: 1-2 hours. Dependencies: None. Files: ez-homelab.sh. + +Task 5.2: Modularize Code with More Functions +Break main() into smaller functions (e.g., parse_args(), handle_menu_choice(), prepare_deployment()). Extract repeated logic (e.g., env copying, dir creation) into helpers. +Effort: 3-4 hours. Dependencies: All other tasks. Files: ez-homelab.sh. + +Task 5.3: Fix Deployment Flow +Streamline perform_deployment(): Ensure consistent step numbering, better error recovery, and integration with dry-run. +Effort: 1 hour. Dependencies: Tasks 3.2, 3.4. Files: ez-homelab.sh. + +Implementation Order and Next Steps +Start with High-Priority Tasks: Bugs (2.1-2.2) and Menu Option 1 (1.1) to fix immediate issues. +Then Core Features: Argument parsing (3.1), dry-run (3.2), and Option 4 (1.3). +Refinements: Error handling (3.4), logging (3.3), and multi-server (4.1). +Cleanup: Function organization (5.x) last, as it depends on other changes. +Testing Plan: After each task, test interactively and with args. Use dry-run for validation. +Questions for You: Confirm if install-prerequisites.sh should be in the same repo. Any specific NVIDIA installer versions to target? Should dry-run output be machine-readable (e.g., JSON)? +Let me know if you'd like me to start implementing any tasks or need more details! \ No newline at end of file diff --git a/scripts/ez-homelab.sh b/scripts/ez-homelab.sh index 5f750de..37c1990 100755 --- a/scripts/ez-homelab.sh +++ b/scripts/ez-homelab.sh @@ -1,10 +1,7 @@ #!/bin/bash # EZ-Homelab Unified Setup & Deployment Script -# Two step process required for first-time setup: -# Run 'sudo ./ez-homelab.sh' to install Docker and perform system setup -# Run './ez-homelab.sh' to deploy stacks after initial setup -set -e # Exit on error +# Removed set -e to allow graceful error handling # Debug logging configuration DEBUG=${DEBUG:-false} @@ -95,25 +92,46 @@ load_env_file_safely() { debug_log "Env file loaded successfully" } -replace_env_placeholders() { +load_env_file() { + load_env_file_safely "$REPO_DIR/.env" +} +localize_yml_file() { local file_path="$1" local fail_on_missing="${2:-false}" # New parameter to control failure behavior local missing_vars="" local replaced_count=0 - debug_log "replace_env_placeholders called for file: $file_path, fail_on_missing: $fail_on_missing" + debug_log "localize_yml_file called for file: $file_path, fail_on_missing: $fail_on_missing" if [ ! -f "$file_path" ]; then - log_warning "File $file_path does not exist, skipping placeholder replacement" + log_warning "File $file_path does not exist, skipping YAML localization" debug_log "File $file_path does not exist" return fi + # Check if file is writable + if [ ! -w "$file_path" ]; then + log_error "File $file_path is not writable, cannot localize" + debug_log "Permission denied for $file_path" + if [ "$fail_on_missing" = true ]; then + exit 1 + fi + return + fi + + # Backup only if target file already exists (not for repo sources) + if [[ "$file_path" != "$REPO_DIR"* ]] && [ -f "$file_path" ]; then + cp "$file_path" "$file_path.backup.$(date +%Y%m%d_%H%M%S)" + debug_log "Backed up $file_path" + fi + # Find all ${VAR} patterns in the file local vars=$(grep -o '\${[^}]*}' "$file_path" | sed 's/\${//' | sed 's/}//' | sort | uniq) debug_log "Found variables to replace: $vars" for var in $vars; do + # Trim whitespace from variable name + var=$(echo "$var" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') # Skip derived variables that should not be replaced case "$var" in "ACME_EMAIL"|"AUTHELIA_ADMIN_EMAIL"|"SMTP_USERNAME"|"SMTP_PASSWORD") @@ -122,23 +140,46 @@ replace_env_placeholders() { ;; esac - if [ -z "${!var:-}" ]; then + if [ -z "${!var+x}" ]; then log_warning "Environment variable $var not found in .env file" debug_log "Missing variable: $var" missing_vars="$missing_vars $var" else - # Replace ${VAR} with the value + # Replace ${VAR} with the value, handling special characters + local escaped_value=$(printf '%s\n' "${!var}" | sed 's/[[\.*^$()+?{|]/\\&/g') debug_log "Replacing \${$var} with value: [HIDDEN]" # Don't log actual secrets - sed -i "s|\${$var}|${!var}|g" "$file_path" + sed -i "s|\${[ \t]*${var}[ \t]*}|${escaped_value}|g" "$file_path" replaced_count=$((replaced_count + 1)) fi done debug_log "Replaced $replaced_count variables in $file_path" + # Post-replacement validation: check for remaining ${VAR} (except skipped) + local remaining_vars=$(grep -v '^[[:space:]]*#' "$file_path" | grep -o '\${[^}]*}' | sed 's/\${//' | sed 's/}//' | sort | uniq) + local invalid_remaining="" + for rvar in $remaining_vars; do + rvar=$(echo "$rvar" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + case "$rvar" in + "ACME_EMAIL"|"AUTHELIA_ADMIN_EMAIL"|"SMTP_USERNAME"|"SMTP_PASSWORD") + continue + ;; + *) + invalid_remaining="$invalid_remaining $rvar" + ;; + esac + done + if [ -n "$invalid_remaining" ]; then + log_error "Failed to replace variables in $file_path: $invalid_remaining" + debug_log "Unreplaced variables: $invalid_remaining" + if [ "$fail_on_missing" = true ]; then + exit 1 + fi + fi + # Handle missing variables if [ -n "$missing_vars" ]; then - MISSING_VARS_SUMMARY="${MISSING_VARS_SUMMARY}${missing_vars}" + GLOBAL_MISSING_VARS="${GLOBAL_MISSING_VARS}${missing_vars}" if [ "$fail_on_missing" = true ]; then log_error "Critical environment variables missing: $missing_vars" debug_log "Failing deployment due to missing critical variables: $missing_vars" @@ -148,17 +189,18 @@ replace_env_placeholders() { } # Enhanced placeholder replacement for all configuration files -enhance_placeholder_replacement() { - log_info "Starting enhanced placeholder replacement..." +localize_deployment() { + log_info "Starting deployment localization..." local processed_files=0 + GLOBAL_MISSING_VARS="" # Process docker-compose files if [ -d "$REPO_DIR/docker-compose" ]; then while IFS= read -r -d '' file_path; do if [ -f "$file_path" ]; then debug_log "Processing docker-compose file: $file_path" - replace_env_placeholders "$file_path" false + localize_yml_file "$file_path" false processed_files=$((processed_files + 1)) fi done < <(find "$REPO_DIR/docker-compose" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null) @@ -169,14 +211,20 @@ enhance_placeholder_replacement() { while IFS= read -r -d '' file_path; do if [ -f "$file_path" ]; then debug_log "Processing config template file: $file_path" - replace_env_placeholders "$file_path" false + localize_yml_file "$file_path" false processed_files=$((processed_files + 1)) fi done < <(find "$REPO_DIR/config-templates" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null) fi - log_success "Enhanced placeholder replacement completed - processed $processed_files files" - debug_log "Enhanced replacement completed for $processed_files files" + log_success "Deployment localization completed - processed $processed_files files" + debug_log "Localization completed for $processed_files files" + + # Report aggregated missing variables + if [ -n "$GLOBAL_MISSING_VARS" ]; then + log_warning "Aggregated missing environment variables across all files: $GLOBAL_MISSING_VARS" + debug_log "Global missing vars: $GLOBAL_MISSING_VARS" + fi } # Function to generate shared CA for multi-server TLS @@ -627,9 +675,12 @@ save_env_file() { # Update HOMEPAGE_ALLOWED_HOSTS dynamically if [ -n "${DOMAIN:-}" ] && [ -n "${SERVER_IP:-}" ]; then - # Allow user to specify homepage subdomain - HOMEPAGE_SUBDOMAIN="${HOMEPAGE_SUBDOMAIN:-homepage}" - HOMEPAGE_ALLOWED_HOSTS="${HOMEPAGE_SUBDOMAIN}.${DOMAIN},${SERVER_IP}:3003" + # Extract Homepage port from compose file + HOMEPAGE_PORT=$(grep -A1 'ports:' "$REPO_DIR/docker-compose/dashboards/docker-compose.yml" | grep -o '"[0-9]*:3000"' | cut -d'"' -f2 | cut -d: -f1) + if [ -z "$HOMEPAGE_PORT" ]; then + HOMEPAGE_PORT=3003 # Fallback + fi + HOMEPAGE_ALLOWED_HOSTS="homepage.${DOMAIN},${SERVER_IP}:${HOMEPAGE_PORT}" sudo -u "$ACTUAL_USER" sed -i "s|HOMEPAGE_ALLOWED_HOSTS=.*|HOMEPAGE_ALLOWED_HOSTS=$HOMEPAGE_ALLOWED_HOSTS|" "$REPO_DIR/.env" fi @@ -669,28 +720,24 @@ save_env_file() { sudo -u "$ACTUAL_USER" sed -i "s%AUTHELIA_ADMIN_EMAIL=.*%AUTHELIA_ADMIN_EMAIL=$ADMIN_EMAIL%" "$REPO_DIR/.env" # Generate password hash if needed - if [ -z "$AUTHELIA_ADMIN_PASSWORD" ]; then + if [ -z "$AUTHELIA_ADMIN_PASSWORD_HASH" ]; then log_info "Generating Authelia password hash..." # Pull Authelia image if needed if ! docker images | grep -q authelia/authelia; then docker pull authelia/authelia:latest > /dev/null 2>&1 fi - AUTHELIA_ADMIN_PASSWORD=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | grep -o '\$argon2id.*') - if [ -z "$AUTHELIA_ADMIN_PASSWORD" ]; then + AUTHELIA_ADMIN_PASSWORD_HASH=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | grep -o '\$argon2id.*') + if [ -z "$AUTHELIA_ADMIN_PASSWORD_HASH" ]; then log_error "Failed to generate Authelia password hash. Please check that ADMIN_PASSWORD is set." exit 1 fi fi # Save password hash - sudo -u "$ACTUAL_USER" sed -i "s%# AUTHELIA_ADMIN_PASSWORD=.*%AUTHELIA_ADMIN_PASSWORD=\"$AUTHELIA_ADMIN_PASSWORD\"%" "$REPO_DIR/.env" - sudo -u "$ACTUAL_USER" sed -i "s%AUTHELIA_ADMIN_PASSWORD=.*%AUTHELIA_ADMIN_PASSWORD=\"$AUTHELIA_ADMIN_PASSWORD\"%" "$REPO_DIR/.env" + sudo -u "$ACTUAL_USER" sed -i "s%# AUTHELIA_ADMIN_PASSWORD_HASH=.*%AUTHELIA_ADMIN_PASSWORD_HASH=\"$AUTHELIA_ADMIN_PASSWORD_HASH\"%" "$REPO_DIR/.env" + sudo -u "$ACTUAL_USER" sed -i "s%AUTHELIA_ADMIN_PASSWORD_HASH=.*%AUTHELIA_ADMIN_PASSWORD_HASH=\"$AUTHELIA_ADMIN_PASSWORD_HASH\"%" "$REPO_DIR/.env" fi - # Update HOMEPAGE_ALLOWED_HOSTS with expanded values - HOMEPAGE_ALLOWED_HOSTS="homepage.${DOMAIN},${SERVER_IP}:3003" - sudo -u "$ACTUAL_USER" sed -i "s|HOMEPAGE_ALLOWED_HOSTS=.*|HOMEPAGE_ALLOWED_HOSTS=$HOMEPAGE_ALLOWED_HOSTS|" "$REPO_DIR/.env" - debug_log "Configuration saved to .env file" log_success "Configuration saved to .env file" } @@ -725,9 +772,9 @@ validate_secrets() { debug_log "AUTHELIA_STORAGE_ENCRYPTION_KEY is missing" fi - if [ -z "${AUTHELIA_ADMIN_PASSWORD:-}" ]; then - missing_secrets="$missing_secrets AUTHELIA_ADMIN_PASSWORD" - debug_log "AUTHELIA_ADMIN_PASSWORD is missing" + if [ -z "${AUTHELIA_ADMIN_PASSWORD_HASH:-}" ]; then + missing_secrets="$missing_secrets AUTHELIA_ADMIN_PASSWORD_HASH" + debug_log "AUTHELIA_ADMIN_PASSWORD_HASH is missing" fi # Check other required variables @@ -752,100 +799,42 @@ validate_secrets() { debug_log "Secret validation passed" } -# System setup function (Docker, directories, etc.) -system_setup() { - log_info "Performing system setup..." +# Install NVIDIA drivers function +install_nvidia() { + log_info "Installing NVIDIA drivers and Docker support..." - # Check if running as root for system setup + # Check if running as root if [ "$EUID" -ne 0 ]; then - log_warning "System setup requires root privileges. Running with sudo..." + log_warning "NVIDIA installation requires root privileges. Running with sudo..." exec sudo "$0" "$@" fi - # Get the actual user who invoked sudo - ACTUAL_USER=${SUDO_USER:-$USER} - - # Step 1: System Update - log_info "Step 1/10: Updating system packages..." - apt-get update && apt-get upgrade -y - log_success "System updated successfully" - - # Step 2: Install required packages - log_info "Step 2/10: Installing required packages..." - apt-get install -y curl wget git htop nano vim ufw fail2ban unattended-upgrades apt-listchanges sshpass - - # Step 3: Install Docker - log_info "Step 3/10: Installing Docker..." - if command -v docker &> /dev/null && docker --version &> /dev/null; then - log_success "Docker is already installed ($(docker --version))" - # Check if user is in docker group - if ! groups "$ACTUAL_USER" | grep -q docker; then - log_info "Adding $ACTUAL_USER to docker group..." - usermod -aG docker "$ACTUAL_USER" - NEEDS_LOGOUT=true - fi - # Check if Docker service is running - if ! systemctl is-active --quiet docker; then - log_warning "Docker service is not running, starting it..." - systemctl start docker - systemctl enable docker - log_success "Docker service started and enabled" - else - log_info "Docker service is already running" - fi - else - curl -fsSL https://get.docker.com | sh - usermod -aG docker "$ACTUAL_USER" - NEEDS_LOGOUT=true + # Check for NVIDIA GPU + if ! lspci | grep -i nvidia > /dev/null; then + log_warning "No NVIDIA GPU detected. Skipping NVIDIA driver installation." + return fi - # Step 4: Install Docker Compose - log_info "Step 4/10: Installing Docker Compose..." - if command -v docker-compose &> /dev/null && docker-compose --version &> /dev/null; then - log_success "Docker Compose is already installed ($(docker-compose --version))" - else - curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose - log_success "Docker Compose installed ($(docker-compose --version))" - fi + # Add NVIDIA repository + log_info "Adding NVIDIA repository..." + apt-get update + apt-get install -y software-properties-common + add-apt-repository -y ppa:graphics-drivers/ppa + apt-get update - # Step 5: Generate shared CA for multi-server TLS - log_info "Step 5/10: Generating shared CA certificate for multi-server TLS..." - generate_shared_ca + # Install NVIDIA drivers (latest) + log_info "Installing NVIDIA drivers..." + apt-get install -y nvidia-driver-470 # Adjust version as needed - # Step 6: Configure Docker TLS - log_info "Step 6/10: Configuring Docker TLS..." - setup_docker_tls + # Install NVIDIA Docker support + log_info "Installing NVIDIA Docker support..." + distribution=$(. /etc/os-release;echo $ID$VERSION_ID) + curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | apt-key add - + curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | tee /etc/apt/sources.list.d/nvidia-docker.list + apt-get update && apt-get install -y nvidia-docker2 + systemctl restart docker - # Step 7: Configure UFW firewall - log_info "Step 7/10: Configuring firewall..." - ufw --force enable - ufw allow ssh - ufw allow 80 - ufw allow 443 - ufw allow 2376/tcp # Docker TLS port - log_success "Firewall configured" - - # Step 8: Configure automatic updates - log_info "Step 8/10: Configuring automatic updates..." - dpkg-reconfigure -f noninteractive unattended-upgrades - - # Step 10: Create Docker networks - log_info "Step 10/10: 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" - - # Step 9: Set proper ownership - log_info "Step 9/10: Setting directory ownership..." - chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt - - log_success "System setup completed!" - echo "" - if [ "$NEEDS_LOGOUT" = true ]; then - log_info "Please log out and back in for Docker group changes to take effect." - echo "" - fi + log_success "NVIDIA drivers and Docker support installed. A reboot may be required." } # Deploy Dockge function @@ -861,17 +850,17 @@ deploy_dockge() { sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge/.env # Remove sensitive variables from dockge .env (Dockge doesn't need them) - sed -i '/^AUTHELIA_ADMIN_PASSWORD=/d' /opt/dockge/.env + sed -i '/^AUTHELIA_ADMIN_PASSWORD_HASH=/d' /opt/dockge/.env sed -i '/^AUTHELIA_JWT_SECRET=/d' /opt/dockge/.env sed -i '/^AUTHELIA_SESSION_SECRET=/d' /opt/dockge/.env sed -i '/^AUTHELIA_STORAGE_ENCRYPTION_KEY=/d' /opt/dockge/.env # Replace placeholders in Dockge compose file - replace_env_placeholders "/opt/dockge/docker-compose.yml" + localize_yml_file "/opt/dockge/docker-compose.yml" # Deploy Dockge stack cd /opt/dockge - docker compose up -d + run_cmd docker compose up -d || true log_success "Dockge deployed" echo "" } @@ -908,7 +897,7 @@ deploy_core() { sed -i '/^HOMEPAGE_VAR_/d' /opt/stacks/core/.env # Replace placeholders in core compose file (fail on missing critical vars) - replace_env_placeholders "/opt/stacks/core/docker-compose.yml" true + localize_yml_file "/opt/stacks/core/docker-compose.yml" true # Copy and configure Traefik config debug_log "Setting up Traefik configuration" @@ -943,9 +932,9 @@ deploy_core() { find /opt/stacks/core/traefik -name "*.yml" -type f | while read -r config_file; do # Don't fail on missing variables for external host files (they're optional) if [[ "$config_file" == *external-host* ]]; then - replace_env_placeholders "$config_file" false + localize_yml_file "$config_file" false else - replace_env_placeholders "$config_file" true + localize_yml_file "$config_file" true fi done @@ -966,7 +955,7 @@ deploy_core() { # Replace all placeholders in Authelia config files debug_log "Replacing placeholders in Authelia config files" find /opt/stacks/core/authelia -name "*.yml" -type f | while read -r config_file; do - replace_env_placeholders "$config_file" true + localize_yml_file "$config_file" true done # Remove invalid session.cookies section from Authelia config (not supported in v4.37.5) @@ -988,7 +977,7 @@ deploy_core() { # Deploy core stack debug_log "Deploying core stack with docker compose" cd /opt/stacks/core - docker compose up -d + run_cmd docker compose up -d || true log_success "Core infrastructure deployed" echo "" } @@ -1026,7 +1015,7 @@ deploy_infrastructure() { sed -i '/^HOMEPAGE_VAR_/d' /opt/stacks/infrastructure/.env # Replace placeholders in infrastructure compose file - replace_env_placeholders "/opt/stacks/infrastructure/docker-compose.yml" + localize_yml_file "/opt/stacks/infrastructure/docker-compose.yml" # Copy any additional config directories for config_dir in "$REPO_DIR/docker-compose/infrastructure"/*/; do @@ -1042,11 +1031,11 @@ deploy_infrastructure() { fi # Replace placeholders in infrastructure compose file - replace_env_placeholders "/opt/stacks/infrastructure/docker-compose.yml" + localize_yml_file "/opt/stacks/infrastructure/docker-compose.yml" # Deploy infrastructure stack cd /opt/stacks/infrastructure - docker compose up -d + run_cmd docker compose up -d || true log_success "Infrastructure stack deployed" echo "" } @@ -1082,7 +1071,7 @@ deploy_dashboards() { sed -i '/^FORMIO_/d' /opt/stacks/dashboards/.env # Replace placeholders in dashboards compose file - replace_env_placeholders "/opt/stacks/dashboards/docker-compose.yml" + localize_yml_file "/opt/stacks/dashboards/docker-compose.yml" # Copy homepage config if [ -d "$REPO_DIR/docker-compose/dashboards/homepage" ]; then @@ -1091,7 +1080,7 @@ deploy_dashboards() { # Replace placeholders in homepage config files find /opt/stacks/dashboards/homepage -name "*.yaml" -type f | while read -r config_file; do - replace_env_placeholders "$config_file" + localize_yml_file "$config_file" done # Remove remote server entries from homepage services for single-server setup @@ -1102,7 +1091,7 @@ deploy_dashboards() { # Process template files and rename them find /opt/stacks/dashboards/homepage -name "*.template" -type f | while read -r template_file; do - replace_env_placeholders "$template_file" + localize_yml_file "$template_file" # Rename template file to remove .template extension new_file="${template_file%.template}" mv "$template_file" "$new_file" @@ -1111,11 +1100,11 @@ deploy_dashboards() { fi # Replace placeholders in dashboards compose file - replace_env_placeholders "/opt/stacks/dashboards/docker-compose.yml" + localize_yml_file "/opt/stacks/dashboards/docker-compose.yml" # Deploy dashboards stack cd /opt/stacks/dashboards - docker compose up -d + run_cmd docker compose up -d || true log_success "Dashboard stack deployed" echo "" } @@ -1126,7 +1115,7 @@ perform_deployment() { log_info "Starting deployment..." # Initialize missing vars summary - MISSING_VARS_SUMMARY="" + GLOBAL_MISSING_VARS="" TLS_ISSUES_SUMMARY="" # Switch back to regular user if we were running as root @@ -1142,6 +1131,22 @@ perform_deployment() { load_env_file_safely "$REPO_DIR/.env" debug_log "Environment loaded, DOMAIN=$DOMAIN, SERVER_IP=$SERVER_IP" + # Generate Authelia password hash if needed + if [ "$AUTHELIA_ADMIN_PASSWORD_HASH" = "generate-with-openssl-rand-hex-64" ] || [ -z "$AUTHELIA_ADMIN_PASSWORD_HASH" ]; then + log_info "Generating Authelia password hash..." + if ! docker images | grep -q authelia/authelia; then + docker pull authelia/authelia:latest > /dev/null 2>&1 + fi + AUTHELIA_ADMIN_PASSWORD_HASH=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$DEFAULT_PASSWORD" 2>&1 | grep -o '\$argon2id.*') + if [ -z "$AUTHELIA_ADMIN_PASSWORD_HASH" ]; then + log_error "Failed to generate Authelia password hash." + exit 1 + fi + # Save it back to .env + sed -i "s%AUTHELIA_ADMIN_PASSWORD_HASH=.*%AUTHELIA_ADMIN_PASSWORD_HASH=\"$AUTHELIA_ADMIN_PASSWORD_HASH\"%" "$REPO_DIR/.env" + log_success "Authelia password hash generated and saved" + fi + # Step 1: Create required directories log_info "Step 1: Creating required directories..." sudo mkdir -p /opt/stacks/core || { log_error "Failed to create /opt/stacks/core"; exit 1; } @@ -1196,9 +1201,9 @@ perform_deployment() { fi # Report any missing variables - if [ -n "$MISSING_VARS_SUMMARY" ]; then + if [ -n "$GLOBAL_MISSING_VARS" ]; then log_warning "The following environment variables were missing and may cause issues:" - echo "$MISSING_VARS_SUMMARY" + echo "$GLOBAL_MISSING_VARS" log_info "Please update your .env file and redeploy affected stacks." fi @@ -1275,7 +1280,7 @@ setup_stacks_for_dockge() { sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$STACK_DIR/.env" # Remove sensitive/unnecessary variables from stack .env - sed -i '/^AUTHELIA_ADMIN_PASSWORD=/d' "$STACK_DIR/.env" + sed -i '/^AUTHELIA_ADMIN_PASSWORD_HASH=/d' "$STACK_DIR/.env" sed -i '/^AUTHELIA_JWT_SECRET=/d' "$STACK_DIR/.env" sed -i '/^AUTHELIA_SESSION_SECRET=/d' "$STACK_DIR/.env" sed -i '/^AUTHELIA_STORAGE_ENCRYPTION_KEY=/d' "$STACK_DIR/.env" @@ -1300,13 +1305,18 @@ setup_stacks_for_dockge() { sed -i '/^HOMEPAGE_VAR_/d' "$STACK_DIR/.env" # Replace placeholders in the compose file - replace_env_placeholders "$STACK_DIR/docker-compose.yml" + localize_yml_file "$STACK_DIR/docker-compose.yml" # Copy any additional config directories for config_dir in "$REPO_STACK_DIR"/*/; do if [ -d "$config_dir" ] && [ "$(basename "$config_dir")" != "." ]; then cp -r "$config_dir" "$STACK_DIR/" sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$STACK_DIR/$(basename "$config_dir")" + + # Replace placeholders in config files + find "$STACK_DIR/$(basename "$config_dir")" -name "*.yml" -o -name "*.yaml" | while read -r config_file; do + localize_yml_file "$config_file" + done fi done @@ -1340,12 +1350,226 @@ show_main_menu() { echo "" } +# Show help function +show_help() { + echo "EZ-Homelab Setup & Deployment Script" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -d, --dry-run Enable dry-run mode (show commands without executing)" + echo " -c, --config FILE Specify configuration file (default: .env)" + echo " -t, --test Run in test mode (validate configs without deploying)" + echo " -v, --validate-only Only validate configuration and exit" + echo " --verbose Enable verbose console logging" + echo "" + echo "If no options are provided, the interactive menu will be shown." + echo "" +} + +# Validate configuration function +validate_configuration() { + log_info "Validating configuration..." + + # Check if .env file exists + if [ ! -f ".env" ]; then + log_error "Configuration file .env not found." + return 1 + fi + + # Load and check required environment variables + if ! load_env_file; then + log_error "Failed to load .env file." + return 1 + fi + + # Check for critical variables + local required_vars=("DOMAIN" "SERVER_IP" "DUCKDNS_TOKEN" "AUTHELIA_ADMIN_PASSWORD_HASH") + local missing_vars=() + for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + missing_vars+=("$var") + fi + done + + if [ ${#missing_vars[@]} -gt 0 ]; then + log_error "Missing required environment variables: ${missing_vars[*]}" + return 1 + fi + + # Check Docker Compose files syntax + log_info "Checking Docker Compose file syntax..." + if command -v docker-compose &> /dev/null; then + if ! docker-compose -f docker-compose/core/docker-compose.yml config -q; then + log_error "Invalid syntax in core docker-compose.yml" + return 1 + fi + log_success "Core docker-compose.yml syntax is valid" + else + log_warning "docker-compose not available for syntax check" + fi + + # Check network connectivity (basic) + log_info "Checking network connectivity..." + if ! ping -c 1 google.com &> /dev/null; then + log_warning "No internet connectivity detected" + else + log_success "Internet connectivity confirmed" + fi + + log_success "Configuration validation completed successfully" +} + +# Parse command line arguments function +parse_args() { + DRY_RUN=false + CONFIG_FILE=".env" + TEST_MODE=false + VALIDATE_ONLY=false + VERBOSE=false + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + exit 0 + ;; + -d|--dry-run) + DRY_RUN=true + shift + ;; + -c|--config) + CONFIG_FILE="$2" + shift 2 + ;; + -t|--test) + TEST_MODE=true + shift + ;; + -v|--validate-only) + VALIDATE_ONLY=true + shift + ;; + --verbose) + VERBOSE=true + shift + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac + done +} + +# Prepare deployment environment +prepare_deployment() { + # Handle special menu options + if [ "$FORCE_SYSTEM_SETUP" = true ]; then + log_info "Installing prerequisites..." + # Run the prerequisites script as root + if [ "$EUID" -eq 0 ]; then + ./scripts/install-prerequisites.sh + else + sudo ./scripts/install-prerequisites.sh + fi + log_success "Prerequisites installed successfully." + exit 0 + fi + + if [ "$INSTALL_NVIDIA" = true ]; then + log_info "Installing NVIDIA drivers..." + install_nvidia + exit 0 + fi + + # Check if system setup is needed + # Only run system setup if Docker is not installed OR if running as root and Docker setup hasn't been done + DOCKER_INSTALLED=false + if command -v docker &> /dev/null && docker --version &> /dev/null; then + DOCKER_INSTALLED=true + fi + + # Check if current user is in docker group (or if we're root and will add them) + USER_IN_DOCKER_GROUP=false + if groups "$USER" 2>/dev/null | grep -q docker; then + USER_IN_DOCKER_GROUP=true + fi + + if [ "$EUID" -eq 0 ]; then + # Running as root - check if we need to do system setup + if [ "$DOCKER_INSTALLED" = false ] || [ "$USER_IN_DOCKER_GROUP" = false ]; then + log_info "Docker not fully installed or user not in docker group. Performing system setup..." + ./scripts/install-prerequisites.sh + echo "" + log_info "System setup complete. Please log out and back in, then run this script again." + exit 0 + else + log_info "Docker is already installed and user is in docker group. Skipping system setup." + fi + else + # Not running as root + if [ "$DOCKER_INSTALLED" = false ]; then + log_error "Docker is not installed. Please run this script with sudo to perform system setup." + exit 1 + fi + if [ "$USER_IN_DOCKER_GROUP" = false ]; then + log_error "Current user is not in the docker group. Please log out and back in, or run with sudo to fix group membership." + exit 1 + fi + fi + + # Ensure required directories exist + log_info "Ensuring required directories exist..." + if [ "$EUID" -eq 0 ]; then + ACTUAL_USER=${SUDO_USER:-$USER} + mkdir -p /opt/stacks /opt/dockge + chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt + else + mkdir -p /opt/stacks /opt/dockge + fi + log_success "Directories prepared" +} + +# Run command function (handles dry-run and test modes) +run_cmd() { + if [ "$DRY_RUN" = true ] || [ "$TEST_MODE" = true ]; then + echo "[DRY-RUN/TEST] $@" + return 0 + else + if "$@"; then + return 0 + else + log_error "Command failed: $@" + return 1 + fi + fi +} + # Main logic main() { debug_log "main() called with arguments: $@" log_info "EZ-Homelab Unified Setup & Deployment Script" echo "" + # Parse command line arguments + parse_args "$@" + + if [ "$DRY_RUN" = true ]; then + log_info "Dry-run mode enabled. Commands will be displayed but not executed." + fi + + if [ "$VALIDATE_ONLY" = true ]; then + log_info "Validation mode enabled. Checking configuration..." + validate_configuration + exit 0 + fi + + if [ "$TEST_MODE" = true ]; then + log_info "Test mode enabled. Will validate and simulate deployment." + fi + # Load existing configuration ENV_EXISTS=false if load_env_file; then @@ -1381,6 +1605,18 @@ main() { ;; 3) log_info "Selected: Deploy Additional Server" + echo "" + echo "⚠️ IMPORTANT: Deploying an additional server requires an existing core server to be already deployed." + echo "The core server provides essential services like Traefik, Authelia, and shared TLS certificates." + echo "" + read -p "Do you have an existing core server deployed? (y/N): " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Returning to main menu. Please deploy a core server first using Option 2." + echo "" + sleep 2 + continue + fi DEPLOY_CORE=false DEPLOY_INFRASTRUCTURE=true DEPLOY_DASHBOARDS=false @@ -1411,71 +1647,8 @@ main() { echo "" - # Handle special menu options - if [ "$FORCE_SYSTEM_SETUP" = true ]; then - log_info "Installing prerequisites..." - system_setup "$@" - log_success "Prerequisites installed successfully." - exit 0 - fi - - if [ "$INSTALL_NVIDIA" = true ]; then - log_info "Installing NVIDIA drivers..." - # TODO: Implement NVIDIA driver installation - log_warning "NVIDIA driver installation not yet implemented." - exit 0 - fi - - # Check if system setup is needed - # Only run system setup if Docker is not installed OR if running as root and Docker setup hasn't been done - DOCKER_INSTALLED=false - if command -v docker &> /dev/null && docker --version &> /dev/null; then - DOCKER_INSTALLED=true - fi - - # Check if current user is in docker group (or if we're root and will add them) - USER_IN_DOCKER_GROUP=false - if groups "$USER" 2>/dev/null | grep -q docker; then - USER_IN_DOCKER_GROUP=true - fi - - if [ "$EUID" -eq 0 ]; then - # Running as root - check if we need to do system setup - if [ "$DOCKER_INSTALLED" = false ] || [ "$USER_IN_DOCKER_GROUP" = false ]; then - log_info "Docker not fully installed or user not in docker group. Performing system setup..." - system_setup "$@" - echo "" - log_info "System setup complete. Please log out and back in, then run this script again." - exit 0 - else - log_info "Docker is already installed and user is in docker group. Skipping system setup." - fi - else - # Not running as root - if [ "$DOCKER_INSTALLED" = false ]; then - log_error "Docker is not installed. Please run this script with sudo to perform system setup." - exit 1 - fi - if [ "$USER_IN_DOCKER_GROUP" = false ]; then - log_error "Current user is not in the docker group. Please log out and back in, or run with sudo to fix group membership." - exit 1 - fi - fi - - # Ensure required directories exist - log_info "Ensuring required directories exist..." - if [ "$EUID" -eq 0 ]; then - mkdir -p /opt/stacks/core - mkdir -p /opt/stacks/infrastructure - mkdir -p /opt/stacks/dashboards - mkdir -p /opt/dockge - else - sudo mkdir -p /opt/stacks/core - sudo mkdir -p /opt/stacks/infrastructure - sudo mkdir -p /opt/stacks/dashboards - sudo mkdir -p /opt/dockge - fi - log_success "Directories ready" + # Prepare deployment environment + prepare_deployment # Prompt for configuration values validate_and_prompt_variables @@ -1484,7 +1657,7 @@ main() { save_env_file # Perform enhanced placeholder replacement across all config files - enhance_placeholder_replacement + localize_deployment # Validate secrets for core deployment validate_secrets @@ -1498,25 +1671,25 @@ main() { echo "║ Deployment Complete! ║" echo "║ SSL Certificates may take a few minutes to be issued. ║" echo "║ ║" - echo "║ https://dockge.kelinreij.duckdns.org ║" - echo "║ http://192.168.4.4:5001 ║" + echo "║ https://dockge.${DOMAIN} ║" + echo "║ http://${SERVER_IP}:5001 ║" echo "║ ║" - echo "║ https://homepage.kelinreij.duckdns.org ║" - echo "║ http://192.168.4.4:3003 ║" + echo "║ https://homepage.${DOMAIN} ║" + echo "║ http://${SERVER_IP}:3003 ║" echo "║ ║" echo "╚═════════════════════════════════════════════════════════════╝" # Show consolidated warnings if any - if [ -n "$MISSING_VARS_SUMMARY" ] || [ -n "$TLS_ISSUES_SUMMARY" ]; then + if [ -n "$GLOBAL_MISSING_VARS" ] || [ -n "$TLS_ISSUES_SUMMARY" ]; then echo "╔═════════════════════════════════════════════════════════════╗" echo "║ ⚠️ WARNING ⚠️ ║" echo "║ The following variables were not defined ║" echo "║ If something isn't working as expected check these first ║" echo "║ ║" - if [ -n "$MISSING_VARS_SUMMARY" ]; then + if [ -n "$GLOBAL_MISSING_VARS" ]; then log_warning "Missing Environment Variables:" - echo "$MISSING_VARS_SUMMARY" + echo "$GLOBAL_MISSING_VARS" echo "║ ║" fi diff --git a/scripts/install-prerequisites.sh b/scripts/install-prerequisites.sh new file mode 100755 index 0000000..182b8ff --- /dev/null +++ b/scripts/install-prerequisites.sh @@ -0,0 +1,197 @@ +#!/bin/bash +# EZ-Homelab Prerequisites Installation Script + +# This script must be run as root or with sudo +# It performs system setup for Docker, networking, and security + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Log functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to generate shared CA for multi-server TLS +generate_shared_ca() { + local ca_dir="/opt/stacks/core/shared-ca" + mkdir -p "$ca_dir" + openssl genrsa -out "$ca_dir/ca-key.pem" 4096 + openssl req -new -x509 -days 365 -key "$ca_dir/ca-key.pem" -sha256 -out "$ca_dir/ca.pem" -subj "/C=US/ST=State/L=City/O=Homelab/CN=Homelab-CA" + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ca_dir" + log_success "Shared CA generated" +} + +# Setup Docker TLS function +setup_docker_tls() { + local TLS_DIR="/home/$ACTUAL_USER/EZ-Homelab/docker-tls" + + # Create TLS directory + mkdir -p "$TLS_DIR" + chown "$ACTUAL_USER:$ACTUAL_USER" "$TLS_DIR" + + # Use shared CA if available, otherwise generate local CA + if [ -f "/opt/stacks/core/shared-ca/ca.pem" ] && [ -f "/opt/stacks/core/shared-ca/ca-key.pem" ]; then + log_info "Using shared CA certificate for Docker TLS..." + cp "/opt/stacks/core/shared-ca/ca.pem" "$TLS_DIR/ca.pem" + cp "/opt/stacks/core/shared-ca/ca-key.pem" "$TLS_DIR/ca-key.pem" + else + log_info "Generating local CA certificate for Docker TLS..." + # Generate CA + openssl genrsa -out "$TLS_DIR/ca-key.pem" 4096 + openssl req -new -x509 -days 365 -key "$TLS_DIR/ca-key.pem" -sha256 -out "$TLS_DIR/ca.pem" -subj "/C=US/ST=State/L=City/O=Organization/CN=Docker-CA" + fi + + # Generate server key and cert + openssl genrsa -out "$TLS_DIR/server-key.pem" 4096 + openssl req -subj "/CN=$SERVER_IP" -new -key "$TLS_DIR/server-key.pem" -out "$TLS_DIR/server.csr" + echo "subjectAltName = DNS:$SERVER_IP,IP:$SERVER_IP,IP:127.0.0.1" > "$TLS_DIR/extfile.cnf" + openssl x509 -req -days 365 -in "$TLS_DIR/server.csr" -CA "$TLS_DIR/ca.pem" -CAkey "$TLS_DIR/ca-key.pem" -CAcreateserial -out "$TLS_DIR/server-cert.pem" -extfile "$TLS_DIR/extfile.cnf" + + # Generate client key and cert + openssl genrsa -out "$TLS_DIR/client-key.pem" 4096 + openssl req -subj "/CN=client" -new -key "$TLS_DIR/client-key.pem" -out "$TLS_DIR/client.csr" + openssl x509 -req -days 365 -in "$TLS_DIR/client.csr" -CA "$TLS_DIR/ca.pem" -CAkey "$TLS_DIR/ca-key.pem" -CAcreateserial -out "$TLS_DIR/client-cert.pem" + + # Configure Docker daemon + tee /etc/docker/daemon.json > /dev/null < /dev/null && docker --version &> /dev/null; then + log_success "Docker is already installed ($(docker --version))" + # Check if user is in docker group + if ! groups "$ACTUAL_USER" 2>/dev/null | grep -q docker; then + log_info "Adding $ACTUAL_USER to docker group..." + usermod -aG docker "$ACTUAL_USER" + NEEDS_LOGOUT=true + fi + # Check if Docker service is running + if ! systemctl is-active --quiet docker; then + log_warning "Docker service is not running, starting it..." + systemctl start docker + systemctl enable docker + log_success "Docker service started and enabled" + else + log_info "Docker service is already running" + fi + else + curl -fsSL https://get.docker.com | sh + usermod -aG docker "$ACTUAL_USER" + NEEDS_LOGOUT=true + fi + + # Step 4: Install Docker Compose + log_info "Step 4/10: Installing Docker Compose..." + if command -v docker-compose &> /dev/null && docker-compose --version &> /dev/null; then + log_success "Docker Compose is already installed ($(docker-compose --version))" + else + curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + log_success "Docker Compose installed ($(docker-compose --version))" + fi + + # Step 5: Generate shared CA for multi-server TLS + log_info "Step 5/10: Generating shared CA certificate for multi-server TLS..." + generate_shared_ca + + # Step 6: Configure Docker TLS + log_info "Step 6/10: Configuring Docker TLS..." + setup_docker_tls + + # Step 7: Configure UFW firewall + log_info "Step 7/10: Configuring firewall..." + ufw --force enable + ufw allow ssh + ufw allow 80 + ufw allow 443 + ufw allow 2376/tcp # Docker TLS port + log_success "Firewall configured" + + # Step 8: Configure automatic updates + log_info "Step 8/10: Configuring automatic updates..." + dpkg-reconfigure -f noninteractive unattended-upgrades + + # Step 10: Create Docker networks + log_info "Step 10/10: 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" + + # Step 9: Set proper ownership + log_info "Step 9/10: Setting directory ownership..." + chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt + + log_success "System setup completed!" + echo "" + if [ "$NEEDS_LOGOUT" = true ]; then + log_info "Please log out and back in for Docker group changes to take effect." + echo "" + fi +} + +# Run the setup +system_setup "$@" \ No newline at end of file