Replace personal URLs with placeholders and fix variable replacement logic

This commit is contained in:
Kelin
2026-02-02 13:19:22 -05:00
parent 0041b15cc2
commit faaf39002a
23 changed files with 959 additions and 462 deletions

View File

@@ -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}

73
TASKS.md Normal file
View File

@@ -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}:<extracted_port>"`.
*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.

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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"

View File

@@ -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
- https://zigbee2mqtt.${DOMAIN}

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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!

View File

@@ -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

197
scripts/install-prerequisites.sh Executable file
View File

@@ -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 <<EOF
{
"tls": true,
"tlsverify": true,
"tlscacert": "$TLS_DIR/ca.pem",
"tlscert": "$TLS_DIR/server-cert.pem",
"tlskey": "$TLS_DIR/server-key.pem"
}
EOF
# Update systemd service
sed -i 's|-H fd://|-H fd:// -H tcp://0.0.0.0:2376|' /lib/systemd/system/docker.service
# Reload and restart Docker
systemctl daemon-reload
systemctl restart docker
log_success "Docker TLS configured on port 2376"
}
# Main system setup function
system_setup() {
log_info "Performing system setup..."
# Check if running as root for system setup
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root or with sudo."
exit 1
fi
# Get the actual user who invoked sudo
ACTUAL_USER=${SUDO_USER:-$USER}
# Get SERVER_IP from environment or prompt
if [ -z "$SERVER_IP" ]; then
read -p "Enter the server IP address: " SERVER_IP
fi
# 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" 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 "$@"