Compare commits
2 Commits
889a666c81
...
3cba0998a7
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cba0998a7 | |||
| a7c38cd58d |
+29
-22
@@ -7,32 +7,39 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Removed
|
---
|
||||||
- Path B install pipeline (setup, privileged-setup, user-operations,
|
|
||||||
services-improved, lib/privileges.sh) -- dead code, never wired in
|
## [1.0.1] - 2026-06-10
|
||||||
|
|
||||||
|
Stabilization pass: bug fixes, security hardening, and codebase consolidation
|
||||||
|
on the Path A install pipeline.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Infinite recursion in get_timezone_mount() and get_gpu_devices() on Linux (B1)
|
||||||
|
- ((x++)) abort under set -e across hops and install (B5)
|
||||||
|
- Glob stored as string breaking multi-user directory detection (B3)
|
||||||
|
- Missing hops_service_definitions.sh reference in firewall setup (B4)
|
||||||
|
- Linux-only guard missing from hops entry point (B6)
|
||||||
|
- eval echo "~$SUDO_USER" replaced with getent passwd in uninstall (B3/S5)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Version reset to 1.0.0
|
- Duplicate log, error_exit, warning, success, info removed from hops,
|
||||||
|
uninstall, and install; single canonical copies in lib/common.sh (A5, Q1)
|
||||||
|
- Duplicate validate_password, generate_secure_password, create_docker_networks,
|
||||||
|
validate_timezone removed from install; lib/security.sh, lib/docker.sh,
|
||||||
|
lib/validation.sh are now the sole definitions (A5)
|
||||||
|
- lib/docker.sh, lib/validation.sh, lib/security.sh use LIB_DIR instead of
|
||||||
|
SCRIPT_DIR so sourcing them inside a function does not clobber the caller
|
||||||
|
- validate_timezone updated to warn-and-default instead of error_exit
|
||||||
|
- validate_password updated to handle empty input (return 3)
|
||||||
|
- uninstall sources lib/common.sh directly, allowing standalone execution
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Path B install pipeline (setup, privileged-setup, user-operations,
|
||||||
|
services-improved, lib/privileges.sh) -- dead code, never wired in (A1, A3)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.0] - TBD
|
## [1.0.0] - TBD
|
||||||
|
|
||||||
Full rewrite and stabilization of the Path A install pipeline.
|
Full rewrite establishing the Path A install pipeline.
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Infinite recursion in get_timezone_mount() and get_gpu_devices() on Linux
|
|
||||||
- ((x++)) abort under set -e across hops and install
|
|
||||||
- Glob stored as string breaking multi-user directory detection
|
|
||||||
- Missing hops_service_definitions.sh reference in firewall setup
|
|
||||||
|
|
||||||
### Security
|
|
||||||
- Replace broken AES-GCM encryption with supported cipher
|
|
||||||
- Move passphrases off command line (use fd-based passphrase input)
|
|
||||||
- Remove committed default Authelia credential
|
|
||||||
- Use mktemp for temp files instead of predictable /tmp paths
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Single canonical service catalog (services)
|
|
||||||
- Latest image tags throughout
|
|
||||||
- lib/secrets.sh wired into install flow for .env encryption at rest
|
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ Generated by codebase audit (2026-06-10). Ranked by severity.
|
|||||||
|
|
||||||
## CRITICAL BUGS (breaks primary use cases)
|
## CRITICAL BUGS (breaks primary use cases)
|
||||||
|
|
||||||
### B1 -- Infinite recursion in `services` on Linux [CRITICAL]
|
### B1 -- Infinite recursion in `services` on Linux [CRITICAL] -- RESOLVED
|
||||||
- File: `services:25-46`
|
- File: `services:25-46`
|
||||||
- `get_timezone_mount()` and `get_gpu_devices()` call themselves on the non-Darwin
|
- `get_timezone_mount()` and `get_gpu_devices()` called themselves on the non-Darwin
|
||||||
branch via `echo "$(get_timezone_mount)"`. Hits bash FUNCNEST limit on every
|
branch. Fixed: both functions now return literal YAML strings directly.
|
||||||
Linux compose generation. Main `./hops` install is broken on Linux.
|
|
||||||
- Fix: replace the recursive calls with the literal YAML strings they should emit.
|
|
||||||
|
|
||||||
### B2 -- Brace mismatch in `lib/privileges.sh` [CRITICAL] -- RESOLVED: delete file
|
### B2 -- Brace mismatch in `lib/privileges.sh` [CRITICAL] -- RESOLVED: delete file
|
||||||
- File: `lib/privileges.sh:429,612`
|
- File: `lib/privileges.sh:429,612`
|
||||||
@@ -32,32 +30,22 @@ Generated by codebase audit (2026-06-10). Ranked by severity.
|
|||||||
|
|
||||||
## HIGH BUGS
|
## HIGH BUGS
|
||||||
|
|
||||||
### B3 -- Glob stored as string, directory detection always fails [HIGH]
|
### B3 -- Glob stored as string, directory detection always fails [HIGH] -- RESOLVED
|
||||||
- Files: `hops:154-166`, `uninstall:127-147`
|
- Files: `hops:154-166`, `uninstall:127-147`
|
||||||
- `homelab_dirs=( "/home/*/hops" )` stores a literal glob; the quoted for-loop
|
- Glob removed from array; expanded separately via `compgen -G "/home/*/hops"`.
|
||||||
never expands it. Multi-user detection is broken, `cd "$HOMELAB_DIR"` fails
|
Also fixed `eval echo "~$SUDO_USER"` -> `getent passwd` in `uninstall`.
|
||||||
under `set -e`.
|
|
||||||
- Fix: iterate unquoted or use `compgen -G "/home/*/hops"`.
|
|
||||||
|
|
||||||
### B4 -- Missing service definitions file reference [HIGH]
|
### B4 -- Missing service definitions file reference [HIGH] -- RESOLVED
|
||||||
- File: `install:916`
|
- File: `install:916`
|
||||||
- `setup_firewall()` sources `"$SCRIPT_DIR/hops_service_definitions.sh"` which
|
- Corrected source path from `hops_service_definitions.sh` to `services`.
|
||||||
does not exist (the file is named `services`). Per-service firewall rules are
|
|
||||||
silently never applied.
|
|
||||||
- Fix: correct the filename to `services`.
|
|
||||||
|
|
||||||
### B5 -- `((x++))` aborts script under `set -e` [HIGH]
|
### B5 -- `((x++))` aborts script under `set -e` [HIGH] -- RESOLVED
|
||||||
- Files: `hops:299,317`, `install:784`, and others
|
- Files: `hops`, `install`
|
||||||
- `((running_count++))` returns exit code 1 when the pre-increment value is 0,
|
- All `((x++))` occurrences replaced with `x=$((x + 1))`.
|
||||||
which kills the script under `set -e`.
|
|
||||||
- Fix: use `running_count=$((running_count + 1))` or append `|| true`.
|
|
||||||
|
|
||||||
### B6 -- `hops` entry point is Linux-only despite macOS library support [HIGH]
|
### B6 -- `hops` entry point is Linux-only despite macOS library support [HIGH] -- RESOLVED
|
||||||
- File: `hops:108-136,263`
|
- File: `hops`
|
||||||
- `check_dependencies` requires `systemctl`, `check_system_requirements` calls
|
- Added Linux-only guard at top of script; exits immediately with a clear error on non-Linux.
|
||||||
`free` and `df -BG`, `show_service_status` calls `systemctl`. All Linux-only.
|
|
||||||
The documented entry point fails immediately on macOS.
|
|
||||||
- Fix: add OS guards or document `hops` as Linux-only.
|
|
||||||
|
|
||||||
### B7 -- Port collisions not detected within a selection [HIGH]
|
### B7 -- Port collisions not detected within a selection [HIGH]
|
||||||
- File: `services` (port map)
|
- File: `services` (port map)
|
||||||
@@ -172,12 +160,16 @@ Generated by codebase audit (2026-06-10). Ranked by severity.
|
|||||||
- Fix passphrase-on-command-line exposure (S1, S2).
|
- Fix passphrase-on-command-line exposure (S1, S2).
|
||||||
- Wire encrypt/decrypt calls into `install` flow.
|
- Wire encrypt/decrypt calls into `install` flow.
|
||||||
|
|
||||||
### A5 -- `hops` duplicates functions from `lib/common.sh` [HIGH] -- DO FIRST
|
### A5 -- `hops` duplicates functions from `lib/common.sh` [HIGH] -- RESOLVED
|
||||||
- `log`, `error_exit`, `warning`, `success`, `info`, `validate_timezone`,
|
- `log`, `error_exit`, `warning`, `success`, `info`, `validate_timezone`,
|
||||||
`validate_password`, `generate_secure_password`, `create_docker_networks`,
|
`validate_password`, `generate_secure_password`, `create_docker_networks`
|
||||||
`get_service_port/image` are all defined twice (or three times).
|
removed from `hops`, `uninstall`, and `install`. Canonical copies kept in
|
||||||
- Fix: source `lib/common.sh` from `hops` and remove local duplicates.
|
`lib/common.sh`, `lib/security.sh`, `lib/validation.sh`, `lib/docker.sh`.
|
||||||
- Must be done before bug fixes to avoid patching the same logic in multiple places.
|
- Fixed `lib/docker.sh`, `lib/validation.sh`, `lib/security.sh` to use `LIB_DIR`
|
||||||
|
instead of `SCRIPT_DIR` so sourcing them inside a function doesn't clobber
|
||||||
|
the caller's `SCRIPT_DIR`.
|
||||||
|
- `validate_timezone` updated to warn-and-default instead of error_exit.
|
||||||
|
- `validate_password` updated to handle empty input (return 3).
|
||||||
|
|
||||||
### A6 -- Caddy is unreachable via the menu [LOW]
|
### A6 -- Caddy is unreachable via the menu [LOW]
|
||||||
- `services` defines `generate_caddy` but the `select_services` menu in
|
- `services` defines `generate_caddy` but the `select_services` menu in
|
||||||
@@ -248,10 +240,8 @@ Generated by codebase audit (2026-06-10). Ranked by severity.
|
|||||||
|
|
||||||
## CODE QUALITY
|
## CODE QUALITY
|
||||||
|
|
||||||
### Q1 -- Three separate error-handling implementations [HIGH] -- DO FIRST
|
### Q1 -- Three separate error-handling implementations [HIGH] -- RESOLVED
|
||||||
- `hops`, `uninstall`, and `lib/common.sh` each define their own `error_exit`
|
- Covered by A5; all resolved.
|
||||||
and `log` with different formats. Consolidate in `lib/common.sh`.
|
|
||||||
- Covered by A5; tracked here for completeness.
|
|
||||||
|
|
||||||
### Q2 -- `set -e` + intentional non-zero returns is a minefield [MED]
|
### Q2 -- `set -e` + intentional non-zero returns is a minefield [MED]
|
||||||
- `validate_password` returns 1/2/3, `check_port` returns 1 -- these work only
|
- `validate_password` returns 1/2/3, `check_port` returns 1 -- these work only
|
||||||
@@ -274,15 +264,15 @@ Generated by codebase audit (2026-06-10). Ranked by severity.
|
|||||||
|
|
||||||
### Cleanup first (do before any bug fixes)
|
### Cleanup first (do before any bug fixes)
|
||||||
1. [DONE] Delete Path B files (A1/A3)
|
1. [DONE] Delete Path B files (A1/A3)
|
||||||
2. Consolidate duplicate functions into `lib/common.sh` (A5, Q1) -- one copy to fix
|
2. [DONE] Consolidate duplicate functions into `lib/common.sh` (A5, Q1)
|
||||||
3. Reconcile `lib/docker.sh` service maps with `services` (A2) -- one catalog to fix
|
3. Reconcile `lib/docker.sh` service maps with `services` (A2) -- one catalog to fix
|
||||||
4. Remove debug echo statements from `lib/system.sh` (Q3) -- reduce noise
|
4. Remove debug echo statements from `lib/system.sh` (Q3) -- reduce noise
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
5. Fix B1 (infinite recursion in `services`) -- unblocks all Linux installs
|
5. [DONE] Fix B1 (infinite recursion in `services`)
|
||||||
6. Fix B5 (`((x++))` under `set -e`) -- prevents silent aborts
|
6. [DONE] Fix B5 (`((x++))` under `set -e`)
|
||||||
7. Fix B3 (glob directory detection) -- fixes multi-user and uninstall
|
7. [DONE] Fix B3 (glob directory detection)
|
||||||
8. Fix B4 (wrong filename in firewall setup)
|
8. [DONE] Fix B4 (wrong filename in firewall setup)
|
||||||
9. Fix B7 (intra-selection port collision detection)
|
9. Fix B7 (intra-selection port collision detection)
|
||||||
|
|
||||||
### Security pass
|
### Security pass
|
||||||
|
|||||||
@@ -7,8 +7,12 @@
|
|||||||
# Exit on any error
|
# Exit on any error
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Script version and metadata
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
||||||
readonly SCRIPT_VERSION="1.0.0"
|
echo "ERROR: HOPS requires Linux (Ubuntu 20.04+, Debian 11+, or Linux Mint 20+)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Script metadata (SCRIPT_VERSION defined in lib/common.sh)
|
||||||
readonly SCRIPT_NAME="HOPS"
|
readonly SCRIPT_NAME="HOPS"
|
||||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
@@ -17,53 +21,15 @@ readonly INSTALLER_SCRIPT="$SCRIPT_DIR/install"
|
|||||||
readonly UNINSTALLER_SCRIPT="$SCRIPT_DIR/uninstall"
|
readonly UNINSTALLER_SCRIPT="$SCRIPT_DIR/uninstall"
|
||||||
readonly SERVICE_DEFINITIONS="$SCRIPT_DIR/services"
|
readonly SERVICE_DEFINITIONS="$SCRIPT_DIR/services"
|
||||||
|
|
||||||
# Load system utilities
|
# Load shared utilities
|
||||||
|
source "$SCRIPT_DIR/lib/common.sh"
|
||||||
source "$SCRIPT_DIR/lib/system.sh"
|
source "$SCRIPT_DIR/lib/system.sh"
|
||||||
|
|
||||||
# Color codes are defined in lib/common.sh
|
|
||||||
|
|
||||||
# Logging setup (will be set by setup_logging)
|
|
||||||
LOG_DIR=""
|
|
||||||
LOG_FILE=""
|
|
||||||
|
|
||||||
# Initialize logging
|
# Initialize logging
|
||||||
init_logging() {
|
init_logging() {
|
||||||
setup_logging "hops-main"
|
setup_logging "hops-main"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Logging function
|
|
||||||
log() {
|
|
||||||
local message="$1"
|
|
||||||
local timestamp="$(date '+%Y-%m-%d %T')"
|
|
||||||
|
|
||||||
if [[ -w "$LOG_FILE" ]]; then
|
|
||||||
echo "$timestamp - $message" >> "$LOG_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "$message"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Error handling
|
|
||||||
error_exit() {
|
|
||||||
log "${RED}❌ ERROR: $1${NC}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Warning function
|
|
||||||
warning() {
|
|
||||||
log "${YELLOW}⚠️ WARNING: $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Success function
|
|
||||||
success() {
|
|
||||||
log "${GREEN}✅ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Info function
|
|
||||||
info() {
|
|
||||||
log "${BLUE}ℹ️ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear screen and show header
|
# Clear screen and show header
|
||||||
show_header() {
|
show_header() {
|
||||||
clear
|
clear
|
||||||
@@ -151,10 +117,10 @@ get_installation_status() {
|
|||||||
local status="not_installed"
|
local status="not_installed"
|
||||||
local homelab_dirs=(
|
local homelab_dirs=(
|
||||||
"$HOME/hops"
|
"$HOME/hops"
|
||||||
"/home/*/hops"
|
|
||||||
"/opt/hops"
|
"/opt/hops"
|
||||||
"/srv/hops"
|
"/srv/hops"
|
||||||
)
|
)
|
||||||
|
while IFS= read -r d; do homelab_dirs+=("$d"); done < <(compgen -G "/home/*/hops" 2>/dev/null)
|
||||||
|
|
||||||
# Check for existing installation
|
# Check for existing installation
|
||||||
for dir in "${homelab_dirs[@]}"; do
|
for dir in "${homelab_dirs[@]}"; do
|
||||||
@@ -291,14 +257,14 @@ show_service_status() {
|
|||||||
local service_port="${service_info#*:}"
|
local service_port="${service_info#*:}"
|
||||||
|
|
||||||
if docker ps --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
if docker ps --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
||||||
((total_count++))
|
total_count=$((total_count + 1))
|
||||||
local status_symbol="${GREEN}●${NC}"
|
local status_symbol="${GREEN}●${NC}"
|
||||||
local status_text="Running"
|
local status_text="Running"
|
||||||
|
|
||||||
# Check if port is accessible
|
# Check if port is accessible
|
||||||
if curl -sSf --max-time 2 --connect-timeout 1 "http://localhost:${service_port}" >/dev/null 2>&1; then
|
if curl -sSf --max-time 2 --connect-timeout 1 "http://localhost:${service_port}" >/dev/null 2>&1; then
|
||||||
status_text="Running & Accessible"
|
status_text="Running & Accessible"
|
||||||
((running_count++))
|
running_count=$((running_count + 1))
|
||||||
else
|
else
|
||||||
status_text="Running (starting up)"
|
status_text="Running (starting up)"
|
||||||
status_symbol="${YELLOW}●${NC}"
|
status_symbol="${YELLOW}●${NC}"
|
||||||
@@ -306,7 +272,7 @@ show_service_status() {
|
|||||||
|
|
||||||
printf " %s %-20s %s (:%s)\n" "$status_symbol" "$service_name" "$status_text" "$service_port"
|
printf " %s %-20s %s (:%s)\n" "$status_symbol" "$service_name" "$status_text" "$service_port"
|
||||||
elif docker ps -a --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
elif docker ps -a --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
||||||
((total_count++))
|
total_count=$((total_count + 1))
|
||||||
printf " %s %-20s %s\n" "${RED}●${NC}" "$service_name" "Stopped"
|
printf " %s %-20s %s\n" "${RED}●${NC}" "$service_name" "Stopped"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -471,7 +437,7 @@ show_access_info() {
|
|||||||
if docker ps --format "{{.Names}}" | grep -qi "${service_name,,}"; then
|
if docker ps --format "{{.Names}}" | grep -qi "${service_name,,}"; then
|
||||||
local url="http://${local_ip}:${service_port}${service_path}"
|
local url="http://${local_ip}:${service_port}${service_path}"
|
||||||
printf " ${GREEN}●${NC} %-15s %s\n" "$service_name" "$url"
|
printf " ${GREEN}●${NC} %-15s %s\n" "$service_name" "$url"
|
||||||
((active_services++))
|
active_services=$((active_services + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -518,7 +484,7 @@ show_logs() {
|
|||||||
local date=$(stat -c %y "$log_file" | cut -d' ' -f1)
|
local date=$(stat -c %y "$log_file" | cut -d' ' -f1)
|
||||||
|
|
||||||
printf " %d) %-40s (%s, %s)\n" "$count" "$basename_log" "$size" "$date"
|
printf " %d) %-40s (%s, %s)\n" "$count" "$basename_log" "$size" "$date"
|
||||||
((count++))
|
count=$((count + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
echo -e "\n${WHITE}Select a log file to view [1-${#log_files[@]}] or 0 to go back: ${NC}"
|
echo -e "\n${WHITE}Select a log file to view [1-${#log_files[@]}] or 0 to go back: ${NC}"
|
||||||
@@ -627,7 +593,7 @@ update_hops() {
|
|||||||
|
|
||||||
# Source the updated script to get new version
|
# Source the updated script to get new version
|
||||||
if [[ -f "$SCRIPT_DIR/hops" ]]; then
|
if [[ -f "$SCRIPT_DIR/hops" ]]; then
|
||||||
local new_version=$(grep '^readonly SCRIPT_VERSION=' "$SCRIPT_DIR/hops" | cut -d'"' -f2)
|
local new_version=$(grep '^readonly SCRIPT_VERSION=' "$SCRIPT_DIR/lib/common.sh" | cut -d'"' -f2)
|
||||||
success "Updated to version $new_version"
|
success "Updated to version $new_version"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ install_hops() {
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Script version for update tracking
|
# Script version for update tracking
|
||||||
local SCRIPT_VERSION="1.0.0"
|
|
||||||
local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
# Load system utilities
|
# Load shared utilities
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
source "$SCRIPT_DIR/lib/common.sh"
|
||||||
source "$SCRIPT_DIR/lib/system.sh"
|
source "$SCRIPT_DIR/lib/system.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/validation.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/security.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/docker.sh"
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# LOGGING SETUP
|
# LOGGING SETUP
|
||||||
@@ -174,8 +176,7 @@ EOF
|
|||||||
if [[ "$keep_tz" =~ ^[Nn]$ ]]; then
|
if [[ "$keep_tz" =~ ^[Nn]$ ]]; then
|
||||||
echo -e "Enter timezone (e.g., America/New_York, Europe/London): "
|
echo -e "Enter timezone (e.g., America/New_York, Europe/London): "
|
||||||
read -r user_timezone
|
read -r user_timezone
|
||||||
validate_timezone "$user_timezone"
|
TIMEZONE=$(validate_timezone "$user_timezone")
|
||||||
TIMEZONE="$user_timezone"
|
|
||||||
else
|
else
|
||||||
TIMEZONE=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "America/New_York")
|
TIMEZONE=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "America/New_York")
|
||||||
fi
|
fi
|
||||||
@@ -210,42 +211,6 @@ EOF
|
|||||||
log " AppData: $APPDATA_DIR"
|
log " AppData: $APPDATA_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------
|
|
||||||
# VALIDATION FUNCTIONS
|
|
||||||
# --------------------------------------------
|
|
||||||
validate_timezone() {
|
|
||||||
if ! timedatectl list-timezones | grep -qx "$1" 2>/dev/null; then
|
|
||||||
log "⚠️ Timezone '$1' invalid, defaulting to 'America/New_York'"
|
|
||||||
TIMEZONE="America/New_York"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_password() {
|
|
||||||
local password="$1"
|
|
||||||
local min_length="${2:-12}"
|
|
||||||
|
|
||||||
if [[ -z "$password" ]]; then
|
|
||||||
echo -e "\n🔐 Password must meet these requirements:"
|
|
||||||
echo " • Minimum $min_length characters"
|
|
||||||
echo " • At least one uppercase letter"
|
|
||||||
echo " • At least one lowercase letter"
|
|
||||||
echo " • At least one number"
|
|
||||||
echo " • At least one special character"
|
|
||||||
return 3
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#password} -lt $min_length ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "$password" =~ [A-Z] ]] || [[ ! "$password" =~ [a-z] ]] || \
|
|
||||||
[[ ! "$password" =~ [0-9] ]] || [[ ! "$password" =~ [^A-Za-z0-9] ]]; then
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
check_port() {
|
check_port() {
|
||||||
local PORT=$1
|
local PORT=$1
|
||||||
local SERVICE=$2
|
local SERVICE=$2
|
||||||
@@ -315,42 +280,6 @@ EOF
|
|||||||
error_exit "No Docker Compose detected. Please install Docker first."
|
error_exit "No Docker Compose detected. Please install Docker first."
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------
|
|
||||||
# IMPROVED PASSWORD GENERATION
|
|
||||||
# --------------------------------------------
|
|
||||||
generate_secure_password() {
|
|
||||||
local length="${1:-16}"
|
|
||||||
local max_attempts=5
|
|
||||||
local attempt=1
|
|
||||||
|
|
||||||
while [[ $attempt -le $max_attempts ]]; do
|
|
||||||
# Generate password with mixed case, numbers, and symbols
|
|
||||||
local password=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-${length})
|
|
||||||
|
|
||||||
# Ensure it meets complexity requirements
|
|
||||||
if validate_password "$password" "$length"; then
|
|
||||||
echo "$password"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
((attempt++))
|
|
||||||
done
|
|
||||||
|
|
||||||
# Fallback: construct a guaranteed compliant password
|
|
||||||
local upper=$(generate_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 2)
|
|
||||||
local lower=$(generate_chars 'abcdefghijklmnopqrstuvwxyz' 4)
|
|
||||||
local digits=$(generate_chars '0123456789' 2)
|
|
||||||
local symbols=$(generate_chars '!@#$%^&*' 2)
|
|
||||||
local remaining_length=$((length - 10))
|
|
||||||
|
|
||||||
if [[ $remaining_length -gt 0 ]]; then
|
|
||||||
local remaining=$(generate_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $remaining_length)
|
|
||||||
shuffle_string "${upper}${lower}${digits}${symbols}${remaining}"
|
|
||||||
else
|
|
||||||
shuffle_string "${upper}${lower}${digits}${symbols}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# ENVIRONMENT FILE GENERATION
|
# ENVIRONMENT FILE GENERATION
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
@@ -642,22 +571,6 @@ EOF
|
|||||||
create_docker_networks
|
create_docker_networks
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------
|
|
||||||
# NETWORK CREATION
|
|
||||||
# --------------------------------------------
|
|
||||||
create_docker_networks() {
|
|
||||||
log "🌐 Creating Docker networks..."
|
|
||||||
|
|
||||||
# Create traefik network if it doesn't exist
|
|
||||||
if ! docker network ls --format "{{.Name}}" | grep -q "^traefik$"; then
|
|
||||||
if docker network create traefik 2>/dev/null; then
|
|
||||||
log "✅ Created traefik network"
|
|
||||||
else
|
|
||||||
log "⚠️ Could not create traefik network (may already exist)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# ENHANCED DEPLOYMENT WITH ROLLBACK
|
# ENHANCED DEPLOYMENT WITH ROLLBACK
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
@@ -913,8 +826,8 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Allow service ports based on selection
|
# Allow service ports based on selection
|
||||||
if [[ -f "$SCRIPT_DIR/hops_service_definitions.sh" ]]; then
|
if [[ -f "$SCRIPT_DIR/services" ]]; then
|
||||||
source "$SCRIPT_DIR/hops_service_definitions.sh"
|
source "$SCRIPT_DIR/services"
|
||||||
|
|
||||||
for svc in "${SERVICES[@]}"; do
|
for svc in "${SERVICES[@]}"; do
|
||||||
local ports=$(get_service_ports "$svc")
|
local ports=$(get_service_ports "$svc")
|
||||||
@@ -1015,7 +928,7 @@ EOF
|
|||||||
local main_port=$(echo $ports | cut -d' ' -f1)
|
local main_port=$(echo $ports | cut -d' ' -f1)
|
||||||
if [[ -n "$main_port" ]]; then
|
if [[ -n "$main_port" ]]; then
|
||||||
echo " • $svc: http://$(get_primary_ip):$main_port"
|
echo " • $svc: http://$(get_primary_ip):$main_port"
|
||||||
((service_count++))
|
service_count=$((service_count + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
# HOPS - Common Utility Functions
|
# HOPS - Common Utility Functions
|
||||||
# Shared functions for logging, error handling, and UI
|
# Shared functions for logging, error handling, and UI
|
||||||
# Version: 1.0.0
|
|
||||||
|
|
||||||
# Prevent multiple sourcing
|
# Prevent multiple sourcing
|
||||||
if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
||||||
@@ -10,6 +9,8 @@ if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
readonly HOPS_COMMON_LOADED=1
|
readonly HOPS_COMMON_LOADED=1
|
||||||
|
|
||||||
|
readonly SCRIPT_VERSION="1.0.1"
|
||||||
|
|
||||||
# Color codes for output
|
# Color codes for output
|
||||||
readonly RED='\033[0;31m'
|
readonly RED='\033[0;31m'
|
||||||
readonly GREEN='\033[0;32m'
|
readonly GREEN='\033[0;32m'
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
# Version: 1.0.0
|
# Version: 1.0.0
|
||||||
|
|
||||||
# Source common functions
|
# Source common functions
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "$SCRIPT_DIR/common.sh"
|
source "$LIB_DIR/common.sh"
|
||||||
|
|
||||||
# Service definitions with pinned versions
|
# Service definitions with pinned versions
|
||||||
declare -A HOPS_SERVICES=(
|
declare -A HOPS_SERVICES=(
|
||||||
|
|||||||
+12
-2
@@ -5,14 +5,24 @@
|
|||||||
# Version: 1.0.0
|
# Version: 1.0.0
|
||||||
|
|
||||||
# Source common functions
|
# Source common functions
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "$SCRIPT_DIR/common.sh"
|
source "$LIB_DIR/common.sh"
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
validate_password() {
|
validate_password() {
|
||||||
local password="$1"
|
local password="$1"
|
||||||
local min_length="${2:-12}"
|
local min_length="${2:-12}"
|
||||||
|
|
||||||
|
if [[ -z "$password" ]]; then
|
||||||
|
echo -e "\n Password must meet these requirements:"
|
||||||
|
echo " - Minimum $min_length characters"
|
||||||
|
echo " - At least one uppercase letter"
|
||||||
|
echo " - At least one lowercase letter"
|
||||||
|
echo " - At least one number"
|
||||||
|
echo " - At least one special character"
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
|
||||||
# Check minimum length
|
# Check minimum length
|
||||||
if [[ ${#password} -lt $min_length ]]; then
|
if [[ ${#password} -lt $min_length ]]; then
|
||||||
debug "Password too short: ${#password} < $min_length"
|
debug "Password too short: ${#password} < $min_length"
|
||||||
|
|||||||
+9
-15
@@ -5,8 +5,8 @@
|
|||||||
# Version: 1.0.0
|
# Version: 1.0.0
|
||||||
|
|
||||||
# Source common functions
|
# Source common functions
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "$SCRIPT_DIR/common.sh"
|
source "$LIB_DIR/common.sh"
|
||||||
|
|
||||||
# Validate and sanitize directory path
|
# Validate and sanitize directory path
|
||||||
validate_directory_path() {
|
validate_directory_path() {
|
||||||
@@ -60,22 +60,16 @@ validate_directory_path() {
|
|||||||
echo "$path"
|
echo "$path"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Validate timezone
|
# Validate timezone -- returns the timezone string, defaulting to America/New_York if invalid
|
||||||
validate_timezone() {
|
validate_timezone() {
|
||||||
local timezone="$1"
|
local timezone="$1"
|
||||||
|
local default="America/New_York"
|
||||||
|
|
||||||
if [[ -z "$timezone" ]]; then
|
if [[ -z "$timezone" ]] || \
|
||||||
error_exit "Timezone cannot be empty"
|
[[ ! "$timezone" =~ ^[A-Za-z_]+(/[A-Za-z_]+)*$ ]] || \
|
||||||
fi
|
! timedatectl list-timezones 2>/dev/null | grep -qx "$timezone"; then
|
||||||
|
warning "Timezone '${timezone}' invalid, defaulting to '${default}'"
|
||||||
# Basic format validation
|
timezone="$default"
|
||||||
if [[ ! "$timezone" =~ ^[A-Za-z_]+(/[A-Za-z_]+)*$ ]]; then
|
|
||||||
error_exit "Invalid timezone format: $timezone"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if timezone file exists
|
|
||||||
if [[ ! -f "/usr/share/zoneinfo/$timezone" ]]; then
|
|
||||||
error_exit "Unknown timezone: $timezone"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$timezone"
|
echo "$timezone"
|
||||||
|
|||||||
@@ -24,24 +24,18 @@ EOF
|
|||||||
# Get timezone mount path for current platform
|
# Get timezone mount path for current platform
|
||||||
get_timezone_mount() {
|
get_timezone_mount() {
|
||||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
# macOS doesn't need timezone mount, use TZ environment variable
|
|
||||||
echo ""
|
echo ""
|
||||||
else
|
else
|
||||||
# Linux timezone mount
|
echo " - /etc/localtime:/etc/localtime:ro"
|
||||||
echo "$(get_timezone_mount)"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get GPU device access for current platform
|
# Get GPU device access for current platform
|
||||||
get_gpu_devices() {
|
get_gpu_devices() {
|
||||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
# macOS doesn't support GPU passthrough to Docker containers
|
|
||||||
echo ""
|
echo ""
|
||||||
else
|
else
|
||||||
# Linux GPU device access
|
printf " devices:\n - /dev/dri:/dev/dri\n"
|
||||||
cat <<EOF
|
|
||||||
$(get_gpu_devices)
|
|
||||||
EOF
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,29 +8,15 @@ uninstall_hops() {
|
|||||||
set +e
|
set +e
|
||||||
|
|
||||||
# Script version for consistency
|
# Script version for consistency
|
||||||
local SCRIPT_VERSION="1.0.0"
|
local _UNINSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Load shared utilities if not already loaded (allows standalone execution)
|
||||||
|
source "$_UNINSTALL_DIR/lib/common.sh"
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# LOGGING SETUP
|
# LOGGING SETUP
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
local LOG_DIR="/var/log/hops"
|
setup_logging "hops-uninstall"
|
||||||
local LOG_FILE="$LOG_DIR/hops-uninstall-$(date +%Y%m%d-%H%M%S).log"
|
|
||||||
mkdir -p "$LOG_DIR"
|
|
||||||
touch "$LOG_FILE"
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo -e "$(date '+%Y-%m-%d %T') - $1" | tee -a "$LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
error_exit() {
|
|
||||||
log "❌ ERROR: $1"
|
|
||||||
log "❌ Uninstallation failed. Check logs at: $LOG_FILE"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
warning() {
|
|
||||||
log "⚠️ WARNING: $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# HEADER
|
# HEADER
|
||||||
@@ -126,14 +112,14 @@ EOF
|
|||||||
find_homelab_directory() {
|
find_homelab_directory() {
|
||||||
local POSSIBLE_DIRS=(
|
local POSSIBLE_DIRS=(
|
||||||
"$HOME/hops"
|
"$HOME/hops"
|
||||||
"/home/*/hops"
|
|
||||||
"/opt/hops"
|
"/opt/hops"
|
||||||
"/srv/hops"
|
"/srv/hops"
|
||||||
)
|
)
|
||||||
|
while IFS= read -r d; do POSSIBLE_DIRS+=("$d"); done < <(compgen -G "/home/*/hops" 2>/dev/null)
|
||||||
|
|
||||||
# Try to find from running user's home first
|
|
||||||
if [[ -n "$SUDO_USER" ]]; then
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
local user_home=$(eval echo "~$SUDO_USER")
|
local user_home
|
||||||
|
user_home=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||||
POSSIBLE_DIRS=("$user_home/hops" "${POSSIBLE_DIRS[@]}")
|
POSSIBLE_DIRS=("$user_home/hops" "${POSSIBLE_DIRS[@]}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user