Compare commits

..

11 Commits

Author SHA1 Message Date
Stephen Klein 3cba0998a7 Consolidate duplicate functions, bump to v1.0.1
- Remove duplicate log/error_exit/warning/success/info from hops and
  uninstall; remove validate_password, generate_secure_password,
  create_docker_networks, validate_timezone from install. Single
  canonical copies now live in lib/common.sh, lib/security.sh,
  lib/validation.sh, and lib/docker.sh (A5, Q1)
- Fix lib/docker.sh, lib/validation.sh, lib/security.sh to use LIB_DIR
  instead of SCRIPT_DIR so sourcing them inside a function does not
  clobber the caller's SCRIPT_DIR
- Move SCRIPT_VERSION to lib/common.sh as the single source of truth;
  remove local declarations from hops, install, and uninstall
- uninstall now sources lib/common.sh directly for standalone safety
- validate_timezone updated to warn-and-default instead of error_exit
- validate_password updated to handle empty input (return 3)
- Update CHANGELOG and TODO to reflect resolved items (B1-B6, A1, A3,
  A5, Q1) and bump version to 1.0.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 22:11:34 -04:00
Stephen Klein a7c38cd58d Fix critical and high bugs B1-B6
- B1: Replace recursive get_timezone_mount/get_gpu_devices with literal YAML strings
- B3: Expand /home/*/hops glob via compgen -G instead of storing as array literal;
  fix eval echo ~$SUDO_USER -> getent passwd in uninstall
- B4: Correct services source path in setup_firewall (hops_service_definitions.sh -> services)
- B5: Replace all ((x++)) with x=$((x + 1)) to avoid set -e abort on zero pre-increment
- B6: Add Linux-only guard at top of hops entry point

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:54:46 -04:00
Stephen Klein 889a666c81 Add CLAUDE.md with static project context
Covers pipeline architecture, file map, key decisions, coding conventions,
and how to syntax-check. Points to TODO.md, CHANGELOG.md, and SERVICES.md
for dynamic information. Also removes CLAUDE.md from .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:41:00 -04:00
Stephen Klein 32f2e1b666 Add TODO: deprecate Readarr (M8)
Readarr has been retired upstream. Remove from service list and docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:29:19 -04:00
Stephen Klein 248c3c3dc7 Add TODO: investigate Profilarr inclusion (M7)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:28:16 -04:00
Stephen Klein b106cfc177 Remove macOS and Windows references from user-facing docs
Strip all macOS/Windows/WSL2 mentions from README.md and SERVICES.md.
Platform status, GPU support, permission examples, and config paths
now reflect Linux-only. TODO.md retains references as future roadmap tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:27:07 -04:00
Stephen Klein 1f823fa9d1 Add TODO: deprecate Watchtower (M6)
Auto-updating containers without review conflicts with HOPS managing
its own deployments and can silently break working installs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:24:51 -04:00
Stephen Klein cce25bfa4d Add TODO: review Huntarr for elfhosted rebuild (M5)
Current definition uses the retired plexguide image. Need to
evaluate and switch to the elfhosted-maintained rebuild before 1.0.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:23:39 -04:00
Stephen Klein f618ce2c69 Strip emojis from documentation
Remove all emoji characters from README.md and SERVICES.md.
Box-drawing characters and typographic arrows (U+2192) retained.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:21:11 -04:00
Stephen Klein a7213441da Stub out stale documentation, rebuild as Linux-only
ADVANCED.md, INSTALLATION.md, and TROUBLESHOOTING.md were ~60-70% stale:
references to deleted Path B scripts, macOS/WSL2 platform sections,
old GitHub URLs, and v3.1-3.3 version history. Pruned to accurate
Linux-only stubs to be rebuilt as the 1.0.0 codebase stabilises.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:17:16 -04:00
Stephen Klein 148a0827e3 Clean up stale README content
- Update platform badge and status to Linux-only (macOS/WSL2 future)
- Remove v3.2.0/v3.3.0 "What's New" sections
- Update git clone URL to git.canadabot.net
- Remove deleted setup script from chmod and quick start
- Remove user-operations commands (file deleted)
- Drop macOS directory paths from directory structure
- Update support links from GitHub to Gitea
- Update cross-platform feature claim to Linux Native

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 21:12:55 -04:00
17 changed files with 402 additions and 1642 deletions
-3
View File
@@ -1,8 +1,5 @@
# HOPS .gitignore # HOPS .gitignore
# Claude Code development documentation
CLAUDE.md
# Log files # Log files
*.log *.log
+23 -674
View File
@@ -1,715 +1,64 @@
# Advanced Configuration & Troubleshooting # Advanced Configuration
This guide covers advanced HOPS configuration, troubleshooting, security features, and system administration. This guide covers advanced HOPS configuration and system administration.
## 🔧 Advanced Configuration > **Note**: This document is being rebuilt alongside the 1.0.0 codebase.
> Content will be expanded as features are stabilized.
### Environment Variables ## Environment Variables
HOPS uses both encrypted secret management and traditional environment files for configuration. HOPS writes a `.env` file to `~/hops/` during installation. Key variables:
#### Encrypted Secret Management (v3.1.0+)
```bash ```bash
# Initialize secret management PUID=1000 # User ID (run: id -u)
./lib/secrets.sh init PGID=1000 # Group ID (run: id -g)
# Create encrypted environment
./lib/secrets.sh create
# Update values
./lib/secrets.sh update DOMAIN example.com
./lib/secrets.sh update ACME_EMAIL admin@example.com
# Retrieve values
./lib/secrets.sh get PUID
./lib/secrets.sh get DEFAULT_ADMIN_PASSWORD
# List all keys
./lib/secrets.sh list
# Backup encrypted secrets
./lib/secrets.sh backup /backup/location/
```
#### Traditional Environment File (~/hops/.env)
```bash
# User and Group Configuration
PUID=1000 # User ID
PGID=1000 # Group ID
TZ=America/New_York # Timezone TZ=America/New_York # Timezone
# Directory Configuration DATA_ROOT=/mnt/media # Media storage
DATA_ROOT=/mnt/media # Media storage (Linux) CONFIG_ROOT=/opt/appdata # App configurations
CONFIG_ROOT=/opt/appdata # App configurations (Linux)
# macOS paths (automatically set) DEFAULT_ADMIN_PASSWORD=... # Auto-generated
DATA_ROOT=/Users/username/hops/media DEFAULT_DB_PASSWORD=... # Auto-generated
CONFIG_ROOT=/Users/username/hops/config
# Security (auto-generated and encrypted)
DEFAULT_ADMIN_PASSWORD=... # Generated secure password
DEFAULT_DB_PASSWORD=... # Database password
JELLYFIN_PASSWORD=... # Service-specific passwords
# Optional: Custom Domain Configuration
DOMAIN=yourdomain.com
ACME_EMAIL=admin@yourdomain.com
# Optional: Service Overrides
JELLYFIN_PORT=8096
SONARR_PORT=8989
RADARR_PORT=7878
``` ```
### Service-Specific Configuration ## Docker Compose Commands
#### Reverse Proxy Setup
**Traefik Configuration:**
```bash
# Traefik automatically configured with labels
# Custom configuration in ~/hops/config/traefik/
# Example dynamic configuration
mkdir -p ~/hops/config/traefik/dynamic
cat > ~/hops/config/traefik/dynamic/middleware.yml << 'EOF'
http:
middlewares:
default-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
EOF
```
**Nginx Proxy Manager:**
- Access admin interface on port 81
- Default credentials: `admin@example.com` / `changeme`
- Configure SSL certificates through web interface
**Caddy Configuration:**
```bash
# Create Caddy configuration directory
mkdir -p ~/hops/config/caddy
# Create custom Caddyfile
cat > ~/hops/config/caddy/Caddyfile << 'EOF'
# Global options
{
email your-email@domain.com
# Optional: Use custom CA
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
# Main domain
yourdomain.com {
reverse_proxy jellyfin:8096
# Custom headers
header {
# Security headers
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
# Subdomain routing
sonarr.yourdomain.com {
reverse_proxy sonarr:8989
}
radarr.yourdomain.com {
reverse_proxy radarr:7878
}
# Internal services (authentication required)
portainer.yourdomain.com {
reverse_proxy portainer:9000
# Optional: IP allowlist
@internal {
remote_ip 192.168.1.0/24 10.0.0.0/8
}
handle @internal {
reverse_proxy portainer:9000
}
handle {
respond "Access denied" 403
}
}
EOF
```
#### Authelia Integration
```bash
# Authelia configuration
mkdir -p ~/hops/config/authelia
# Example configuration (simplified)
cat > ~/hops/config/authelia/configuration.yml << 'EOF'
theme: dark
default_redirection_url: https://yourdomain.com
server:
host: 0.0.0.0
port: 9091
log:
level: warn
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
access_control:
default_policy: deny
rules:
- domain: jellyfin.yourdomain.com
policy: bypass
- domain: "*.yourdomain.com"
policy: one_factor
session:
name: authelia_session
domain: yourdomain.com
regulation:
max_retries: 3
ban_time: 10m
storage:
local:
path: /config/db.sqlite3
notifier:
filesystem:
filename: /config/notification.txt
EOF
```
### Service Management Commands
#### User Operations Script (No Sudo Required)
```bash
# Service status and control
./user-operations status # View all service status
./user-operations status jellyfin # View specific service
./user-operations logs jellyfin # View service logs
./user-operations logs jellyfin -f # Follow logs
# Deployment operations
./user-operations deploy # Deploy all services
./user-operations stop # Stop all services
./user-operations restart # Restart all services
./user-operations restart jellyfin # Restart specific service
# Update operations
./user-operations update # Update all containers
./user-operations update jellyfin # Update specific container
```
#### Direct Docker Compose Commands
```bash ```bash
cd ~/hops cd ~/hops
# Service management
docker compose ps # List services docker compose ps # List services
docker compose up -d # Start all services docker compose up -d # Start all services
docker compose down # Stop all services docker compose down # Stop all services
docker compose restart # Restart all services docker compose restart [service] # Restart a service
docker compose restart jellyfin # Restart specific service docker compose logs -f [service] # Follow logs
docker compose logs --tail=100 [service] # Last 100 lines
# Logs and monitoring
docker compose logs # View all logs
docker compose logs -f jellyfin # Follow specific service logs
docker compose logs --tail=100 sonarr # Last 100 lines
# Updates and maintenance
docker compose pull # Pull new images docker compose pull # Pull new images
docker compose up -d --force-recreate # Recreate containers docker compose up -d --force-recreate # Recreate containers
docker compose down && docker compose up -d # Full restart
# Resource monitoring
docker stats # Real-time resource usage docker stats # Real-time resource usage
docker system df # Disk usage docker system df # Disk usage
docker system prune # Clean unused data docker system prune # Clean unused data
``` ```
### New Modular Architecture ## Firewall
HOPS v3.1.0+ introduces a modular library system: HOPS configures UFW automatically on Linux. Manual management:
```
hops/
├── lib/ # Shared libraries
│ ├── common.sh # Logging, UI, utilities
│ ├── system.sh # OS detection, requirements
│ ├── docker.sh # Docker operations
│ ├── security.sh # Security functions
│ ├── validation.sh # Input validation
│ ├── secrets.sh # Secret management
│ └── privileges.sh # Privilege separation
├── setup # Main installation wrapper
├── privileged-setup # Root-only operations
├── user-operations # User operations
├── services-improved # Enhanced service definitions
└── hops # Main script
```
#### Using Library Functions
```bash ```bash
# Source libraries in custom scripts
source lib/common.sh
source lib/system.sh
# Use logging functions
log "INFO" "Starting custom operation"
warning "This is a warning message"
error_exit "Fatal error occurred"
# Use system functions
detect_os
check_system_requirements 2 10 # 2GB RAM, 10GB disk
# Use validation functions
validate_port "8080"
validate_domain "example.com"
```
## 🔒 Security Features & Hardening
### Automatic Security Hardening
HOPS automatically implements several security measures:
#### Firewall Configuration (Linux)
```bash
# UFW rules automatically created
sudo ufw status sudo ufw status
# Manual firewall management
sudo ufw allow 8096/tcp comment "Jellyfin" sudo ufw allow 8096/tcp comment "Jellyfin"
sudo ufw allow 9000/tcp comment "Portainer" sudo ufw delete allow 8096/tcp
sudo ufw delete allow 8096/tcp # Remove rule
``` ```
#### File Permissions ## File Permissions
```bash
# Automatic permission hardening
# Secrets: 600 (owner read/write only)
# Configs: 644 (owner write, group/other read)
# Scripts: 755 (executable)
# Manual permission fixes ```bash
chmod 600 ~/hops/.env chmod 600 ~/hops/.env
chmod 644 ~/hops/docker-compose.yml chmod 644 ~/hops/docker-compose.yml
chmod 755 ~/hops/user-operations
```
#### Network Isolation
```bash
# Docker network isolation
docker network ls | grep homelab
docker network inspect homelab
# View network configuration
docker compose config | grep network
```
### Security Auditing
```bash
# Run security audit
./lib/security.sh audit
# Check for security issues
./lib/security.sh check-passwords
./lib/security.sh check-permissions
./lib/security.sh check-firewall
# Security recommendations
./lib/security.sh recommendations
```
### SSL/TLS Configuration
#### Traefik SSL
Traefik automatically handles SSL certificates with Let's Encrypt:
```bash
# Check certificate status
docker compose logs traefik | grep -i certificate
# Manual certificate renewal (if needed)
docker compose restart traefik
```
#### Custom SSL Certificates
```bash
# For custom certificates, place in:
# ~/hops/config/traefik/certs/
# - yourdomain.com.crt
# - yourdomain.com.key
# Update Traefik configuration to use custom certs
```
## 🆘 Troubleshooting
### Common Issues & Solutions
#### Docker Issues
**Docker Not Starting:**
```bash
# Check Docker status
systemctl status docker
# Restart Docker
sudo systemctl restart docker
# Check Docker logs
journalctl -u docker --since "1 hour ago"
# Reinstall Docker (Linux)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
```
**Docker Compose Errors:**
```bash
# Validate compose file
docker compose config
# Check for syntax errors
docker compose config --quiet
# Force recreate containers
docker compose up -d --force-recreate
```
#### Service-Specific Issues
**Service Won't Start:**
```bash
# Check service logs
docker compose logs [service-name]
# Check container status
docker compose ps
# Restart service
docker compose restart [service-name]
# Check port conflicts
sudo lsof -i :[port-number]
```
**Permission Issues:**
```bash
# Fix ownership (Linux)
sudo chown -R $USER:$USER /opt/appdata sudo chown -R $USER:$USER /opt/appdata
sudo chown -R $USER:$USER /mnt/media sudo chown -R $USER:$USER /mnt/media
sudo chown -R $USER:$USER ~/hops
# Fix ownership (macOS)
sudo chown -R $USER:$USER ~/hops/config
sudo chown -R $USER:$USER ~/hops/media
# Check PUID/PGID values
id $USER # Should match PUID/PGID in .env
```
**Database Issues:**
```bash
# Reset service database (example: Sonarr)
docker compose down sonarr
rm -rf ~/hops/config/sonarr/sonarr.db* # macOS
rm -rf /opt/appdata/sonarr/sonarr.db* # Linux
docker compose up -d sonarr
```
#### Network Issues
**Can't Access Services:**
```bash
# Check if services are running
docker compose ps
# Check port mapping
docker compose port jellyfin 8096
# Check firewall (Linux)
sudo ufw status
# Check local firewall (macOS)
# System Preferences → Security & Privacy → Firewall
# Find container IP
docker inspect jellyfin | grep IPAddress
```
**Reverse Proxy Issues:**
```bash
# Check proxy logs
docker compose logs traefik
docker compose logs nginx-proxy-manager
# Verify DNS resolution
nslookup yourdomain.com
# Check certificate status
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
```
#### Platform-Specific Issues
**macOS Issues:**
```bash
# Docker Desktop not starting
open /Applications/Docker.app
# Homebrew issues
brew doctor
brew update && brew upgrade
# Fix keychain authentication
security unlock-keychain ~/Library/Keychains/login.keychain
```
**Windows/WSL2 Issues:**
```bash
# WSL2 not starting
wsl --shutdown
wsl -d Ubuntu-22.04
# Docker Desktop WSL2 integration
# Check Docker Desktop → Settings → Resources → WSL Integration
# File permission issues
# Ensure files are in WSL2 filesystem, not /mnt/c/
```
### Log Analysis
#### Log Locations
- **Installation Logs**:
- Linux: `/var/log/hops/`
- macOS: `/usr/local/var/log/hops/`
- Windows: `~/hops/logs/`
- **Service Logs**: `docker compose logs [service-name]`
- **System Logs**: `journalctl -u docker`
#### Log Analysis Commands
```bash
# HOPS installation logs
sudo tail -f /var/log/hops/hops-main-*.log
# Service logs with filtering
docker compose logs jellyfin | grep -i error
docker compose logs --since="1h" sonarr
docker compose logs --tail=100 radarr
# System resource logs
dmesg | grep -i memory
journalctl --since="1 hour ago" | grep docker
```
### Recovery Procedures
#### Service Recovery
```bash
# Stop problematic service
docker compose stop [service-name]
# Remove container (keeps data)
docker compose rm [service-name]
# Recreate service
docker compose up -d [service-name]
# Full service reset (destroys data)
docker compose down [service-name]
rm -rf /path/to/service/config
docker compose up -d [service-name]
```
#### Complete System Recovery
```bash
# Stop all services
docker compose down
# Backup current state
sudo tar -czf hops-backup-$(date +%Y%m%d).tar.gz ~/hops /opt/appdata
# Clean Docker system
docker system prune -a
docker volume prune
# Restart from backup
cd ~/hops
docker compose up -d
# Or reinstall HOPS
sudo ./uninstall
sudo ./setup
```
## 📊 Performance Tuning
### Resource Monitoring
```bash
# Container resource usage
docker stats
# System resource usage
htop
iotop
free -h
df -h
# Service-specific monitoring
docker compose exec portainer sh
# Access Portainer for detailed monitoring
```
### Optimization Strategies
#### For Low-Resource Systems (2-4GB RAM)
```bash
# Recommended minimal stack
# - Jellyfin (media server)
# - qBittorrent (download)
# - Sonarr (TV management)
# - Portainer (monitoring)
# Resource limits in docker-compose.yml
services:
jellyfin:
mem_limit: 1g
cpus: '2'
sonarr:
mem_limit: 512m
cpus: '1'
```
#### For High-Performance Systems (8GB+ RAM)
```bash
# Full stack deployment
# Enable GPU transcoding
# Use multiple download clients
# Add monitoring stack
# GPU support (Linux only)
# Intel GPU passthrough automatically configured
devices:
- /dev/dri:/dev/dri
```
#### Storage Optimization
```bash
# Use SSD for application data
# HDD for media storage
# Separate Docker volumes
# Example optimized storage layout
/opt/appdata -> SSD
/mnt/media -> HDD array
~/hops -> SSD
```
### Update Management
```bash
# Automated updates with Watchtower
# Configure update schedule
WATCHTOWER_SCHEDULE=0 0 2 * * * # 2 AM daily
# Manual update process
docker compose pull
docker compose up -d
# Rollback to previous version
docker compose down
docker tag service:latest service:backup
docker pull service:previous
docker compose up -d
```
## 🔧 Advanced Features
### Custom Service Definitions
```bash
# Add custom services to docker-compose.yml
services:
custom-service:
image: custom/image:latest
container_name: custom-service
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ${CONFIG_ROOT}/custom:/config
- ${DATA_ROOT}:/data
ports:
- "8999:8999"
restart: unless-stopped
networks:
- homelab
```
### Backup Automation
```bash
# Automated backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backup/hops"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup configurations
tar -czf "$BACKUP_DIR/config-$DATE.tar.gz" /opt/appdata
# Backup compose files
cp ~/hops/.env ~/hops/docker-compose.yml "$BACKUP_DIR/"
# Backup encrypted secrets
./lib/secrets.sh backup "$BACKUP_DIR/secrets-$DATE.enc"
# Clean old backups (keep 7 days)
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
```
### Development & Testing
```bash
# Development setup
git clone https://github.com/skiercm/hops.git
cd hops
# Syntax validation
bash -n lib/*.sh
bash -n *.sh
# Test service definitions
./services-improved list
./services-improved generate jellyfin
# Test installation in VM/container
# Use test environment before production deployment
``` ```
--- ---
For installation guides, see [INSTALLATION.md](INSTALLATION.md). For installation, see [INSTALLATION.md](INSTALLATION.md).
For service information, see [SERVICES.md](SERVICES.md). For troubleshooting, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
+29 -22
View File
@@ -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
+84
View File
@@ -0,0 +1,84 @@
# HOPS - Claude Code Project Guide
## Project Overview
HOPS (Homelab Orchestration Provisioning Script) is a bash-based tool that
deploys homelab infrastructure via Docker Compose. It presents an interactive
menu for selecting services, generates a docker-compose.yml and .env, installs
Docker if needed, and manages the lifecycle of the deployment.
**Target platform**: Linux only (Ubuntu 20.04+, Debian 11+, Linux Mint 20+).
macOS and WSL2 are on the future roadmap, not in scope for 1.0.0.
## Install Pipeline (Path A -- canonical)
```
sudo ./hops # Entry point and management menu
-> install # Full installation logic (sourced by hops)
-> services # Service definitions and compose generators (sourced by install)
-> uninstall # Removal logic (standalone)
```
All other top-level scripts and `lib/privileges.sh` were Path B artifacts and
have been deleted. Do not recreate them.
## File Map
| File | Purpose |
|------|---------|
| `hops` | Entry point, management menu, update logic |
| `install` | Full install flow: Docker setup, service selection, compose generation, firewall |
| `uninstall` | Reversal of install: stops containers, removes dirs, optionally purges Docker |
| `services` | Single canonical service catalog: compose generators, port map, image refs |
| `lib/common.sh` | Logging, UI helpers, shared utilities -- source this, don't duplicate |
| `lib/system.sh` | OS detection, Docker install, system requirement checks |
| `lib/docker.sh` | Docker service management, status checks, network helpers |
| `lib/security.sh` | Password generation, validation, firewall, security audit |
| `lib/validation.sh` | Input sanitisation and validation functions |
| `lib/secrets.sh` | .env encryption at rest (being fixed and wired in -- see TODO M4/A4/S1) |
## Key Architectural Decisions
- **One service catalog**: `services` is the single source of truth for images,
ports, and compose definitions. `lib/docker.sh` has a secondary map that must
be kept in sync until reconciled (TODO A2).
- **Latest image tags**: all service images use `latest`, not pinned versions.
- **Secrets**: `lib/secrets.sh` will encrypt the `.env` file at rest using gpg.
Currently being fixed -- the old AES-GCM implementation was broken.
- **No duplicate functions**: consolidate into `lib/common.sh`. Do not define
`log`, `error_exit`, `warning`, `success`, or `info` in any other file.
## Coding Conventions
- Bash only. No Python, no Node, no external interpreters required at runtime.
- ASCII only -- no Unicode, no emojis in scripts or output strings.
- No comments explaining what the code does. Only add a comment when the WHY
is non-obvious (hidden constraint, workaround, subtle invariant).
- Error handling: use `set -e` with care. Arithmetic increments must use
`x=$((x + 1))` not `((x++))` -- the latter aborts under `set -e` when the
pre-increment value is 0.
- Temp files: always use `mktemp`, never `/tmp/name_$$`.
- User home resolution: use `getent passwd "$SUDO_USER" | cut -d: -f6`,
never `eval echo "~$SUDO_USER"`.
- Command arrays: build commands as bash arrays, not strings, to avoid
word-splitting on unusual usernames or paths.
## Running / Testing
There is no test suite. Validate with:
```bash
bash -n hops install uninstall services # syntax check
bash -n lib/*.sh
```
Full integration testing requires a Linux VM. The scripts must be run as root
or via sudo on a supported distro.
## Dynamic Information
The following docs change frequently -- check them rather than this file:
- **TODO.md** -- prioritised bug list, cleanup tasks, and feature backlog
- **CHANGELOG.md** -- version history and unreleased changes
- **SERVICES.md** -- supported service list with ports and upstream links
+28 -331
View File
@@ -1,78 +1,37 @@
# Installation Guide # Installation Guide
This guide provides detailed installation instructions for all supported platforms. > **Note**: This document is being rebuilt alongside the 1.0.0 codebase.
> Content will be expanded as features are stabilized.
## 🔧 System Requirements ## System Requirements
### Minimum Requirements - **OS**: Ubuntu 20.04+, Debian 11+, or Linux Mint 20+ (x86_64)
- **RAM**: 2GB (4GB+ recommended) - **RAM**: 2GB minimum, 4GB+ recommended
- **Storage**: 10GB free space (more for media storage) - **Storage**: 10GB free (more for media)
- **CPU**: 2 cores recommended - **CPU**: 2 cores recommended
- **Network**: Internet connection required - **Access**: sudo privileges
### Platform-Specific Requirements ## Installation
#### Linux (Ubuntu/Debian/Mint)
- **OS**: Ubuntu 20.04+, Debian 11+, or Linux Mint 20+
- **Architecture**: x86_64
- **Access**: Root/sudo privileges
- **Dependencies**: Automatically installed
#### macOS
- **OS**: macOS 11.0+ (Big Sur)
- **Architecture**: Intel (x86_64) or Apple Silicon (ARM64)
- **Access**: Admin privileges (sudo)
- **Dependencies**: Homebrew and Docker Desktop installed automatically
#### Windows (WSL2)
- **OS**: Windows 10 (build 19041+) or Windows 11
- **WSL**: WSL2 enabled with Ubuntu 20.04+ distribution
- **Docker**: Docker Desktop with WSL2 backend
- **Access**: Admin access for initial setup
- **Architecture**: x86_64 with virtualization support
- **BIOS**: Hyper-V and virtualization enabled
⚠️ **Note**: Windows support has limited testing. Proceed with caution and ensure backups.
## 🐧 Linux Installation
### Quick Install
```bash ```bash
# Download HOPS git clone https://git.canadabot.net/canadabot/hops.git
git clone https://github.com/skiercm/hops.git
cd hops cd hops
chmod +x hops install uninstall setup chmod +x hops install uninstall
# Run installation
sudo ./setup
```
### Manual Installation (Advanced)
```bash
# Two-phase installation for enhanced security
sudo ./privileged-setup # Root operations
./user-operations generate <services> # User operations
./user-operations deploy # Deploy services
# Legacy method (still supported)
sudo ./hops sudo ./hops
``` ```
### What Gets Installed HOPS will install Docker automatically if not present, then walk you through
- Docker Engine (via official Docker script) selecting and deploying services interactively.
- Docker Compose
- Required system packages ## Directory Structure
- UFW firewall rules (automatic)
- Service directories and permissions
### Directory Structure
``` ```
~/hops/ # Main deployment directory ~/hops/ # Deployment directory
├── docker-compose.yml # Service definitions ├── docker-compose.yml # Generated service definitions
├── .env # Environment variables ├── .env # Environment variables and secrets
└── logs/ # Application logs └── logs/ # HOPS logs
/opt/appdata/ # Application configurations /opt/appdata/ # Application config data
├── jellyfin/ ├── jellyfin/
├── sonarr/ ├── sonarr/
└── [other services]/ └── [other services]/
@@ -84,282 +43,20 @@ sudo ./hops
└── downloads/ └── downloads/
``` ```
## 🍎 macOS Installation ## Post-Installation
### Prerequisites Setup
HOPS automatically handles dependency installation on macOS, but you can pre-install if desired:
```bash ```bash
# Optional: Install Homebrew (will be done automatically) # Check service status
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" cd ~/hops && docker compose ps
# Optional: Install Docker Desktop (will be done automatically) # View logs
brew install --cask docker docker compose logs -f [service-name]
```
### HOPS Installation # Access management interface
```bash
# Download HOPS
git clone https://github.com/skiercm/hops.git
cd hops
chmod +x hops install uninstall setup
# Run installation with admin privileges
sudo ./setup
```
### macOS-Specific Features
- **Docker Desktop Integration**: Automatic installation and startup
- **Keychain Authentication**: Secure Docker authentication
- **User Directory Structure**: All files in user home directory
- **Automatic Dependency Resolution**: Homebrew packages installed as needed
### Directory Structure (macOS)
```
~/hops/ # Main deployment directory
├── docker-compose.yml # Service definitions
├── .env # Environment variables
├── logs/ # Application logs
├── config/ # Application configurations
│ ├── jellyfin/
│ ├── sonarr/
│ └── [other services]/
└── media/ # Media storage
├── movies/
├── tv/
├── music/
└── downloads/
```
### Important Notes for macOS
- **GPU Acceleration**: Not available (Docker containers cannot access macOS GPU)
- **Firewall**: Manual configuration required (automatic UFW setup skipped)
- **File Permissions**: Uses user's home directory for better compatibility
- **Performance**: Excellent performance on both Intel and Apple Silicon
## 🪟 Windows Installation (WSL2)
Windows support uses WSL2 (Windows Subsystem for Linux) for excellent Linux compatibility.
### Step 1: Enable WSL2
```powershell
# Run in PowerShell as Administrator
wsl --install
# Restart computer when prompted
```
### Step 2: Install Ubuntu Distribution
```powershell
# Install Ubuntu 22.04 LTS (recommended)
wsl --install Ubuntu-22.04
# Follow prompts to create username and password
```
### Step 3: Install Docker Desktop
1. Download [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
2. Enable WSL2 integration during installation
3. Ensure "Use WSL 2 based engine" is enabled in Docker settings
4. Enable integration with your Ubuntu distribution
### Step 4: Install HOPS
```bash
# Launch Ubuntu from Start Menu or run: wsl -d Ubuntu-22.04
cd ~
git clone https://github.com/skiercm/hops.git
cd hops
chmod +x hops install uninstall setup
# Run installation (same as Linux)
sudo ./setup
```
### Windows-Specific Considerations
#### File System Performance
- **Always run HOPS from WSL2 filesystem** (`~/hops/`) for optimal performance
- **Avoid Windows drives** (`/mnt/c/`) as they have significant performance penalties
- WSL2 provides 95% of native Linux performance when using WSL2 filesystem
#### Media Access
Your Windows media can be accessed from WSL2:
- `C:\Users\YourName\``/mnt/c/Users/YourName/`
- External drives → `/mnt/d/`, `/mnt/e/`, etc.
#### Service Access
- **Web interfaces**: Accessible from Windows browsers using WSL2 IP or localhost
- **File shares**: Available in Windows Explorer via `\\wsl.localhost\Ubuntu-22.04\home\username\hops\`
- **Network**: Services are accessible from both Windows and WSL2
#### Directory Structure (Windows/WSL2)
```
# WSL2 filesystem (recommended location)
~/hops/ # Main deployment directory
├── docker-compose.yml # Service definitions
├── .env # Environment variables
└── logs/ # Application logs
/opt/appdata/ # Application configurations
├── jellyfin/
├── sonarr/
└── [other services]/
/mnt/media/ # Media storage (can link to Windows drives)
├── movies/ -> /mnt/d/Movies/
├── tv/ -> /mnt/d/TV/
├── music/ -> /mnt/d/Music/
└── downloads/
```
## 🔧 Installation Options
### Option 1: Secure Installation (Recommended)
```bash
sudo ./setup
```
- **Best Practice**: Separates privileged and user operations
- **Enhanced Security**: Minimizes root access time
- **User-Friendly**: Guided interactive setup
### Option 2: Manual Two-Phase Installation
```bash
sudo ./privileged-setup # Root-only operations
./user-operations generate [services] # Select and generate services
./user-operations deploy # Deploy services
```
- **Advanced Users**: Full control over each phase
- **Automation**: Can be scripted for multiple deployments
- **Security**: Clear separation of privileged operations
### Option 3: Legacy Installation
```bash
sudo ./hops sudo ./hops
``` ```
- **Compatibility**: Original installation method
- **Full-Featured**: Complete management interface
- **Reliability**: Extensively tested method
## 🔍 Post-Installation Verification ## Getting Help
### Check Service Status - Issues: [git.canadabot.net/canadabot/hops/issues](https://git.canadabot.net/canadabot/hops/issues)
```bash - Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
# Via HOPS management interface
sudo ./hops
# Select option 4: Service Status
# Via user operations
./user-operations status
# Direct Docker commands
cd ~/hops
docker compose ps
```
### Access Service URLs
After installation, HOPS provides URLs for all services:
```
📱 Access your services at:
● Jellyfin http://192.168.1.100:8096
● Sonarr http://192.168.1.100:8989
● Radarr http://192.168.1.100:7878
● Portainer http://192.168.1.100:9000
```
### Verify File Permissions
```bash
# Check directory ownership
ls -la ~/hops/
ls -la /opt/appdata/ (Linux) or ~/hops/config/ (macOS)
ls -la /mnt/media/ (Linux) or ~/hops/media/ (macOS)
```
## 🆘 Troubleshooting Installation
### Common Issues
#### Docker Installation Failed
```bash
# Check Docker status
systemctl status docker
# Restart Docker
sudo systemctl restart docker
# Reinstall Docker (Linux)
curl -fsSL https://get.docker.com | sh
```
#### Permission Denied Errors
```bash
# Fix directory ownership
sudo chown -R $USER:$USER ~/hops/
sudo chown -R $USER:$USER /opt/appdata/ (Linux)
sudo chown -R $USER:$USER ~/hops/config/ (macOS)
```
#### Port Conflicts
```bash
# Check what's using a port
sudo lsof -i :PORT_NUMBER
# Kill conflicting process
sudo kill -9 PID
```
#### WSL2 Issues (Windows)
```bash
# Restart WSL2
wsl --shutdown
wsl -d Ubuntu-22.04
# Check Docker Desktop WSL2 integration
# Go to Docker Desktop Settings → Resources → WSL Integration
```
### Log Locations
- **Installation Logs**:
- Linux: `/var/log/hops/`
- macOS: `/usr/local/var/log/hops/`
- Windows: `~/hops/logs/`
- **Service Logs**: `docker compose logs [service-name]`
- **System Logs**: `journalctl -u docker`
### Getting Help
1. **Built-in Help**: `sudo ./hops` → Option 7
2. **Check Logs**: Review installation logs for errors
3. **Verify Prerequisites**: Ensure all system requirements are met
4. **Docker Status**: Confirm Docker is running and accessible
5. **GitHub Issues**: Report persistent issues with logs
## 🔄 Backup Strategy
### Before Installation
```bash
# Backup existing media and configs
sudo tar -czf homelab-backup-$(date +%Y%m%d).tar.gz \
/path/to/your/media \
/path/to/your/configs
```
### After Installation
```bash
# Backup HOPS configuration
sudo tar -czf hops-config-backup-$(date +%Y%m%d).tar.gz \
~/hops \
/opt/appdata (Linux) or ~/hops/config (macOS)
```
## 📊 Performance Optimization
### During Installation
- **SSD Storage**: Install on SSD for better performance
- **Sufficient RAM**: Ensure adequate memory for selected services
- **Network Speed**: Faster internet improves Docker image downloads
### After Installation
- **Monitor Resources**: Use Portainer to monitor CPU, RAM, and disk usage
- **Optimize Services**: Start with fewer services, add more gradually
- **Storage Configuration**: Use dedicated drives for media storage
---
For additional help, see [ADVANCED.md](ADVANCED.md) for configuration and troubleshooting details.
+25 -47
View File
@@ -2,40 +2,26 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Version](https://img.shields.io/badge/Version-1.0.0-blue.svg)]() [![Version](https://img.shields.io/badge/Version-1.0.0-blue.svg)]()
[![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-orange.svg)]() [![Platform](https://img.shields.io/badge/Platform-Linux-orange.svg)]()
**HOPS** is a comprehensive automation tool for deploying homelab infrastructure using Docker Compose. Deploy and manage popular homelab services including media servers, download clients, monitoring tools, and more through an intuitive menu-driven interface. **HOPS** is a comprehensive automation tool for deploying homelab infrastructure using Docker Compose. Deploy and manage popular homelab services including media servers, download clients, monitoring tools, and more through an intuitive menu-driven interface.
## ⚠️ Important: Beta Software ## Important: Beta Software
**HOPS is beta software**. Always backup your data before installation and test in non-production environments first. **HOPS is beta software**. Always backup your data before installation and test in non-production environments first.
**Platform Status:** **Platform Status:**
- **Linux**: Stable and extensively tested - **Linux**: Stable and actively developed
- **macOS**: ✅ Recently improved with v3.2.0 fixes
- **Windows (WSL2)**: ⚠️ Limited testing
## 🆕 What's New in v3.3.0 ## Key Features
- **🔄 Automatic Updates**: Git-based update system with backup functionality
- **📱 Command Line Interface**: `--update`, `--check-updates`, `--version`, `--help` flags
- **🛡️ Safe Updates**: Automatic backup of local changes before updating
- **📋 Change Tracking**: View recent changes and version comparison
### Previous Updates (v3.2.0) - **Easy Installation**: One-command setup with automatic Docker installation
- **Enhanced macOS Support**: Docker Desktop integration, keychain authentication, user directory fixes - **Security First**: Encrypted secrets, firewall configuration, input validation
- **Caddy Support**: Added reverse proxy option (user provides configuration) - **Management**: Real-time monitoring, centralized logs, service control
- **Bug Fixes**: Password generation, container creation, healthchecks, file permissions - **Auto-Updates**: Built-in update system with backup protection
- **Security**: Encrypted secret management, input validation, privilege separation - **Reliability**: Error handling, rollback capabilities, dependency management
- **Linux Native**: Ubuntu, Debian, and Linux Mint support
## ✨ Key Features ## Supported Services
- **🚀 Easy Installation**: One-command setup with automatic Docker installation
- **🔒 Security First**: Encrypted secrets, firewall configuration, input validation
- **📊 Management**: Real-time monitoring, centralized logs, service control
- **🔄 Auto-Updates**: Built-in update system with backup protection
- **🔧 Reliability**: Error handling, rollback capabilities, dependency management
- **🌐 Cross-Platform**: Linux, macOS, and Windows (WSL2) support
## 📱 Supported Services
**Media Management**: Sonarr, Radarr, Lidarr, Readarr, Bazarr, Prowlarr, Tdarr, Huntarr **Media Management**: Sonarr, Radarr, Lidarr, Readarr, Bazarr, Prowlarr, Tdarr, Huntarr
**Download Clients**: qBittorrent, Transmission, NZBGet, SABnzbd **Download Clients**: qBittorrent, Transmission, NZBGet, SABnzbd
@@ -46,27 +32,25 @@
[View complete service list with support links →](SERVICES.md) [View complete service list with support links →](SERVICES.md)
## 🔧 System Requirements ## System Requirements
**Minimum**: 2GB RAM, 10GB storage, 2 CPU cores, internet connection **Minimum**: 2GB RAM, 10GB storage, 2 CPU cores, internet connection
**Supported Platforms:** **Supported Platforms:**
- **Linux**: Ubuntu 20.04+, Debian 11+, Linux Mint 20+ (x86_64, sudo access) - **Linux**: Ubuntu 20.04+, Debian 11+, Linux Mint 20+ (x86_64, sudo access)
- **macOS**: 11.0+ Big Sur, Intel/Apple Silicon (admin access, auto-installs Docker Desktop)
- **Windows**: 10/11 with WSL2 + Ubuntu 20.04+ (limited testing, requires Docker Desktop)
[View detailed installation guides →](INSTALLATION.md) [View detailed installation guides →](INSTALLATION.md)
## 🚀 Quick Start ## Quick Start
```bash ```bash
# 1. Download HOPS # 1. Download HOPS
git clone https://github.com/skiercm/hops.git git clone https://git.canadabot.net/canadabot/hops.git
cd hops cd hops
chmod +x hops install uninstall setup chmod +x hops install uninstall
# 2. Run installation # 2. Run installation
sudo ./setup sudo ./hops
# 3. Follow interactive setup to select services # 3. Follow interactive setup to select services
@@ -76,10 +60,10 @@ sudo ./setup
**Directory Structure:** **Directory Structure:**
- `~/hops/` - Main deployment (docker-compose.yml, .env, logs) - `~/hops/` - Main deployment (docker-compose.yml, .env, logs)
- `/opt/appdata/` (Linux) or `~/hops/config/` (macOS) - App configs - `/opt/appdata/` - App configs
- `/mnt/media/` (Linux) or `~/hops/media/` (macOS) - Media storage - `/mnt/media/` - Media storage
## 🎛️ Management ## Management
```bash ```bash
# Access management interface # Access management interface
@@ -90,36 +74,30 @@ sudo ./hops --update # Update to latest version
sudo ./hops --check-updates # Check for updates sudo ./hops --check-updates # Check for updates
./hops --version # Show version ./hops --version # Show version
./hops --help # Show help ./hops --help # Show help
# Service operations (no sudo required)
./user-operations status # View service status
./user-operations logs <service> # View logs
./user-operations deploy # Deploy services
./user-operations stop # Stop all services
``` ```
[View advanced configuration and troubleshooting →](ADVANCED.md) [View advanced configuration and troubleshooting →](ADVANCED.md)
## 📞 Support ## Support
**HOPS Issues**: [GitHub Issues](https://github.com/skiercm/hops/issues) | [Discussions](https://github.com/skiercm/hops/discussions) **HOPS Issues**: [Gitea Issues](https://git.canadabot.net/canadabot/hops/issues)
**Service Issues**: Contact individual service developers (links in [SERVICES.md](SERVICES.md)) **Service Issues**: Contact individual service developers (links in [SERVICES.md](SERVICES.md))
**Contact HOPS for**: Installation/deployment issues, Docker Compose problems, cross-platform issues **Contact HOPS for**: Installation/deployment issues, Docker Compose problems, cross-platform issues
**Contact Service Developers for**: Service configuration, features, service-specific bugs **Contact Service Developers for**: Service configuration, features, service-specific bugs
## 📄 Documentation ## Documentation
- [INSTALLATION.md](INSTALLATION.md) - Detailed installation guides for all platforms - [INSTALLATION.md](INSTALLATION.md) - Detailed installation guide
- [SERVICES.md](SERVICES.md) - Complete service list with support links - [SERVICES.md](SERVICES.md) - Complete service list with support links
- [ADVANCED.md](ADVANCED.md) - Configuration, troubleshooting, security - [ADVANCED.md](ADVANCED.md) - Configuration, troubleshooting, security
- [CHANGELOG.md](CHANGELOG.md) - Version history and changes - [CHANGELOG.md](CHANGELOG.md) - Version history and changes
## 📄 License ## License
MIT License - see [LICENSE](LICENSE) file for details. MIT License - see [LICENSE](LICENSE) file for details.
--- ---
**Made with ❤️ for the homelab community** **Made for the homelab community**
+12 -22
View File
@@ -2,7 +2,7 @@
HOPS supports a comprehensive collection of homelab services across multiple categories. All services use LinuxServer.io containers for consistency and reliability. HOPS supports a comprehensive collection of homelab services across multiple categories. All services use LinuxServer.io containers for consistency and reliability.
## 📺 Media Management (*arr Stack) ## Media Management (*arr Stack)
### Sonarr - TV Series Management ### Sonarr - TV Series Management
**Purpose**: Automatic TV show downloading and management **Purpose**: Automatic TV show downloading and management
@@ -27,7 +27,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Default Port**: 8787 **Default Port**: 8787
**Support**: [github.com/Readarr/Readarr](https://github.com/Readarr/Readarr) **Support**: [github.com/Readarr/Readarr](https://github.com/Readarr/Readarr)
**Documentation**: [wiki.servarr.com/readarr](https://wiki.servarr.com/readarr) **Documentation**: [wiki.servarr.com/readarr](https://wiki.servarr.com/readarr)
**Status**: ⚠️ Project retired, limited support **Status**: Project retired, limited support
### Bazarr - Subtitle Management ### Bazarr - Subtitle Management
**Purpose**: Automatic subtitle downloading for movies and TV **Purpose**: Automatic subtitle downloading for movies and TV
@@ -53,7 +53,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Support**: [github.com/plexguide/Huntarr.io](https://github.com/plexguide/Huntarr.io) **Support**: [github.com/plexguide/Huntarr.io](https://github.com/plexguide/Huntarr.io)
**Documentation**: Community-driven **Documentation**: Community-driven
## ⬇️ Download Clients ## Download Clients
### qBittorrent - BitTorrent Client ### qBittorrent - BitTorrent Client
**Purpose**: Feature-rich BitTorrent client with web interface **Purpose**: Feature-rich BitTorrent client with web interface
@@ -79,7 +79,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Support**: [github.com/sabnzbd/sabnzbd](https://github.com/sabnzbd/sabnzbd) **Support**: [github.com/sabnzbd/sabnzbd](https://github.com/sabnzbd/sabnzbd)
**Documentation**: [sabnzbd.org/wiki](https://sabnzbd.org/wiki) **Documentation**: [sabnzbd.org/wiki](https://sabnzbd.org/wiki)
## 🎞️ Media Servers ## Media Servers
### Jellyfin - Open Source Media Server ### Jellyfin - Open Source Media Server
**Purpose**: Free, open-source media server and entertainment hub **Purpose**: Free, open-source media server and entertainment hub
@@ -109,7 +109,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Documentation**: GitHub repository **Documentation**: GitHub repository
**Requirements**: Requires Jellyfin server **Requirements**: Requires Jellyfin server
## 🎛️ Request Management ## Request Management
### Overseerr - Plex Request Management ### Overseerr - Plex Request Management
**Purpose**: Media discovery and request management for Plex **Purpose**: Media discovery and request management for Plex
@@ -132,7 +132,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Documentation**: [docs.ombi.app](https://docs.ombi.app) **Documentation**: [docs.ombi.app](https://docs.ombi.app)
**Integration**: Plex, Emby, Jellyfin **Integration**: Plex, Emby, Jellyfin
## 🔒 Reverse Proxy & Security ## Reverse Proxy & Security
### Traefik - Modern Reverse Proxy ### Traefik - Modern Reverse Proxy
**Purpose**: Automatic reverse proxy with SSL certificate management **Purpose**: Automatic reverse proxy with SSL certificate management
@@ -153,7 +153,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Default Ports**: 80, 443, 2019 (admin) **Default Ports**: 80, 443, 2019 (admin)
**Support**: [github.com/caddyserver/caddy](https://github.com/caddyserver/caddy) **Support**: [github.com/caddyserver/caddy](https://github.com/caddyserver/caddy)
**Documentation**: [caddyserver.com/docs](https://caddyserver.com/docs) **Documentation**: [caddyserver.com/docs](https://caddyserver.com/docs)
**Note**: ⚠️ **Configuration not included** - Users must provide their own Caddyfile **Note**: **Configuration not included** - Users must provide their own Caddyfile
### Authelia - Authentication & Authorization ### Authelia - Authentication & Authorization
**Purpose**: Multi-factor authentication and single sign-on **Purpose**: Multi-factor authentication and single sign-on
@@ -162,7 +162,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Documentation**: [authelia.com/integration](https://www.authelia.com/integration) **Documentation**: [authelia.com/integration](https://www.authelia.com/integration)
**Features**: 2FA, LDAP, OAuth2, OIDC **Features**: 2FA, LDAP, OAuth2, OIDC
## 📈 Monitoring & Management ## Monitoring & Management
### Portainer - Container Management ### Portainer - Container Management
**Purpose**: Web-based Docker container management interface **Purpose**: Web-based Docker container management interface
@@ -185,7 +185,7 @@ HOPS supports a comprehensive collection of homelab services across multiple cat
**Documentation**: [containrrr.dev/watchtower](https://containrrr.dev/watchtower) **Documentation**: [containrrr.dev/watchtower](https://containrrr.dev/watchtower)
**Features**: Scheduled updates, notifications, selective updating **Features**: Scheduled updates, notifications, selective updating
## 🔧 Service Dependencies ## Service Dependencies
### Common Dependencies ### Common Dependencies
Most services depend on: Most services depend on:
@@ -213,7 +213,7 @@ Most services depend on:
2. **Uptime Kuma** → Service monitoring 2. **Uptime Kuma** → Service monitoring
3. **Watchtower** → Automatic updates 3. **Watchtower** → Automatic updates
## ⚠️ Important Service Notes ## Important Service Notes
### Caddy Configuration ### Caddy Configuration
HOPS provides the Caddy container but **does not include Caddyfile configuration**. Users must: HOPS provides the Caddy container but **does not include Caddyfile configuration**. Users must:
@@ -234,8 +234,6 @@ radarr.example.com {
### GPU Support ### GPU Support
- **Linux**: Intel GPU support via `/dev/dri` passthrough - **Linux**: Intel GPU support via `/dev/dri` passthrough
- **macOS**: No GPU acceleration available
- **Windows**: Limited GPU support in WSL2
### Service Health Checks ### Service Health Checks
All web-based services include health checks for: All web-based services include health checks for:
@@ -243,7 +241,7 @@ All web-based services include health checks for:
- Automatic restart on failure - Automatic restart on failure
- Status monitoring integration - Status monitoring integration
## 🆘 Service-Specific Support ## Service-Specific Support
### When to Contact Service Developers ### When to Contact Service Developers
@@ -257,7 +255,6 @@ All web-based services include health checks for:
**Contact HOPS for:** **Contact HOPS for:**
- Docker Compose generation issues - Docker Compose generation issues
- Service deployment problems - Service deployment problems
- Cross-platform compatibility
- Installation and automation issues - Installation and automation issues
### Getting Service Help ### Getting Service Help
@@ -272,11 +269,7 @@ All web-based services include health checks for:
#### Permission Problems #### Permission Problems
```bash ```bash
# Fix ownership for Linux
sudo chown -R $USER:$USER /opt/appdata/[service-name] sudo chown -R $USER:$USER /opt/appdata/[service-name]
# Fix ownership for macOS
sudo chown -R $USER:$USER ~/hops/config/[service-name]
``` ```
#### Service Won't Start #### Service Won't Start
@@ -289,10 +282,7 @@ docker compose restart [service-name]
``` ```
#### Configuration Issues #### Configuration Issues
Most services store configuration in: Service configuration is stored in `/opt/appdata/[service-name]/`.
- **Linux**: `/opt/appdata/[service-name]/`
- **macOS**: `~/hops/config/[service-name]/`
- **Windows**: `/opt/appdata/[service-name]/` (in WSL2)
--- ---
+53 -40
View File
@@ -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
@@ -199,6 +191,29 @@ Generated by codebase audit (2026-06-10). Ranked by severity.
### M4 -- RESOLVED (`lib/privileges.sh` deleted) ### M4 -- RESOLVED (`lib/privileges.sh` deleted)
### M6 -- Deprecate Watchtower [LOW]
- Watchtower auto-updates running containers without user review, which can
silently break working setups. At odds with HOPS managing its own deploys.
- Remove from the selectable service list and SERVICES.md.
- Users who want auto-updates can add it manually.
### M8 -- Deprecate Readarr [LOW]
- Readarr project has been retired upstream. Remove from the selectable service
list, services definition, and SERVICES.md.
- Add a note in SERVICES.md under the *arr section if users ask why it's gone.
### M7 -- Investigate adding Profilarr [LOW]
- Profilarr manages and syncs quality profiles across Radarr/Sonarr instances.
- Evaluate: image source, default port, dependencies, config requirements.
- Determine if it fits the existing *arr service template or needs a custom definition.
### M5 -- Review Huntarr inclusion -- switch to elfhosted rebuild [MED]
- The current `services` definition uses the original plexguide/Huntarr.io image.
The project has been rebuilt by elfhosted under a new image/repo.
- Review the new elfhosted image name, default port, and any config changes.
- Update the service definition in `services` and the entry in SERVICES.md.
- Verify the new image is actively maintained before shipping 1.0.0.
--- ---
## PLATFORM SUPPORT ## PLATFORM SUPPORT
@@ -225,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
@@ -251,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
+70 -289
View File
@@ -1,314 +1,95 @@
# HOPS Troubleshooting Guide # Troubleshooting
This document provides solutions to common issues encountered when installing and running HOPS. ## Docker Not Starting
## Docker Repository Issues
### Linux Mint Docker Repository Error
**Symptoms:**
```
E: The repository 'https://download.docker.com/linux/ubuntu xia Release' does not have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
```
**Root Cause:**
Linux Mint uses its own codenames (e.g., "xia", "vera", "vanessa") that don't exist in Docker's Ubuntu repositories. Docker only supports Ubuntu codenames like "noble", "jammy", "focal".
**Solution:**
This issue has been resolved in HOPS v3.3.0+. The system now automatically detects the correct Ubuntu codename for Linux Mint installations using `UBUNTU_CODENAME` from `/etc/os-release`.
**Manual Fix (if needed):**
1. **Clean existing Docker repositories:**
```bash
sudo rm -f /etc/apt/sources.list.d/docker*
sudo rm -f /etc/apt/sources.list.d/*docker*
sudo rm -f /usr/share/keyrings/docker*
sudo rm -f /etc/apt/keyrings/docker*
sudo apt clean
```
2. **Verify Ubuntu codename detection:**
```bash
grep '^UBUNTU_CODENAME=' /etc/os-release
# Should return: UBUNTU_CODENAME=noble (for Linux Mint 22.x)
```
3. **Update HOPS to latest version:**
```bash
cd ~/hops
git pull
sudo ./hops --update
```
### Docker Installation Fails
**Symptoms:**
- `Package 'docker-ce' has no installation candidate`
- `Cannot connect to the Docker daemon at unix:///var/run/docker.sock`
**Solutions:**
1. **Check Docker service status:**
```bash
sudo systemctl status docker
sudo systemctl status docker.socket
```
2. **Fix missing Docker group:**
```bash
sudo groupadd docker
sudo usermod -aG docker $USER
sudo chown root:docker /var/run/docker.sock
```
3. **Start Docker services:**
```bash
sudo systemctl start docker.socket
sudo systemctl start docker
sudo systemctl enable docker
```
4. **Verify Docker installation:**
```bash
sudo docker --version
sudo docker compose version
sudo docker info
```
## Directory and Permission Issues
### Homelab Directory Not Found
**Symptoms:**
```
ERROR: Homelab directory not found: /root/hops
```
**Root Cause:**
When running with `sudo`, the script may look for files in `/root/hops` instead of the user's home directory.
**Solution:**
This issue has been resolved in HOPS v3.3.0+. The system now properly detects the original user's home directory when running with sudo.
**Manual Workaround:**
```bash ```bash
# Ensure you're in the correct directory sudo systemctl status docker
cd ~/hops sudo systemctl restart docker
sudo ./hops journalctl -u docker --since "1 hour ago"
``` ```
### Permission Denied Errors If Docker is missing or broken:
**Symptoms:**
- Permission denied accessing Docker socket
- Cannot create directories in `/opt/appdata`
**Solutions:**
1. **Fix Docker permissions:**
```bash
sudo usermod -aG docker $USER
# Log out and log back in, or run:
newgrp docker
```
2. **Fix directory permissions:**
```bash
sudo chown -R $USER:$USER ~/hops
sudo chmod -R 755 ~/hops
```
## Service Access Issues
### Cannot Access Service Web UI
**Symptoms:**
- Service shows as running but web UI is not accessible
- Connection refused errors
**Solutions:**
1. **Check container status:**
```bash
cd ~/hops
sudo docker compose ps
sudo docker compose logs [service-name]
```
2. **Verify port availability:**
```bash
sudo netstat -tlnp | grep :8989 # For Sonarr
sudo ss -tlnp | grep :8989 # Alternative command
```
3. **Check firewall settings:**
```bash
sudo ufw status
# If needed, allow ports:
sudo ufw allow 8989 # Example for Sonarr
```
4. **Restart services:**
```bash
cd ~/hops
sudo docker compose restart [service-name]
```
## Network Issues
### Docker Network Creation Fails
**Symptoms:**
- `Error response from daemon: network with name [network] already exists`
- Network connectivity issues between containers
**Solutions:**
1. **List existing networks:**
```bash
sudo docker network ls
```
2. **Remove conflicting networks:**
```bash
sudo docker network rm traefik homelab
```
3. **Recreate networks:**
```bash
cd ~/hops
sudo docker compose up -d
```
## System Requirements
### Insufficient Resources
**Symptoms:**
- Services randomly stopping
- Out of memory errors
- Slow performance
**Solutions:**
1. **Check system resources:**
```bash
free -h # Memory usage
df -h # Disk usage
top # CPU and memory usage
```
2. **Minimum requirements:**
- RAM: 2GB minimum, 4GB+ recommended
- Disk: 10GB minimum, 50GB+ recommended for media
- CPU: 2 cores minimum
3. **Optimize services:**
- Disable unnecessary services
- Adjust container resource limits
- Use external storage for media files
## Getting Help
### Log Files
Check HOPS log files for detailed error information:
**Linux:**
```bash ```bash
sudo tail -f /var/log/hops/hops-main-*.log curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
``` ```
**macOS:** ## Linux Mint: Docker Repository Error
**Symptom:** `The repository 'https://download.docker.com/linux/ubuntu xia Release' does not have a Release file`
Linux Mint uses its own codenames that Docker's repo doesn't recognise. HOPS
detects the underlying Ubuntu codename via `UBUNTU_CODENAME` in `/etc/os-release`.
Manual fix:
```bash ```bash
sudo tail -f /usr/local/var/log/hops/hops-main-*.log sudo rm -f /etc/apt/sources.list.d/docker* /etc/apt/keyrings/docker*
sudo apt clean
grep '^UBUNTU_CODENAME=' /etc/os-release # confirm value is present
sudo ./hops # re-run installation
``` ```
### Docker Compose Logs ## Permission Denied
```bash
# Docker socket access
sudo usermod -aG docker $USER
newgrp docker
# Directory ownership
sudo chown -R $USER:$USER ~/hops
sudo chown -R $USER:$USER /opt/appdata
```
## Service Won't Start
```bash ```bash
cd ~/hops cd ~/hops
sudo docker compose logs -f [service-name] docker compose logs [service-name]
docker compose ps
docker compose restart [service-name]
``` ```
### System Information ## Port Already in Use
When reporting issues, include: ```bash
sudo lsof -i :[port]
sudo ss -tlnp | grep :[port]
```
## Cannot Access Service Web UI
1. Confirm container is running: `docker compose ps`
2. Check for port conflicts (above)
3. Check firewall: `sudo ufw status`
4. Allow port if needed: `sudo ufw allow [port]/tcp`
## Docker Network Errors
```bash
# Remove conflicting networks and recreate
sudo docker network rm traefik homelab
cd ~/hops && docker compose up -d
```
## Log Locations
- **HOPS logs**: `/var/log/hops/hops-main-*.log`
- **Service logs**: `docker compose logs [service-name]`
- **System logs**: `journalctl -u docker`
## Reporting Issues
Collect this info before reporting:
```bash ```bash
# System information
lsb_release -a lsb_release -a
uname -a uname -a
docker --version docker --version
docker compose version docker compose version
# HOPS version
./hops --version ./hops --version
cd ~/hops && docker compose ps
# Container status
cd ~/hops && sudo docker compose ps
``` ```
### Reporting Issues Report at: [git.canadabot.net/canadabot/hops/issues](https://git.canadabot.net/canadabot/hops/issues)
1. Check this troubleshooting guide first
2. Search existing [GitHub Issues](https://github.com/skiercm/hops/issues)
3. Create a new issue with:
- Complete error messages
- System information (above commands)
- Steps to reproduce
- Log file excerpts
## Recovery Commands
### Complete Reset
If HOPS installation is completely broken:
```bash
# Stop all containers
cd ~/hops && sudo docker compose down
# Remove containers and images (CAUTION: This removes all data)
sudo docker system prune -a --volumes
# Clean up repositories
sudo rm -f /etc/apt/sources.list.d/docker*
sudo apt clean
# Reinstall HOPS
cd ~/hops
git pull
sudo ./hops
```
### Partial Reset
To reset only HOPS configuration:
```bash
# Stop services
cd ~/hops && sudo docker compose down
# Remove generated files
rm -f ~/hops/docker-compose.yml ~/hops/.env
# Restart HOPS
sudo ./hops
```
---
## Version History
- **v3.3.0+**: Fixed Linux Mint Docker repository detection
- **v3.2.0+**: Added macOS support and improved error handling
- **v3.1.0+**: Initial multi-platform support
For the latest updates and fixes, always ensure you're running the latest version:
```bash
cd ~/hops
sudo ./hops --update
```
+15 -49
View File
@@ -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 -95
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+2 -8
View File
@@ -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 -22
View File
@@ -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