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 ADMIN_EMAIL=${DEFAULT_EMAIL} # Used for admin user account
AUTHELIA_ADMIN_USER=${DEFAULT_USER} AUTHELIA_ADMIN_USER=${DEFAULT_USER}
AUTHELIA_ADMIN_EMAIL=${DEFAULT_EMAIL} 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 for Authelia Notifications
SMTP_USERNAME=${SMTP_EMAIL_FROM} 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: users:
${AUTHELIA_ADMIN_USER}: ${AUTHELIA_ADMIN_USER}:
displayname: "${AUTHELIA_ADMIN_USER}" displayname: ${AUTHELIA_ADMIN_USER}
password: "${AUTHELIA_ADMIN_PASSWORD}" password: ${AUTHELIA_ADMIN_PASSWORD_HASH}
email: "${AUTHELIA_ADMIN_EMAIL}" email: ${AUTHELIA_ADMIN_EMAIL}
groups: groups:
- admins - admins
- users - users

View File

@@ -7,29 +7,29 @@
href: https://homepage.kelinreij.duckdns.org href: https://homepage.kelinreij.duckdns.org
description: Hosted on Raspberry Pi description: Hosted on Raspberry Pi
- Homepage - ${REMOTE_SERVER_HOSTNAME}: - Homepage - your-remote-server :
icon: homepage.png icon: homepage.png
href: https://homepage.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://homepage.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Application Dashboard description: your-remote-server - Application Dashboard
- Homarr: - Homarr:
icon: homarr.png icon: homarr.png
href: https://homarr.kelinreij.duckdns.org href: https://homarr.kelinreij.duckdns.org
description: Alternative Dashboard description: Alternative Dashboard
- Homarr - ${REMOTE_SERVER_HOSTNAME}: - Homarr - your-remote-server :
icon: homarr.png icon: homarr.png
href: https://homarr.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://homarr.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Alternative Dashboard description: your-remote-server - Alternative Dashboard
- Dockge - jasper: - Dockge - jasper:
icon: dockge.png icon: dockge.png
href: https://jasper.kelinreij.duckdns.org href: https://jasper.kelinreij.duckdns.org
description: Main Server description: Main Server
- Dockge - ${REMOTE_SERVER_HOSTNAME}: - Dockge - your-remote-server :
icon: dockge.png icon: dockge.png
href: https://${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://your-remote-server .kelinreij.duckdns.org
description: Raspberry Pi Authentication Server description: Raspberry Pi Authentication Server
- Core: - Core:
@@ -56,38 +56,38 @@
- Dozzle: - Dozzle:
icon: dozzle.png icon: dozzle.png
href: https://dozzle.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://dozzle.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Real-time Log Viewer description: your-remote-server - Real-time Log Viewer
- Glances - jasper: - Glances - jasper:
icon: glances.png icon: glances.png
href: https://glances.jasper.kelinreij.duckdns.org href: https://glances.jasper.kelinreij.duckdns.org
description: jasper - System Monitoring description: jasper - System Monitoring
- Glances - ${REMOTE_SERVER_HOSTNAME}: - Glances - your-remote-server :
icon: glances.png icon: glances.png
href: https://glances.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://glances.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - System Monitoring description: your-remote-server - System Monitoring
- Uptime Kuma: - Uptime Kuma:
icon: uptime-kuma.png icon: uptime-kuma.png
href: https://uptime-kuma.kelinreij.duckdns.org href: https://uptime-kuma.kelinreij.duckdns.org
description: Uptime Monitoring description: Uptime Monitoring
- Grafana - ${REMOTE_SERVER_HOSTNAME}: - Grafana - your-remote-server :
icon: grafana.png icon: grafana.png
href: https://grafana.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://grafana.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Metrics Dashboard description: your-remote-server - Metrics Dashboard
- Prometheus - ${REMOTE_SERVER_HOSTNAME}: - Prometheus - your-remote-server :
icon: prometheus.png icon: prometheus.png
href: https://prometheus.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://prometheus.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Metrics Collection description: your-remote-server - Metrics Collection
- Uptime Kuma - ${REMOTE_SERVER_HOSTNAME}: - Uptime Kuma - your-remote-server :
icon: uptime-kuma.png icon: uptime-kuma.png
href: https://status.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://status.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Uptime Monitoring description: your-remote-server - Uptime Monitoring
- Media: - Media:
- Jellyfin: - Jellyfin:
@@ -243,20 +243,20 @@
href: https://backrest.kelinreij.duckdns.org href: https://backrest.kelinreij.duckdns.org
description: Backup Solution description: Backup Solution
- Backrest - ${REMOTE_SERVER_HOSTNAME}: - Backrest - your-remote-server :
icon: mdi-backup-restore icon: mdi-backup-restore
href: https://backrest.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://backrest.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Backup Solution description: your-remote-server - Backup Solution
- Duplicati: - Duplicati:
icon: duplicati.png icon: duplicati.png
href: https://duplicati.kelinreij.duckdns.org href: https://duplicati.kelinreij.duckdns.org
description: Backup Software description: Backup Software
- Duplicati - ${REMOTE_SERVER_HOSTNAME}: - Duplicati - your-remote-server :
icon: duplicati.png icon: duplicati.png
href: https://duplicati.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://duplicati.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Backup Software description: your-remote-server - Backup Software
- Metrics: - Metrics:
- Grafana: - Grafana:

View File

@@ -1,8 +1,5 @@
# Alternative Services Stack # Alternative Services Stack
# This stack contains alternative/optional services that are not deployed by default # 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: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
@@ -10,8 +7,6 @@
services: services:
# Portainer - Docker management UI (Alternative to Dockge) # 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 # Docker management interface should always run when deployed
portainer: portainer:
image: portainer/portainer-ce:2.19.4 image: portainer/portainer-ce:2.19.4
@@ -35,14 +30,14 @@ services:
- "homelab.description=Docker container management UI (Alternative to Dockge)" - "homelab.description=Docker container management UI (Alternative to Dockge)"
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
- "traefik.http.routers.portainer.middlewares=authelia@docker" - "traefik.http.routers.portainer.middlewares=authelia@docker"
- "traefik.http.services.portainer.loadbalancer.server.port=9000" - "traefik.http.services.portainer.loadbalancer.server.port=9000"
# Authentik - Alternative SSO/Identity Provider with Web UI # 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 # 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 # WARNING: Do not run both Authelia and Authentik at the same time
# SSO service should always run when deployed as alternative to Authelia # 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)" - "homelab.description=SSO/Identity provider with web UI (Alternative to Authelia)"
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.authentik.tls.certresolver=letsencrypt" - "traefik.http.routers.authentik.tls.certresolver=letsencrypt"
- "traefik.http.routers.authentik.middlewares=authelia@docker" - "traefik.http.routers.authentik.middlewares=authelia@docker"
@@ -165,9 +160,7 @@ services:
retries: 5 retries: 5
# Plex Media Server - Alternative to Jellyfin # 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. # NOTE: No Authelia - allows app access from Roku, Fire TV, mobile, etc.
# Media server should always run when deployed as alternative to Jellyfin
plex: plex:
image: plexinc/pms-docker:1.40.0.7998-f68041501 image: plexinc/pms-docker:1.40.0.7998-f68041501
container_name: plex container_name: plex
@@ -214,12 +207,12 @@ services:
# Traefik labels - NO Authelia for app access # Traefik labels - NO Authelia for app access
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.plex.tls.certresolver=letsencrypt" - "traefik.http.routers.plex.tls.certresolver=letsencrypt"
- "traefik.http.services.plex.loadbalancer.server.port=32400" - "traefik.http.services.plex.loadbalancer.server.port=32400"
- "x-dockge.url=https://plex.kelinreij.duckdns.org" - "x-dockge.url=https://plex.${DOMAIN}"
- "x-dockge.url=https://plex.kelinreij.duckdns.org" - "x-dockge.url=https://plex.${DOMAIN}"
volumes: volumes:
portainer-data: portainer-data:

View File

@@ -3,10 +3,10 @@
############################################################### ###############################################################
users: users:
kelin: ${DEFAULT_USER}:
displayname: "Admin User" displayname: "Admin User"
password: "$argon2id$v=19$m=65536,t=3,p=4$a+3pIrywP/li9wy9J6UkMA$+3THyJiAnS/gNYnLaYtlsRCaYfgnnxsUyGZ4D3xGnUg" password: "${AUTHELIA_ADMIN_PASSWORD_HASH}"
email: kelinshomelab@gmail.com email: ${DEFAULT_EMAIL}
groups: groups:
- admins - admins
- users - users

View File

@@ -1,7 +1,4 @@
# Core Infrastructure Services # 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: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
@@ -50,11 +47,8 @@ services:
# Service metadata # Service metadata
- "homelab.category=core" - "homelab.category=core"
- "homelab.description=Reverse proxy and SSL termination" - "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.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.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt" - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.middlewares=authelia@docker" - "traefik.http.routers.traefik.middlewares=authelia@docker"
@@ -86,13 +80,13 @@ services:
# If Traefik is on a remote server: these labels are NOT USED; # If Traefik is on a remote server: these labels are NOT USED;
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.authelia.rule=Host(`auth.kelinreij.duckdns.org`)" - "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)"
- "traefik.http.routers.authelia.entrypoints=websecure" - "traefik.http.routers.authelia.entrypoints=websecure"
- "traefik.http.routers.authelia.tls.certresolver=letsencrypt" - "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
- "traefik.http.routers.authelia.service=authelia" - "traefik.http.routers.authelia.service=authelia"
- "traefik.http.services.authelia.loadbalancer.server.port=9091" - "traefik.http.services.authelia.loadbalancer.server.port=9091"
# Authelia forward auth middleware configuration # Authelia forward auth middleware configuration
- "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.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.authResponseHeaders=X-Secret"
- "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true" - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true"
@@ -133,7 +127,7 @@ networks:
x-dockge: x-dockge:
urls: urls:
- https://auth.kelinreij.duckdns.org - https://auth.${DOMAIN}
- http://192.168.4.11:9091 - http://192.168.4.11:9091
- https://traefik.kelinreij.duckdns.org - https://traefik.${DOMAIN}
- http://192.168.4.11:8080 - http://192.168.4.11:8080

View File

@@ -1,11 +1,9 @@
# Dashboard Services # 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 # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml
# RESTART POLICY GUIDE:
# Service Access URLs: # - unless-stopped: Core infrastructure services that should always run
# - Homepage: https://homepage.${DOMAIN} # - no: Services with Sablier lazy loading (start on-demand)
# - Homarr: https://homarr.${DOMAIN} # - See individual service comments for specific reasoning
services: services:
# Homepage - Default Application Dashboard # Homepage - Default Application Dashboard
@@ -61,7 +59,6 @@ services:
# Homarr - Modern dashboard # Homarr - Modern dashboard
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
homarr: homarr:
image: ghcr.io/ajnart/homarr:latest image: ghcr.io/ajnart/homarr:latest
deploy: deploy:

View File

@@ -17,9 +17,9 @@
href: https://jasper.kelinreij.duckdns.org href: https://jasper.kelinreij.duckdns.org
description: Main Server description: Main Server
- Dockge - ${REMOTE_SERVER_HOSTNAME}: - Dockge - your-remote-server :
icon: dockge.png icon: dockge.png
href: https://${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://your-remote-server .kelinreij.duckdns.org
description: Raspberry Pi Authentication Server description: Raspberry Pi Authentication Server
- Core: - Core:
@@ -46,18 +46,18 @@
- Dozzle: - Dozzle:
icon: dozzle.png icon: dozzle.png
href: https://dozzle.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://dozzle.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - Real-time Log Viewer description: your-remote-server - Real-time Log Viewer
- Glances - jasper: - Glances - jasper:
icon: glances.png icon: glances.png
href: https://glances.jasper.kelinreij.duckdns.org href: https://glances.jasper.kelinreij.duckdns.org
description: jasper - System Monitoring description: jasper - System Monitoring
- Glances - ${REMOTE_SERVER_HOSTNAME}: - Glances - your-remote-server :
icon: glances.png icon: glances.png
href: https://glances.${REMOTE_SERVER_HOSTNAME}.kelinreij.duckdns.org href: https://glances.your-remote-server .kelinreij.duckdns.org
description: ${REMOTE_SERVER_HOSTNAME} - System Monitoring description: your-remote-server - System Monitoring
- Uptime Kuma: - Uptime Kuma:
icon: uptime-kuma.png icon: uptime-kuma.png

View File

@@ -1,18 +1,11 @@
# Dockge Stack # Dockge Stack
# Docker Compose Stack Manager
# Place in /opt/dockge/docker-compose.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
# - See individual service comments for specific reasoning # - See individual service comments for specific reasoning
# Service Access URLs:
# - Dockge: https://dockge.kelinreij.duckdns.org
services: services:
# Dockge - Docker Compose Stack Manager (PRIMARY - preferred over Portainer) # Dockge - Docker Compose Stack Manager
# Access at: https://dockge.kelinreij.duckdns.org
# Stack management interface should always run for container management # Stack management interface should always run for container management
dockge: dockge:
image: louislam/dockge:1 image: louislam/dockge:1
@@ -51,7 +44,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.dockge.tls.certresolver=letsencrypt" - "traefik.http.routers.dockge.tls.certresolver=letsencrypt"
- "traefik.http.routers.dockge.middlewares=authelia@docker" - "traefik.http.routers.dockge.middlewares=authelia@docker"

View File

@@ -1,17 +1,11 @@
# Home Assistant and IoT Services # Home Assistant and IoT Services
# Home automation platform and related tools # RESTART POLICY GUIDE:
# Place in /opt/stacks/homeassistant/docker-compose.yml # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand)
# Service Access URLs: # - See individual service comments for specific reasoning
# - 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)
services: services:
# Home Assistant - Home automation platform # Home Assistant - Home automation platform
# Access at: https://ha.kelinreij.duckdns.org
# NOTE: No Authelia - HA has its own authentication # NOTE: No Authelia - HA has its own authentication
homeassistant: homeassistant:
image: ghcr.io/home-assistant/home-assistant:2024.1 image: ghcr.io/home-assistant/home-assistant:2024.1
@@ -40,7 +34,6 @@ services:
# Use Traefik's file provider or external host routing # Use Traefik's file provider or external host routing
# ESPHome - ESP8266/ESP32 firmware manager # ESPHome - ESP8266/ESP32 firmware manager
# Access at: https://esphome.kelinreij.duckdns.org
esphome: esphome:
image: ghcr.io/esphome/esphome:latest image: ghcr.io/esphome/esphome:latest
deploy: deploy:
@@ -77,14 +70,13 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.esphome.tls.certresolver=letsencrypt" - "traefik.http.routers.esphome.tls.certresolver=letsencrypt"
- "traefik.http.routers.esphome.middlewares=authelia@docker" - "traefik.http.routers.esphome.middlewares=authelia@docker"
- "traefik.http.services.esphome.loadbalancer.server.port=6052" - "traefik.http.services.esphome.loadbalancer.server.port=6052"
# TasmoAdmin - Tasmota device manager # TasmoAdmin - Tasmota device manager
# Access at: https://tasmoadmin.kelinreij.duckdns.org
tasmoadmin: tasmoadmin:
image: ghcr.io/tasmoadmin/tasmoadmin:latest image: ghcr.io/tasmoadmin/tasmoadmin:latest
container_name: tasmoadmin container_name: tasmoadmin
@@ -109,14 +101,13 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.tasmoadmin.tls.certresolver=letsencrypt" - "traefik.http.routers.tasmoadmin.tls.certresolver=letsencrypt"
- "traefik.http.routers.tasmoadmin.middlewares=authelia@docker" - "traefik.http.routers.tasmoadmin.middlewares=authelia@docker"
- "traefik.http.services.tasmoadmin.loadbalancer.server.port=80" - "traefik.http.services.tasmoadmin.loadbalancer.server.port=80"
# MotionEye - Video surveillance # MotionEye - Video surveillance
# Access at: https://motioneye.kelinreij.duckdns.org
motioneye: motioneye:
image: ccrisan/motioneye:master-amd64 image: ccrisan/motioneye:master-amd64
container_name: motioneye container_name: motioneye
@@ -125,7 +116,7 @@ services:
- homelab-network - homelab-network
- traefik-network - traefik-network
ports: ports:
- "8765:8765" # Optional: direct access - "8765:8765"
volumes: volumes:
- ./$(basename $file .yml)/config:/etc/motioneye - ./$(basename $file .yml)/config:/etc/motioneye
- /mnt/surveillance:/var/lib/motioneye # Large video files on separate drive - /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. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.motioneye.tls.certresolver=letsencrypt" - "traefik.http.routers.motioneye.tls.certresolver=letsencrypt"
- "traefik.http.routers.motioneye.middlewares=authelia@docker" - "traefik.http.routers.motioneye.middlewares=authelia@docker"
- "traefik.http.services.motioneye.loadbalancer.server.port=8765" - "traefik.http.services.motioneye.loadbalancer.server.port=8765"
# Node-RED - Flow-based automation (Home Assistant addon alternative) # Node-RED - Flow-based automation (Home Assistant addon alternative)
# Access at: https://nodered.kelinreij.duckdns.org
nodered: nodered:
image: nodered/node-red:latest image: nodered/node-red:latest
deploy: deploy:
@@ -183,7 +173,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.nodered.tls.certresolver=letsencrypt" - "traefik.http.routers.nodered.tls.certresolver=letsencrypt"
- "traefik.http.routers.nodered.middlewares=authelia@docker" - "traefik.http.routers.nodered.middlewares=authelia@docker"
@@ -209,7 +199,6 @@ services:
- "homelab.description=MQTT message broker" - "homelab.description=MQTT message broker"
# Zigbee2MQTT - Zigbee to MQTT bridge (DISABLED - requires USB adapter) # 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) # NOTE: Requires USB Zigbee adapter (e.g., ConBee II, Sonoff ZBDongle)
# Uncomment after connecting adapter # Uncomment after connecting adapter
# zigbee2mqtt: # zigbee2mqtt:
@@ -233,7 +222,7 @@ services:
# - "homelab.category=iot" # - "homelab.category=iot"
# - "homelab.description=Zigbee to MQTT bridge" # - "homelab.description=Zigbee to MQTT bridge"
# - "traefik.enable=true" # - "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.entrypoints=websecure"
# - "traefik.http.routers.zigbee2mqtt.tls.certresolver=letsencrypt" # - "traefik.http.routers.zigbee2mqtt.tls.certresolver=letsencrypt"
# - "traefik.http.routers.zigbee2mqtt.middlewares=authelia@docker" # - "traefik.http.routers.zigbee2mqtt.middlewares=authelia@docker"
@@ -248,15 +237,15 @@ networks:
x-dockge: x-dockge:
urls: urls:
# Proxied URLs (through Traefik) # Proxied URLs (through Traefik)
- https://ha.kelinreij.duckdns.org - https://ha.${DOMAIN}
- http://192.168.4.4:8123 - http://192.168.4.4:8123
- https://esphome.kelinreij.duckdns.org - https://esphome.${DOMAIN}
- http://192.168.4.4:6052 - http://192.168.4.4:6052
- https://tasmoadmin.kelinreij.duckdns.org - https://tasmoadmin.${DOMAIN}
- http://192.168.4.4:8084 - http://192.168.4.4:8084
- https://motioneye.kelinreij.duckdns.org - https://motioneye.${DOMAIN}
- http://192.168.4.4:8765 - http://192.168.4.4:8765
- https://nodered.kelinreij.duckdns.org - https://nodered.${DOMAIN}
- http://192.168.4.4:1880 - http://192.168.4.4:1880
- mqtt://192.168.4.4:1883 - mqtt://192.168.4.4:1883
- https://zigbee2mqtt.kelinreij.duckdns.org - https://zigbee2mqtt.${DOMAIN}

View File

@@ -1,9 +1,5 @@
# Infrastructure Services # 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 # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
@@ -41,7 +37,6 @@ services:
- homelab.description=Docker socket proxy for security - homelab.description=Docker socket proxy for security
# Pi-hole - Network-wide ad blocker and DNS server # 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 # DNS service must always run for network-wide ad blocking
pihole: pihole:
image: pihole/pihole:2024.01.0 image: pihole/pihole:2024.01.0
@@ -87,15 +82,13 @@ services:
# - This prevents conflicts between Docker labels and file provider # - This prevents conflicts between Docker labels and file provider
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.pihole.tls.certresolver=letsencrypt" - "traefik.http.routers.pihole.tls.certresolver=letsencrypt"
- "traefik.http.routers.pihole.middlewares=authelia@docker" - "traefik.http.routers.pihole.middlewares=authelia@docker"
- "traefik.http.services.pihole.loadbalancer.server.port=80" - "traefik.http.services.pihole.loadbalancer.server.port=80"
# Watchtower - Automatic container updates # Watchtower - Automatic container updates
# Monitors and updates Docker containers to latest versions
# Runs daily at 4 AM
watchtower: watchtower:
image: containrrr/watchtower:latest image: containrrr/watchtower:latest
container_name: watchtower container_name: watchtower
@@ -116,7 +109,6 @@ services:
- "homelab.description=Automatic Docker container updates" - "homelab.description=Automatic Docker container updates"
# Dozzle - Real-time Docker log viewer # Dozzle - Real-time Docker log viewer
# Access at: https://dozzle.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
dozzle: dozzle:
image: amir20/dozzle:latest image: amir20/dozzle:latest
@@ -157,7 +149,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.dozzle.tls=true" - "traefik.http.routers.dozzle.tls=true"
- "traefik.http.routers.dozzle.middlewares=authelia@docker" - "traefik.http.routers.dozzle.middlewares=authelia@docker"
@@ -169,7 +161,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Glances - System monitoring # Glances - System monitoring
# Access at: https://glances.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 30min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 30min inactivity
glances: glances:
image: nicolargo/glances:latest-full image: nicolargo/glances:latest-full
@@ -210,7 +201,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.glances.tls=true" - "traefik.http.routers.glances.tls=true"
- "traefik.http.routers.glances.middlewares=authelia@docker" - "traefik.http.routers.glances.middlewares=authelia@docker"
@@ -222,7 +213,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Code Server - VS Code in browser # Code Server - VS Code in browser
# Access at: https://code.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 30min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 30min inactivity
code-server: code-server:
image: lscr.io/linuxserver/code-server:latest image: lscr.io/linuxserver/code-server:latest
@@ -267,7 +257,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.code-server.tls.certresolver=letsencrypt" - "traefik.http.routers.code-server.tls.certresolver=letsencrypt"
- "traefik.http.routers.code-server.middlewares=authelia@docker" - "traefik.http.routers.code-server.middlewares=authelia@docker"
@@ -280,13 +270,13 @@ services:
x-dockge: x-dockge:
urls: urls:
- https://pihole.kelinreij.duckdns.org - https://pihole.${DOMAIN}
- https://192.168.4.4:53 - https://192.168.4.4:53
- https://dozzle.kelinreij.duckdns.org - https://dozzle.${DOMAIN}
- https://192.168.4.4:8085 - https://192.168.4.4:8085
- https://glances.kelinreij.duckdns.org - https://glances.${DOMAIN}
- https://192.168.4.4:61208 - https://192.168.4.4:61208
- https://code.kelinreij.duckdns.org - https://code.${DOMAIN}
- https://192.168.4.4:8079 - https://192.168.4.4:8079
- http://192.168.4.4:2375 # Docker Proxy - http://192.168.4.4:2375 # Docker Proxy
- http://192.168.4.4:19999 # Netdata - http://192.168.4.4:19999 # Netdata

View File

@@ -1,16 +1,12 @@
# Media Management Services # Media Management Services
# Content automation and library management (*arr apps, transcoders, etc.)
# Place in /opt/stacks/media-management/docker-compose.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
# - See individual service comments for specific reasoning # - See individual service comments for specific reasoning
services: services:
# Sonarr - TV show automation
# Access at: https://sonarr.yourdomain.duckdns.org
sonarr: sonarr:
# Sonarr - TV show management and automation
image: linuxserver/sonarr:4.0.0 image: linuxserver/sonarr:4.0.0
container_name: sonarr container_name: sonarr
restart: no restart: no
@@ -45,7 +41,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.sonarr.tls.certresolver=letsencrypt" - "traefik.http.routers.sonarr.tls.certresolver=letsencrypt"
- "traefik.http.routers.sonarr.middlewares=authelia@docker" - "traefik.http.routers.sonarr.middlewares=authelia@docker"
@@ -55,7 +51,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Radarr - Movie automation # Radarr - Movie automation
# Access at: https://radarr.yourdomain.duckdns.org
radarr: radarr:
image: linuxserver/radarr:5.2.6 image: linuxserver/radarr:5.2.6
container_name: radarr container_name: radarr
@@ -91,7 +86,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.radarr.tls.certresolver=letsencrypt" - "traefik.http.routers.radarr.tls.certresolver=letsencrypt"
- "traefik.http.routers.radarr.middlewares=authelia@docker" - "traefik.http.routers.radarr.middlewares=authelia@docker"
@@ -135,7 +130,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.prowlarr.tls.certresolver=letsencrypt" - "traefik.http.routers.prowlarr.tls.certresolver=letsencrypt"
- "traefik.http.routers.prowlarr.middlewares=authelia@docker" - "traefik.http.routers.prowlarr.middlewares=authelia@docker"
@@ -145,7 +140,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Readarr - Ebook and audiobook management # Readarr - Ebook and audiobook management
# Access at: https://readarr.kelinreij.duckdns.org
readarr: readarr:
image: linuxserver/readarr:0.4.19-nightly image: linuxserver/readarr:0.4.19-nightly
container_name: readarr container_name: readarr
@@ -175,7 +169,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.readarr.tls.certresolver=letsencrypt" - "traefik.http.routers.readarr.tls.certresolver=letsencrypt"
- "traefik.http.routers.readarr.middlewares=authelia@docker" - "traefik.http.routers.readarr.middlewares=authelia@docker"
@@ -185,7 +179,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Lidarr - Music collection manager # Lidarr - Music collection manager
# Access at: https://lidarr.kelinreij.duckdns.org
lidarr: lidarr:
image: linuxserver/lidarr:2.0.7 image: linuxserver/lidarr:2.0.7
container_name: lidarr container_name: lidarr
@@ -215,7 +208,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.lidarr.tls.certresolver=letsencrypt" - "traefik.http.routers.lidarr.tls.certresolver=letsencrypt"
- "traefik.http.routers.lidarr.middlewares=authelia@docker" - "traefik.http.routers.lidarr.middlewares=authelia@docker"
@@ -225,7 +218,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Lazy Librarian - Book manager # Lazy Librarian - Book manager
# Access at: https://lazylibrarian.kelinreij.duckdns.org
lazylibrarian: lazylibrarian:
image: linuxserver/lazylibrarian:latest image: linuxserver/lazylibrarian:latest
container_name: lazylibrarian container_name: lazylibrarian
@@ -256,7 +248,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.lazylibrarian.tls.certresolver=letsencrypt" - "traefik.http.routers.lazylibrarian.tls.certresolver=letsencrypt"
- "traefik.http.routers.lazylibrarian.middlewares=authelia@docker" - "traefik.http.routers.lazylibrarian.middlewares=authelia@docker"
@@ -266,7 +258,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Mylar3 - Comic book manager # Mylar3 - Comic book manager
# Access at: https://mylar.kelinreij.duckdns.org
mylar3: mylar3:
image: linuxserver/mylar3:latest image: linuxserver/mylar3:latest
container_name: mylar3 container_name: mylar3
@@ -296,7 +287,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.mylar.tls.certresolver=letsencrypt" - "traefik.http.routers.mylar.tls.certresolver=letsencrypt"
- "traefik.http.routers.mylar.middlewares=authelia@docker" - "traefik.http.routers.mylar.middlewares=authelia@docker"
@@ -306,7 +297,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Jellyseerr - Request management for Jellyfin/Plex # Jellyseerr - Request management for Jellyfin/Plex
# Access at: https://jellyseerr.kelinreij.duckdns.org
jellyseerr: jellyseerr:
image: fallenbagel/jellyseerr:latest image: fallenbagel/jellyseerr:latest
container_name: jellyseerr container_name: jellyseerr
@@ -339,7 +329,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt" - "traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt"
- "traefik.http.routers.jellyseerr.middlewares=authelia@docker" - "traefik.http.routers.jellyseerr.middlewares=authelia@docker"
@@ -368,21 +358,21 @@ services:
x-dockge: x-dockge:
urls: urls:
- https://sonarr.kelinreij.duckdns.org - https://sonarr.${DOMAIN}
- http://192.168.4.4:8989 - http://192.168.4.4:8989
- https://radarr.kelinreij.duckdns.org - https://radarr.${DOMAIN}
- http://192.168.4.4:7878 - http://192.168.4.4:7878
- https://prowlarr.kelinreij.duckdns.org - https://prowlarr.${DOMAIN}
- http://192.168.4.4:9696 - http://192.168.4.4:9696
- https://readarr.kelinreij.duckdns.org - https://readarr.${DOMAIN}
- http://192.168.4.4:8787 - http://192.168.4.4:8787
- https://lidarr.kelinreij.duckdns.org - https://lidarr.${DOMAIN}
- http://192.168.4.4:8686 - http://192.168.4.4:8686
- https://lazylibrarian.kelinreij.duckdns.org - https://lazylibrarian.${DOMAIN}
- http://192.168.4.4:5299 - http://192.168.4.4:5299
- https://mylar.kelinreij.duckdns.org - https://mylar.${DOMAIN}
- http://192.168.4.4:8090 - http://192.168.4.4:8090
- https://jellyseerr.kelinreij.duckdns.org - https://jellyseerr.${DOMAIN}
- http://192.168.4.4:5055 - http://192.168.4.4:5055
networks: networks:

View File

@@ -1,22 +1,14 @@
# Media Services # 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 # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
# - See individual service comments for specific reasoning # - 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: services:
# Jellyfin - Open-source media streaming server # 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. # NOTE: No Authelia - allows app access from Roku, Fire TV, mobile, etc.
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
jellyfin: jellyfin:
@@ -63,7 +55,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.jellyfin.tls=true" - "traefik.http.routers.jellyfin.tls=true"
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
@@ -76,7 +68,7 @@ services:
- "sablier.theme=hacker-terminal" - "sablier.theme=hacker-terminal"
# Calibre-Web - Ebook reader and server # Calibre-Web - Ebook reader and server
# Access at: https://calibre.kelinreij.duckdns.org # Access at: https://calibre.${DOMAIN}
calibre-web: calibre-web:
image: lscr.io/linuxserver/calibre-web:latest image: lscr.io/linuxserver/calibre-web:latest
deploy: deploy:
@@ -112,7 +104,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.calibre.tls.certresolver=letsencrypt" - "traefik.http.routers.calibre.tls.certresolver=letsencrypt"
- "traefik.http.routers.calibre.middlewares=authelia@docker" - "traefik.http.routers.calibre.middlewares=authelia@docker"
@@ -123,15 +115,12 @@ services:
- "sablier.group=jasper-calibre-web" - "sablier.group=jasper-calibre-web"
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# ==========================================
# DOCKGE URL CONFIGURATION
# ==========================================
x-dockge: x-dockge:
urls: urls:
# Proxied URLs (through Traefik) # Proxied URLs (through Traefik)
- https://jellyfin.kelinreij.duckdns.org - https://jellyfin.${DOMAIN}
- http://192.168.4.4:8096 - http://192.168.4.4:8096
- https://calibre.kelinreij.duckdns.org - https://calibre.${DOMAIN}
- http://192.168.4.4:8083 - http://192.168.4.4:8083
networks: networks:

View File

@@ -1,25 +1,11 @@
# Monitoring and Observability Services # Monitoring and Observability Services
# Services for monitoring your homelab infrastructure
# Place in /opt/stacks/monitoring/docker-compose.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
# - See individual service comments for specific reasoning # - 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: services:
# Prometheus - Metrics collection and storage # Prometheus - Metrics collection and storage
# Access at: http://192.168.4.4:9090
prometheus: prometheus:
image: prom/prometheus:v2.48.1 image: prom/prometheus:v2.48.1
deploy: deploy:
@@ -59,7 +45,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.prometheus.tls=true" - "traefik.http.routers.prometheus.tls=true"
- "traefik.http.routers.prometheus.tls.certresolver=letsencrypt" - "traefik.http.routers.prometheus.tls.certresolver=letsencrypt"
@@ -67,7 +53,6 @@ services:
- "traefik.http.services.prometheus.loadbalancer.server.port=9090" - "traefik.http.services.prometheus.loadbalancer.server.port=9090"
# Grafana - Metrics visualization # Grafana - Metrics visualization
# Access at: http://192.168.4.4:3000
# Default credentials: admin / admin (change on first login) # Default credentials: admin / admin (change on first login)
grafana: grafana:
image: grafana/grafana:10.2.3 image: grafana/grafana:10.2.3
@@ -93,7 +78,7 @@ services:
environment: environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
- GF_USERS_ALLOW_SIGN_UP=false - 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 - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource,grafana-piechart-panel
user: "1000:1000" user: "1000:1000"
depends_on: depends_on:
@@ -109,7 +94,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.grafana.tls=true" - "traefik.http.routers.grafana.tls=true"
- "traefik.http.routers.grafana.tls.certresolver=letsencrypt" - "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
@@ -170,7 +155,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.cadvisor.tls=true" - "traefik.http.routers.cadvisor.tls=true"
- "traefik.http.routers.cadvisor.tls.certresolver=letsencrypt" - "traefik.http.routers.cadvisor.tls.certresolver=letsencrypt"
@@ -178,7 +163,6 @@ services:
- "traefik.http.services.cadvisor.loadbalancer.server.port=8080" - "traefik.http.services.cadvisor.loadbalancer.server.port=8080"
# Uptime Kuma - Uptime monitoring # Uptime Kuma - Uptime monitoring
# Access at: https://uptime-kuma.kelinreij.duckdns.org
uptime-kuma: uptime-kuma:
image: louislam/uptime-kuma:1 image: louislam/uptime-kuma:1
deploy: deploy:
@@ -211,7 +195,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.uptime-kuma.tls=true" - "traefik.http.routers.uptime-kuma.tls=true"
- "traefik.http.routers.uptime-kuma.tls.certresolver=letsencrypt" - "traefik.http.routers.uptime-kuma.tls.certresolver=letsencrypt"
@@ -253,7 +237,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.loki.tls=true" - "traefik.http.routers.loki.tls=true"
- "traefik.http.routers.loki.tls.certresolver=letsencrypt" - "traefik.http.routers.loki.tls.certresolver=letsencrypt"
@@ -300,7 +284,7 @@ x-dockge:
# Proxied URLs (through Traefik) # Proxied URLs (through Traefik)
- http://192.168.4.4:9090 - http://192.168.4.4:9090
- http://192.168.4.4:3000 - 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:9100/metrics
- http://192.168.4.4:8082 - http://192.168.4.4:8082
- http://192.168.4.4:3100 - http://192.168.4.4:3100

View File

@@ -1,8 +1,5 @@
# Productivity and Content Management Services # 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 # SABLIER SESSION DURATION: Set to 5m for testing. Increase to 30m for production in config-templates/traefik/dynamic/sablier.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
@@ -10,7 +7,6 @@
services: services:
# Nextcloud - File sync and collaboration # Nextcloud - File sync and collaboration
# Access at: https://nextcloud.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
nextcloud: nextcloud:
image: nextcloud:28 image: nextcloud:28
@@ -40,10 +36,10 @@ services:
- MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD} - MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - 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 - TRUSTED_PROXIES=172.18.0.0/16
- OVERWRITEPROTOCOL=https - OVERWRITEPROTOCOL=https
- OVERWRITEHOST=nextcloud.kelinreij.duckdns.org - OVERWRITEHOST=nextcloud.${DOMAIN}
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/status.php"] test: ["CMD", "curl", "-f", "http://localhost/status.php"]
interval: 30s interval: 30s
@@ -61,7 +57,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.routers.nextcloud.middlewares=authelia@docker" - "traefik.http.routers.nextcloud.middlewares=authelia@docker"
@@ -91,7 +87,6 @@ services:
- "homelab.description=Nextcloud database" - "homelab.description=Nextcloud database"
# Mealie - Recipe manager # Mealie - Recipe manager
# Access at: https://mealie.kelinreij.duckdns.org
mealie: mealie:
image: ghcr.io/mealie-recipes/mealie:latest image: ghcr.io/mealie-recipes/mealie:latest
container_name: mealie container_name: mealie
@@ -107,7 +102,7 @@ services:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- TZ=America/New_York - TZ=America/New_York
- BASE_URL=https://mealie.kelinreij.duckdns.org - BASE_URL=https://mealie.${DOMAIN}
- DB_ENGINE=sqlite - DB_ENGINE=sqlite
labels: labels:
# TRAEFIK CONFIGURATION # TRAEFIK CONFIGURATION
@@ -118,7 +113,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.mealie.tls.certresolver=letsencrypt" - "traefik.http.routers.mealie.tls.certresolver=letsencrypt"
- "traefik.http.routers.mealie.middlewares=authelia@docker" - "traefik.http.routers.mealie.middlewares=authelia@docker"
@@ -130,7 +125,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# WordPress - Blog/website platform # WordPress - Blog/website platform
# Access at: https://blog.kelinreij.duckdns.org
wordpress: wordpress:
image: wordpress:latest image: wordpress:latest
container_name: wordpress container_name: wordpress
@@ -164,7 +158,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.wordpress.tls.certresolver=letsencrypt" - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt"
- "traefik.http.routers.wordpress.middlewares=authelia@docker" - "traefik.http.routers.wordpress.middlewares=authelia@docker"
@@ -193,7 +187,6 @@ services:
- "homelab.description=WordPress database" - "homelab.description=WordPress database"
# Gitea - Self-hosted Git service # Gitea - Self-hosted Git service
# Access at: https://git.kelinreij.duckdns.org
gitea: gitea:
image: gitea/gitea:latest image: gitea/gitea:latest
deploy: deploy:
@@ -241,7 +234,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt" - "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
- "traefik.http.routers.gitea.middlewares=authelia@docker" - "traefik.http.routers.gitea.middlewares=authelia@docker"
@@ -270,7 +263,6 @@ services:
# Jupyter Lab - Interactive computing notebooks # Jupyter Lab - Interactive computing notebooks
# Access at: https://jupyter.kelinreij.duckdns.org
# Token displayed in logs on first start # Token displayed in logs on first start
jupyter: jupyter:
image: jupyter/scipy-notebook:latest image: jupyter/scipy-notebook:latest
@@ -307,7 +299,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.jupyter.tls.certresolver=letsencrypt" - "traefik.http.routers.jupyter.tls.certresolver=letsencrypt"
- "traefik.http.routers.jupyter.middlewares=authelia@docker" - "traefik.http.routers.jupyter.middlewares=authelia@docker"
@@ -331,13 +323,13 @@ networks:
x-dockge: x-dockge:
urls: urls:
# Proxied URLs (through Traefik) # Proxied URLs (through Traefik)
- https://nextcloud.kelinreij.duckdns.org - https://nextcloud.${DOMAIN}
- https://192.168.4.4:8089 - https://192.168.4.4:8089
- https://mealie.kelinreij.duckdns.org - https://mealie.${DOMAIN}
- https://192.168.4.4:9000 - https://192.168.4.4:9000
- https://wordpress.kelinreij.duckdns.org - https://wordpress.${DOMAIN}
- https://192.168.4.4:8088 - https://192.168.4.4:8088
- https://gitea.kelinreij.duckdns.org - https://gitea.${DOMAIN}
- https://192.168.4.4:3010 - https://192.168.4.4:3010
- https://jupyter.kelinreij.duckdns.org - https://jupyter.${DOMAIN}
- https://192.168.4.4:8890 - 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: services:
# Tdarr Server - Distributed transcoding server # Tdarr Server - Distributed transcoding server
# Access at: https://tdarr.kelinreij.duckdns.org
tdarr-server: tdarr-server:
image: ghcr.io/haveagitgat/tdarr:latest image: ghcr.io/haveagitgat/tdarr:latest
container_name: tdarr-server container_name: tdarr-server
@@ -36,7 +41,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.tdarr.tls.certresolver=letsencrypt" - "traefik.http.routers.tdarr.tls.certresolver=letsencrypt"
- "traefik.http.routers.tdarr.middlewares=authelia@docker" - "traefik.http.routers.tdarr.middlewares=authelia@docker"
@@ -75,7 +80,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Unmanic - Another transcoding option # Unmanic - Another transcoding option
# Access at: https://unmanic.kelinreij.duckdns.org
unmanic: unmanic:
image: josh5/unmanic:latest image: josh5/unmanic:latest
container_name: unmanic container_name: unmanic
@@ -105,7 +109,7 @@ services:
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "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.entrypoints=websecure"
- "traefik.http.routers.unmanic.tls.certresolver=letsencrypt" - "traefik.http.routers.unmanic.tls.certresolver=letsencrypt"
- "traefik.http.routers.unmanic.middlewares=authelia@docker" - "traefik.http.routers.unmanic.middlewares=authelia@docker"
@@ -122,7 +126,7 @@ networks:
x-dockge: x-dockge:
urls: urls:
- https://tdarr.kelinreij.duckdns.org - https://tdarr.${DOMAIN}
- http://192.168.4.4:8265 - http://192.168.4.4:8265
- https://unmanic.kelinreij.duckdns.org - https://unmanic.${DOMAIN}
- http://192.168.4.4:8888 - http://192.168.4.4:8888

View File

@@ -1,6 +1,4 @@
# Backup and Utility Services # Backup and Utility Services
# Place in /opt/stacks/utilities/docker-compose.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
@@ -8,7 +6,6 @@
services: services:
# Backrest - Backup solution for restic # Backrest - Backup solution for restic
# Access at: https://backrest.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
backrest: backrest:
image: garethgeorge/backrest:latest image: garethgeorge/backrest:latest
@@ -44,7 +41,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.backrest.tls.certresolver=letsencrypt" - "traefik.http.routers.backrest.tls.certresolver=letsencrypt"
- "traefik.http.routers.backrest.middlewares=authelia@docker" - "traefik.http.routers.backrest.middlewares=authelia@docker"
@@ -56,7 +53,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# Duplicati - Backup solution # Duplicati - Backup solution
# Access at: https://duplicati.kelinreij.duckdns.org
duplicati: duplicati:
image: lscr.io/linuxserver/duplicati:2.0.7 image: lscr.io/linuxserver/duplicati:2.0.7
container_name: duplicati container_name: duplicati
@@ -90,7 +86,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.duplicati.tls.certresolver=letsencrypt" - "traefik.http.routers.duplicati.tls.certresolver=letsencrypt"
- "traefik.http.routers.duplicati.middlewares=authelia@docker" - "traefik.http.routers.duplicati.middlewares=authelia@docker"
@@ -134,7 +130,7 @@ services:
# Traefik labels # Traefik labels
- "traefik.enable=true" - "traefik.enable=true"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.formio.tls.certresolver=letsencrypt" - "traefik.http.routers.formio.tls.certresolver=letsencrypt"
- "traefik.http.routers.formio.middlewares=authelia@docker" - "traefik.http.routers.formio.middlewares=authelia@docker"
@@ -156,7 +152,6 @@ services:
- "homelab.description=Form.io database" - "homelab.description=Form.io database"
# Bitwarden (Vaultwarden) - Password manager # Bitwarden (Vaultwarden) - Password manager
# Access at: https://vault.kelinreij.duckdns.org
# Note: SSO disabled for browser extension and mobile app compatibility # Note: SSO disabled for browser extension and mobile app compatibility
vaultwarden: vaultwarden:
@@ -171,7 +166,7 @@ services:
volumes: volumes:
- ./vaultwarden/data:/data - ./vaultwarden/data:/data
environment: environment:
- DOMAIN=https://vault.kelinreij.duckdns.org - DOMAIN=https://vault.${DOMAIN}
- SIGNUPS_ALLOWED=${BITWARDEN_SIGNUPS_ALLOWED} - SIGNUPS_ALLOWED=${BITWARDEN_SIGNUPS_ALLOWED}
- INVITATIONS_ALLOWED=${BITWARDEN_INVITATIONS_ALLOWED} - INVITATIONS_ALLOWED=${BITWARDEN_INVITATIONS_ALLOWED}
- ADMIN_TOKEN=${BITWARDEN_ADMIN_TOKEN} - ADMIN_TOKEN=${BITWARDEN_ADMIN_TOKEN}
@@ -198,7 +193,7 @@ services:
# If Traefik is on a remote server: these labels are NOT USED; # If Traefik is on a remote server: these labels are NOT USED;
# configure external yml files in /traefik/dynamic folder instead. # configure external yml files in /traefik/dynamic folder instead.
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.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.entrypoints=websecure"
- "traefik.http.routers.vaultwarden.tls=true" - "traefik.http.routers.vaultwarden.tls=true"
- "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt" - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
@@ -238,11 +233,11 @@ networks:
x-dockge: x-dockge:
urls: urls:
- https://backrest.kelinreij.duckdns.org - https://backrest.${DOMAIN}
- https://192.168.4.4:9898 - https://192.168.4.4:9898
- https://duplicati.kelinreij.duckdns.org - https://duplicati.${DOMAIN}
- https://192.168.4.4:8200 - https://192.168.4.4:8200
- https://forms.kelinreij.duckdns.org - https://forms.${DOMAIN}
- https://192.168.4.4:3002 - https://192.168.4.4:3002
- https://vault.kelinreij.duckdns.org - https://vault.${DOMAIN}
- https://192.168.4.4:8091 - https://192.168.4.4:8091

View File

@@ -1,7 +1,4 @@
# VPN Stack # VPN Stack
# VPN client and VPN-routed download clients
# Place in /opt/stacks/vpn/docker-compose.yml
# RESTART POLICY GUIDE: # RESTART POLICY GUIDE:
# - unless-stopped: Core infrastructure services that should always run # - unless-stopped: Core infrastructure services that should always run
# - no: Services with Sablier lazy loading (start on-demand) # - no: Services with Sablier lazy loading (start on-demand)
@@ -10,7 +7,6 @@
services: services:
# Gluetun - VPN client (Surfshark) # Gluetun - VPN client (Surfshark)
# Routes download clients through VPN for security # Routes download clients through VPN for security
# VPN service should always run to maintain secure connections
gluetun: gluetun:
image: qmcgaw/gluetun:latest image: qmcgaw/gluetun:latest
container_name: gluetun container_name: gluetun
@@ -47,7 +43,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.qbittorrent.tls=true" - "traefik.http.routers.qbittorrent.tls=true"
- "traefik.http.routers.qbittorrent.middlewares=authelia@docker" - "traefik.http.routers.qbittorrent.middlewares=authelia@docker"
@@ -59,7 +55,6 @@ services:
- "sablier.sessionDuration=1h" - "sablier.sessionDuration=1h"
# qBittorrent - Torrent client # qBittorrent - Torrent client
# Routes through Gluetun VPN
qbittorrent: qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest image: lscr.io/linuxserver/qbittorrent:latest
deploy: deploy:
@@ -93,5 +88,5 @@ networks:
x-dockge: x-dockge:
urls: urls:
- https://qbit.kelinreij.duckdns.org - https://qbit.${DOMAIN}
- https://192.168.4.4:8081 - 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: services:
# DokuWiki - Wiki without database # DokuWiki - Wiki without database
# Access at: https://wiki.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
dokuwiki: dokuwiki:
image: lscr.io/linuxserver/dokuwiki:latest image: lscr.io/linuxserver/dokuwiki:latest
@@ -26,7 +31,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.dokuwiki.tls.certresolver=letsencrypt" - "traefik.http.routers.dokuwiki.tls.certresolver=letsencrypt"
- "traefik.http.routers.dokuwiki.middlewares=authelia@docker" - "traefik.http.routers.dokuwiki.middlewares=authelia@docker"
@@ -38,7 +43,6 @@ services:
- "sablier.start-on-demand=true" - "sablier.start-on-demand=true"
# BookStack - Documentation platform # BookStack - Documentation platform
# Access at: https://docs.kelinreij.duckdns.org
# Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity # Uses Sablier lazy loading - starts on-demand, stops after 5min inactivity
bookstack: bookstack:
image: lscr.io/linuxserver/bookstack:latest image: lscr.io/linuxserver/bookstack:latest
@@ -54,7 +58,7 @@ services:
environment: environment:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- APP_URL=https://bookstack.kelinreij.duckdns.org - APP_URL=https://bookstack.${DOMAIN}
- DB_HOST=bookstack-db - DB_HOST=bookstack-db
- DB_PORT=3306 - DB_PORT=3306
- DB_DATABASE=bookstack - DB_DATABASE=bookstack
@@ -78,7 +82,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.bookstack.tls.certresolver=letsencrypt" - "traefik.http.routers.bookstack.tls.certresolver=letsencrypt"
- "traefik.http.routers.bookstack.middlewares=authelia@docker" - "traefik.http.routers.bookstack.middlewares=authelia@docker"
@@ -107,7 +111,6 @@ services:
- "homelab.description=BookStack database" - "homelab.description=BookStack database"
# MediaWiki - Wiki platform # MediaWiki - Wiki platform
# Access at: https://mediawiki.kelinreij.duckdns.org
mediawiki: mediawiki:
image: mediawiki:latest image: mediawiki:latest
container_name: mediawiki container_name: mediawiki
@@ -142,7 +145,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=traefik-network" - "traefik.docker.network=traefik-network"
# Router configuration # 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.entrypoints=websecure"
- "traefik.http.routers.mediawiki.tls.certresolver=letsencrypt" - "traefik.http.routers.mediawiki.tls.certresolver=letsencrypt"
- "traefik.http.routers.mediawiki.middlewares=authelia@docker" - "traefik.http.routers.mediawiki.middlewares=authelia@docker"
@@ -183,9 +186,9 @@ networks:
x-dockge: x-dockge:
urls: urls:
# Proxied URLs (through Traefik) # Proxied URLs (through Traefik)
- https://bookstack.kelinreij.duckdns.org - https://bookstack.${DOMAIN}
- https://192.168.4.4:6875 - https://192.168.4.4:6875
- https://dokuwiki.kelinreij.duckdns.org - https://dokuwiki.${DOMAIN}
- https://192.168.4.4:8087 - https://192.168.4.4:8087
- https://mediawiki.kelinreij.duckdns.org - https://mediawiki.${DOMAIN}
- https://192.168.4.4:8086 - 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 #!/bin/bash
# EZ-Homelab Unified Setup & Deployment Script # EZ-Homelab Unified Setup & Deployment Script
# Two step process required for first-time setup: # Removed set -e to allow graceful error handling
# 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
# Debug logging configuration # Debug logging configuration
DEBUG=${DEBUG:-false} DEBUG=${DEBUG:-false}
@@ -95,25 +92,46 @@ load_env_file_safely() {
debug_log "Env file loaded successfully" 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 file_path="$1"
local fail_on_missing="${2:-false}" # New parameter to control failure behavior local fail_on_missing="${2:-false}" # New parameter to control failure behavior
local missing_vars="" local missing_vars=""
local replaced_count=0 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 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" debug_log "File $file_path does not exist"
return return
fi 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 # Find all ${VAR} patterns in the file
local vars=$(grep -o '\${[^}]*}' "$file_path" | sed 's/\${//' | sed 's/}//' | sort | uniq) local vars=$(grep -o '\${[^}]*}' "$file_path" | sed 's/\${//' | sed 's/}//' | sort | uniq)
debug_log "Found variables to replace: $vars" debug_log "Found variables to replace: $vars"
for var in $vars; do 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 # Skip derived variables that should not be replaced
case "$var" in case "$var" in
"ACME_EMAIL"|"AUTHELIA_ADMIN_EMAIL"|"SMTP_USERNAME"|"SMTP_PASSWORD") "ACME_EMAIL"|"AUTHELIA_ADMIN_EMAIL"|"SMTP_USERNAME"|"SMTP_PASSWORD")
@@ -122,23 +140,46 @@ replace_env_placeholders() {
;; ;;
esac esac
if [ -z "${!var:-}" ]; then if [ -z "${!var+x}" ]; then
log_warning "Environment variable $var not found in .env file" log_warning "Environment variable $var not found in .env file"
debug_log "Missing variable: $var" debug_log "Missing variable: $var"
missing_vars="$missing_vars $var" missing_vars="$missing_vars $var"
else 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 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)) replaced_count=$((replaced_count + 1))
fi fi
done done
debug_log "Replaced $replaced_count variables in $file_path" 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 # Handle missing variables
if [ -n "$missing_vars" ]; then 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 if [ "$fail_on_missing" = true ]; then
log_error "Critical environment variables missing: $missing_vars" log_error "Critical environment variables missing: $missing_vars"
debug_log "Failing deployment due to missing critical variables: $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 # Enhanced placeholder replacement for all configuration files
enhance_placeholder_replacement() { localize_deployment() {
log_info "Starting enhanced placeholder replacement..." log_info "Starting deployment localization..."
local processed_files=0 local processed_files=0
GLOBAL_MISSING_VARS=""
# Process docker-compose files # Process docker-compose files
if [ -d "$REPO_DIR/docker-compose" ]; then if [ -d "$REPO_DIR/docker-compose" ]; then
while IFS= read -r -d '' file_path; do while IFS= read -r -d '' file_path; do
if [ -f "$file_path" ]; then if [ -f "$file_path" ]; then
debug_log "Processing docker-compose file: $file_path" 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)) processed_files=$((processed_files + 1))
fi fi
done < <(find "$REPO_DIR/docker-compose" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null) 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 while IFS= read -r -d '' file_path; do
if [ -f "$file_path" ]; then if [ -f "$file_path" ]; then
debug_log "Processing config template file: $file_path" 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)) processed_files=$((processed_files + 1))
fi fi
done < <(find "$REPO_DIR/config-templates" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null) done < <(find "$REPO_DIR/config-templates" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null)
fi fi
log_success "Enhanced placeholder replacement completed - processed $processed_files files" log_success "Deployment localization completed - processed $processed_files files"
debug_log "Enhanced replacement completed for $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 # Function to generate shared CA for multi-server TLS
@@ -627,9 +675,12 @@ save_env_file() {
# Update HOMEPAGE_ALLOWED_HOSTS dynamically # Update HOMEPAGE_ALLOWED_HOSTS dynamically
if [ -n "${DOMAIN:-}" ] && [ -n "${SERVER_IP:-}" ]; then if [ -n "${DOMAIN:-}" ] && [ -n "${SERVER_IP:-}" ]; then
# Allow user to specify homepage subdomain # Extract Homepage port from compose file
HOMEPAGE_SUBDOMAIN="${HOMEPAGE_SUBDOMAIN:-homepage}" HOMEPAGE_PORT=$(grep -A1 'ports:' "$REPO_DIR/docker-compose/dashboards/docker-compose.yml" | grep -o '"[0-9]*:3000"' | cut -d'"' -f2 | cut -d: -f1)
HOMEPAGE_ALLOWED_HOSTS="${HOMEPAGE_SUBDOMAIN}.${DOMAIN},${SERVER_IP}:3003" 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" sudo -u "$ACTUAL_USER" sed -i "s|HOMEPAGE_ALLOWED_HOSTS=.*|HOMEPAGE_ALLOWED_HOSTS=$HOMEPAGE_ALLOWED_HOSTS|" "$REPO_DIR/.env"
fi 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" sudo -u "$ACTUAL_USER" sed -i "s%AUTHELIA_ADMIN_EMAIL=.*%AUTHELIA_ADMIN_EMAIL=$ADMIN_EMAIL%" "$REPO_DIR/.env"
# Generate password hash if needed # Generate password hash if needed
if [ -z "$AUTHELIA_ADMIN_PASSWORD" ]; then if [ -z "$AUTHELIA_ADMIN_PASSWORD_HASH" ]; then
log_info "Generating Authelia password hash..." log_info "Generating Authelia password hash..."
# Pull Authelia image if needed # Pull Authelia image if needed
if ! docker images | grep -q authelia/authelia; then if ! docker images | grep -q authelia/authelia; then
docker pull authelia/authelia:latest > /dev/null 2>&1 docker pull authelia/authelia:latest > /dev/null 2>&1
fi fi
AUTHELIA_ADMIN_PASSWORD=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | grep -o '\$argon2id.*') 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" ]; then if [ -z "$AUTHELIA_ADMIN_PASSWORD_HASH" ]; then
log_error "Failed to generate Authelia password hash. Please check that ADMIN_PASSWORD is set." log_error "Failed to generate Authelia password hash. Please check that ADMIN_PASSWORD is set."
exit 1 exit 1
fi fi
fi fi
# Save password hash # 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_HASH=.*%AUTHELIA_ADMIN_PASSWORD_HASH=\"$AUTHELIA_ADMIN_PASSWORD_HASH\"%" "$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"
fi 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" debug_log "Configuration saved to .env file"
log_success "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" debug_log "AUTHELIA_STORAGE_ENCRYPTION_KEY is missing"
fi fi
if [ -z "${AUTHELIA_ADMIN_PASSWORD:-}" ]; then if [ -z "${AUTHELIA_ADMIN_PASSWORD_HASH:-}" ]; then
missing_secrets="$missing_secrets AUTHELIA_ADMIN_PASSWORD" missing_secrets="$missing_secrets AUTHELIA_ADMIN_PASSWORD_HASH"
debug_log "AUTHELIA_ADMIN_PASSWORD is missing" debug_log "AUTHELIA_ADMIN_PASSWORD_HASH is missing"
fi fi
# Check other required variables # Check other required variables
@@ -752,100 +799,42 @@ validate_secrets() {
debug_log "Secret validation passed" debug_log "Secret validation passed"
} }
# System setup function (Docker, directories, etc.) # Install NVIDIA drivers function
system_setup() { install_nvidia() {
log_info "Performing system setup..." 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 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" "$@" exec sudo "$0" "$@"
fi fi
# Get the actual user who invoked sudo # Check for NVIDIA GPU
ACTUAL_USER=${SUDO_USER:-$USER} if ! lspci | grep -i nvidia > /dev/null; then
log_warning "No NVIDIA GPU detected. Skipping NVIDIA driver installation."
# Step 1: System Update return
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
fi fi
# Step 4: Install Docker Compose # Add NVIDIA repository
log_info "Step 4/10: Installing Docker Compose..." log_info "Adding NVIDIA repository..."
if command -v docker-compose &> /dev/null && docker-compose --version &> /dev/null; then apt-get update
log_success "Docker Compose is already installed ($(docker-compose --version))" apt-get install -y software-properties-common
else add-apt-repository -y ppa:graphics-drivers/ppa
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose apt-get update
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 # Install NVIDIA drivers (latest)
log_info "Step 5/10: Generating shared CA certificate for multi-server TLS..." log_info "Installing NVIDIA drivers..."
generate_shared_ca apt-get install -y nvidia-driver-470 # Adjust version as needed
# Step 6: Configure Docker TLS # Install NVIDIA Docker support
log_info "Step 6/10: Configuring Docker TLS..." log_info "Installing NVIDIA Docker support..."
setup_docker_tls 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_success "NVIDIA drivers and Docker support installed. A reboot may be required."
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
} }
# Deploy Dockge function # Deploy Dockge function
@@ -861,17 +850,17 @@ deploy_dockge() {
sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge/.env sudo chown "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge/.env
# Remove sensitive variables from dockge .env (Dockge doesn't need them) # 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_JWT_SECRET=/d' /opt/dockge/.env
sed -i '/^AUTHELIA_SESSION_SECRET=/d' /opt/dockge/.env sed -i '/^AUTHELIA_SESSION_SECRET=/d' /opt/dockge/.env
sed -i '/^AUTHELIA_STORAGE_ENCRYPTION_KEY=/d' /opt/dockge/.env sed -i '/^AUTHELIA_STORAGE_ENCRYPTION_KEY=/d' /opt/dockge/.env
# Replace placeholders in Dockge compose file # 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 # Deploy Dockge stack
cd /opt/dockge cd /opt/dockge
docker compose up -d run_cmd docker compose up -d || true
log_success "Dockge deployed" log_success "Dockge deployed"
echo "" echo ""
} }
@@ -908,7 +897,7 @@ deploy_core() {
sed -i '/^HOMEPAGE_VAR_/d' /opt/stacks/core/.env sed -i '/^HOMEPAGE_VAR_/d' /opt/stacks/core/.env
# Replace placeholders in core compose file (fail on missing critical vars) # 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 # Copy and configure Traefik config
debug_log "Setting up Traefik configuration" 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 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) # Don't fail on missing variables for external host files (they're optional)
if [[ "$config_file" == *external-host* ]]; then if [[ "$config_file" == *external-host* ]]; then
replace_env_placeholders "$config_file" false localize_yml_file "$config_file" false
else else
replace_env_placeholders "$config_file" true localize_yml_file "$config_file" true
fi fi
done done
@@ -966,7 +955,7 @@ deploy_core() {
# Replace all placeholders in Authelia config files # Replace all placeholders in Authelia config files
debug_log "Replacing 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 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 done
# Remove invalid session.cookies section from Authelia config (not supported in v4.37.5) # Remove invalid session.cookies section from Authelia config (not supported in v4.37.5)
@@ -988,7 +977,7 @@ deploy_core() {
# Deploy core stack # Deploy core stack
debug_log "Deploying core stack with docker compose" debug_log "Deploying core stack with docker compose"
cd /opt/stacks/core cd /opt/stacks/core
docker compose up -d run_cmd docker compose up -d || true
log_success "Core infrastructure deployed" log_success "Core infrastructure deployed"
echo "" echo ""
} }
@@ -1026,7 +1015,7 @@ deploy_infrastructure() {
sed -i '/^HOMEPAGE_VAR_/d' /opt/stacks/infrastructure/.env sed -i '/^HOMEPAGE_VAR_/d' /opt/stacks/infrastructure/.env
# Replace placeholders in infrastructure compose file # 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 # Copy any additional config directories
for config_dir in "$REPO_DIR/docker-compose/infrastructure"/*/; do for config_dir in "$REPO_DIR/docker-compose/infrastructure"/*/; do
@@ -1042,11 +1031,11 @@ deploy_infrastructure() {
fi fi
# Replace placeholders in infrastructure compose file # 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 # Deploy infrastructure stack
cd /opt/stacks/infrastructure cd /opt/stacks/infrastructure
docker compose up -d run_cmd docker compose up -d || true
log_success "Infrastructure stack deployed" log_success "Infrastructure stack deployed"
echo "" echo ""
} }
@@ -1082,7 +1071,7 @@ deploy_dashboards() {
sed -i '/^FORMIO_/d' /opt/stacks/dashboards/.env sed -i '/^FORMIO_/d' /opt/stacks/dashboards/.env
# Replace placeholders in dashboards compose file # 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 # Copy homepage config
if [ -d "$REPO_DIR/docker-compose/dashboards/homepage" ]; then if [ -d "$REPO_DIR/docker-compose/dashboards/homepage" ]; then
@@ -1091,7 +1080,7 @@ deploy_dashboards() {
# Replace placeholders in homepage config files # Replace placeholders in homepage config files
find /opt/stacks/dashboards/homepage -name "*.yaml" -type f | while read -r config_file; do 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 done
# Remove remote server entries from homepage services for single-server setup # Remove remote server entries from homepage services for single-server setup
@@ -1102,7 +1091,7 @@ deploy_dashboards() {
# Process template files and rename them # Process template files and rename them
find /opt/stacks/dashboards/homepage -name "*.template" -type f | while read -r template_file; do 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 # Rename template file to remove .template extension
new_file="${template_file%.template}" new_file="${template_file%.template}"
mv "$template_file" "$new_file" mv "$template_file" "$new_file"
@@ -1111,11 +1100,11 @@ deploy_dashboards() {
fi fi
# Replace placeholders in dashboards compose file # 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 # Deploy dashboards stack
cd /opt/stacks/dashboards cd /opt/stacks/dashboards
docker compose up -d run_cmd docker compose up -d || true
log_success "Dashboard stack deployed" log_success "Dashboard stack deployed"
echo "" echo ""
} }
@@ -1126,7 +1115,7 @@ perform_deployment() {
log_info "Starting deployment..." log_info "Starting deployment..."
# Initialize missing vars summary # Initialize missing vars summary
MISSING_VARS_SUMMARY="" GLOBAL_MISSING_VARS=""
TLS_ISSUES_SUMMARY="" TLS_ISSUES_SUMMARY=""
# Switch back to regular user if we were running as root # Switch back to regular user if we were running as root
@@ -1142,6 +1131,22 @@ perform_deployment() {
load_env_file_safely "$REPO_DIR/.env" load_env_file_safely "$REPO_DIR/.env"
debug_log "Environment loaded, DOMAIN=$DOMAIN, SERVER_IP=$SERVER_IP" 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 # Step 1: Create required directories
log_info "Step 1: Creating 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; } sudo mkdir -p /opt/stacks/core || { log_error "Failed to create /opt/stacks/core"; exit 1; }
@@ -1196,9 +1201,9 @@ perform_deployment() {
fi fi
# Report any missing variables # 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:" 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." log_info "Please update your .env file and redeploy affected stacks."
fi fi
@@ -1275,7 +1280,7 @@ setup_stacks_for_dockge() {
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$STACK_DIR/.env" sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$STACK_DIR/.env"
# Remove sensitive/unnecessary variables from stack .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_JWT_SECRET=/d' "$STACK_DIR/.env"
sed -i '/^AUTHELIA_SESSION_SECRET=/d' "$STACK_DIR/.env" sed -i '/^AUTHELIA_SESSION_SECRET=/d' "$STACK_DIR/.env"
sed -i '/^AUTHELIA_STORAGE_ENCRYPTION_KEY=/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" sed -i '/^HOMEPAGE_VAR_/d' "$STACK_DIR/.env"
# Replace placeholders in the compose file # 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 # Copy any additional config directories
for config_dir in "$REPO_STACK_DIR"/*/; do for config_dir in "$REPO_STACK_DIR"/*/; do
if [ -d "$config_dir" ] && [ "$(basename "$config_dir")" != "." ]; then if [ -d "$config_dir" ] && [ "$(basename "$config_dir")" != "." ]; then
cp -r "$config_dir" "$STACK_DIR/" cp -r "$config_dir" "$STACK_DIR/"
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$STACK_DIR/$(basename "$config_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 fi
done done
@@ -1340,12 +1350,226 @@ show_main_menu() {
echo "" 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 logic
main() { main() {
debug_log "main() called with arguments: $@" debug_log "main() called with arguments: $@"
log_info "EZ-Homelab Unified Setup & Deployment Script" log_info "EZ-Homelab Unified Setup & Deployment Script"
echo "" 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 # Load existing configuration
ENV_EXISTS=false ENV_EXISTS=false
if load_env_file; then if load_env_file; then
@@ -1381,6 +1605,18 @@ main() {
;; ;;
3) 3)
log_info "Selected: Deploy Additional Server" 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_CORE=false
DEPLOY_INFRASTRUCTURE=true DEPLOY_INFRASTRUCTURE=true
DEPLOY_DASHBOARDS=false DEPLOY_DASHBOARDS=false
@@ -1411,71 +1647,8 @@ main() {
echo "" echo ""
# Handle special menu options # Prepare deployment environment
if [ "$FORCE_SYSTEM_SETUP" = true ]; then prepare_deployment
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"
# Prompt for configuration values # Prompt for configuration values
validate_and_prompt_variables validate_and_prompt_variables
@@ -1484,7 +1657,7 @@ main() {
save_env_file save_env_file
# Perform enhanced placeholder replacement across all config files # Perform enhanced placeholder replacement across all config files
enhance_placeholder_replacement localize_deployment
# Validate secrets for core deployment # Validate secrets for core deployment
validate_secrets validate_secrets
@@ -1498,25 +1671,25 @@ main() {
echo "║ Deployment Complete! ║" echo "║ Deployment Complete! ║"
echo "║ SSL Certificates may take a few minutes to be issued. ║" echo "║ SSL Certificates may take a few minutes to be issued. ║"
echo "║ ║" echo "║ ║"
echo "║ https://dockge.kelinreij.duckdns.org ║" echo "║ https://dockge.${DOMAIN} "
echo "║ http://192.168.4.4:5001 ║" echo "║ http://${SERVER_IP}:5001 ║"
echo "║ ║" echo "║ ║"
echo "║ https://homepage.kelinreij.duckdns.org ║" echo "║ https://homepage.${DOMAIN} "
echo "║ http://192.168.4.4:3003 ║" echo "║ http://${SERVER_IP}:3003 ║"
echo "║ ║" echo "║ ║"
echo "╚═════════════════════════════════════════════════════════════╝" echo "╚═════════════════════════════════════════════════════════════╝"
# Show consolidated warnings if any # 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 "╔═════════════════════════════════════════════════════════════╗"
echo "║ ⚠️ WARNING ⚠️ ║" echo "║ ⚠️ WARNING ⚠️ ║"
echo "║ The following variables were not defined ║" echo "║ The following variables were not defined ║"
echo "║ If something isn't working as expected check these first ║" echo "║ If something isn't working as expected check these first ║"
echo "║ ║" echo "║ ║"
if [ -n "$MISSING_VARS_SUMMARY" ]; then if [ -n "$GLOBAL_MISSING_VARS" ]; then
log_warning "Missing Environment Variables:" log_warning "Missing Environment Variables:"
echo "$MISSING_VARS_SUMMARY" echo "$GLOBAL_MISSING_VARS"
echo "║ ║" echo "║ ║"
fi 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 "$@"