Compare commits
24 Commits
b81779784b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cba0998a7 | |||
| a7c38cd58d | |||
| 889a666c81 | |||
| 32f2e1b666 | |||
| 248c3c3dc7 | |||
| b106cfc177 | |||
| 1f823fa9d1 | |||
| cce25bfa4d | |||
| f618ce2c69 | |||
| a7213441da | |||
| 148a0827e3 | |||
| a99a1e65ce | |||
| 814c4e71a3 | |||
| a0e4e63d01 | |||
| 2ba24f2a94 | |||
| a28a6e5007 | |||
| 736ed1b098 | |||
| d2e9a69313 | |||
| ce0f7f2ae4 | |||
| 4fd78ec40f | |||
| af57a772d1 | |||
| 2422d2cd50 | |||
| 7d8aeee45b | |||
| b114bd54c8 |
@@ -1,8 +1,5 @@
|
||||
# HOPS .gitignore
|
||||
|
||||
# Claude Code development documentation
|
||||
CLAUDE.md
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Advanced Configuration
|
||||
|
||||
This guide covers advanced HOPS configuration and system administration.
|
||||
|
||||
> **Note**: This document is being rebuilt alongside the 1.0.0 codebase.
|
||||
> Content will be expanded as features are stabilized.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
HOPS writes a `.env` file to `~/hops/` during installation. Key variables:
|
||||
|
||||
```bash
|
||||
PUID=1000 # User ID (run: id -u)
|
||||
PGID=1000 # Group ID (run: id -g)
|
||||
TZ=America/New_York # Timezone
|
||||
|
||||
DATA_ROOT=/mnt/media # Media storage
|
||||
CONFIG_ROOT=/opt/appdata # App configurations
|
||||
|
||||
DEFAULT_ADMIN_PASSWORD=... # Auto-generated
|
||||
DEFAULT_DB_PASSWORD=... # Auto-generated
|
||||
```
|
||||
|
||||
## Docker Compose Commands
|
||||
|
||||
```bash
|
||||
cd ~/hops
|
||||
|
||||
docker compose ps # List services
|
||||
docker compose up -d # Start all services
|
||||
docker compose down # Stop all services
|
||||
docker compose restart [service] # Restart a service
|
||||
docker compose logs -f [service] # Follow logs
|
||||
docker compose logs --tail=100 [service] # Last 100 lines
|
||||
docker compose pull # Pull new images
|
||||
docker compose up -d --force-recreate # Recreate containers
|
||||
docker stats # Real-time resource usage
|
||||
docker system df # Disk usage
|
||||
docker system prune # Clean unused data
|
||||
```
|
||||
|
||||
## Firewall
|
||||
|
||||
HOPS configures UFW automatically on Linux. Manual management:
|
||||
|
||||
```bash
|
||||
sudo ufw status
|
||||
sudo ufw allow 8096/tcp comment "Jellyfin"
|
||||
sudo ufw delete allow 8096/tcp
|
||||
```
|
||||
|
||||
## File Permissions
|
||||
|
||||
```bash
|
||||
chmod 600 ~/hops/.env
|
||||
chmod 644 ~/hops/docker-compose.yml
|
||||
sudo chown -R $USER:$USER /opt/appdata
|
||||
sudo chown -R $USER:$USER /mnt/media
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For installation, see [INSTALLATION.md](INSTALLATION.md).
|
||||
For troubleshooting, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
||||
@@ -0,0 +1,45 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to HOPS will be documented here.
|
||||
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [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
|
||||
- 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
|
||||
|
||||
Full rewrite establishing the Path A install pipeline.
|
||||
@@ -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
|
||||
@@ -0,0 +1,62 @@
|
||||
# Installation Guide
|
||||
|
||||
> **Note**: This document is being rebuilt alongside the 1.0.0 codebase.
|
||||
> Content will be expanded as features are stabilized.
|
||||
|
||||
## System Requirements
|
||||
|
||||
- **OS**: Ubuntu 20.04+, Debian 11+, or Linux Mint 20+ (x86_64)
|
||||
- **RAM**: 2GB minimum, 4GB+ recommended
|
||||
- **Storage**: 10GB free (more for media)
|
||||
- **CPU**: 2 cores recommended
|
||||
- **Access**: sudo privileges
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://git.canadabot.net/canadabot/hops.git
|
||||
cd hops
|
||||
chmod +x hops install uninstall
|
||||
sudo ./hops
|
||||
```
|
||||
|
||||
HOPS will install Docker automatically if not present, then walk you through
|
||||
selecting and deploying services interactively.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
~/hops/ # Deployment directory
|
||||
├── docker-compose.yml # Generated service definitions
|
||||
├── .env # Environment variables and secrets
|
||||
└── logs/ # HOPS logs
|
||||
|
||||
/opt/appdata/ # Application config data
|
||||
├── jellyfin/
|
||||
├── sonarr/
|
||||
└── [other services]/
|
||||
|
||||
/mnt/media/ # Media storage
|
||||
├── movies/
|
||||
├── tv/
|
||||
├── music/
|
||||
└── downloads/
|
||||
```
|
||||
|
||||
## Post-Installation
|
||||
|
||||
```bash
|
||||
# Check service status
|
||||
cd ~/hops && docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f [service-name]
|
||||
|
||||
# Access management interface
|
||||
sudo ./hops
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Issues: [git.canadabot.net/canadabot/hops/issues](https://git.canadabot.net/canadabot/hops/issues)
|
||||
- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
|
||||
@@ -1,623 +1,103 @@
|
||||
# HOPS - Homelab Orchestration Provisioning Script
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
**HOPS** is a comprehensive, automated deployment solution for popular homelab applications. It simplifies the process of setting up and managing Docker-based services including media servers, download clients, monitoring tools, and more.
|
||||
**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 Notices
|
||||
## Important: Beta Software
|
||||
**HOPS is beta software**. Always backup your data before installation and test in non-production environments first.
|
||||
|
||||
### 🚧 Development Status
|
||||
**HOPS is under active development** and should be considered beta software. While core functionality is stable and tested, new features and improvements are being added regularly.
|
||||
**Platform Status:**
|
||||
- **Linux**: Stable and actively developed
|
||||
|
||||
### 🖥️ Platform Testing Status
|
||||
- **Linux (Ubuntu/Debian/Mint)**: ✅ Extensively tested and stable
|
||||
- **macOS**: ✅ Recently improved with comprehensive compatibility fixes
|
||||
- **Windows (WSL2)**: ⚠️ **Limited testing** - Basic functionality works but may have undiscovered issues
|
||||
## Key Features
|
||||
|
||||
### 💾 Data Safety & Backups
|
||||
**IMPORTANT**: Always maintain regular backups of your data before using HOPS.
|
||||
- **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
|
||||
- **Linux Native**: Ubuntu, Debian, and Linux Mint support
|
||||
|
||||
- **Backup your media and configuration files** before installation
|
||||
- **Test in a non-production environment** first if possible
|
||||
- **HOPS is not responsible for any data loss** that may occur during installation or operation
|
||||
- **Docker operations can be destructive** - ensure you understand what services you're installing
|
||||
## Supported Services
|
||||
|
||||
**Media Management**: Sonarr, Radarr, Lidarr, Readarr, Bazarr, Prowlarr, Tdarr, Huntarr
|
||||
**Download Clients**: qBittorrent, Transmission, NZBGet, SABnzbd
|
||||
**Media Servers**: Jellyfin, Plex, Emby, Jellystat
|
||||
**Request Management**: Overseerr, Jellyseerr, Ombi
|
||||
**Reverse Proxy**: Traefik, Nginx Proxy Manager, Caddy, Authelia
|
||||
**Monitoring**: Portainer, Uptime Kuma, Watchtower
|
||||
|
||||
[View complete service list with support links →](SERVICES.md)
|
||||
|
||||
## System Requirements
|
||||
|
||||
**Minimum**: 2GB RAM, 10GB storage, 2 CPU cores, internet connection
|
||||
|
||||
**Supported Platforms:**
|
||||
- **Linux**: Ubuntu 20.04+, Debian 11+, Linux Mint 20+ (x86_64, sudo access)
|
||||
|
||||
[View detailed installation guides →](INSTALLATION.md)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 📋 Recommended Backup Strategy
|
||||
```bash
|
||||
# Before running HOPS, backup important directories
|
||||
sudo tar -czf homelab-backup-$(date +%Y%m%d).tar.gz /path/to/your/media /path/to/your/configs
|
||||
|
||||
# After installation, backup HOPS configuration
|
||||
sudo tar -czf hops-config-backup-$(date +%Y%m%d).tar.gz ~/hops ~/.config/hops
|
||||
```
|
||||
|
||||
## 🆕 What's New in v3.2.0
|
||||
|
||||
### Major macOS Compatibility Improvements
|
||||
- **🍎 Enhanced macOS Support**: Comprehensive fixes for macOS installation and operation
|
||||
- **🔐 Keychain Integration**: Proper Docker authentication with macOS keychain
|
||||
- **👤 User Directory Fixes**: All directories now use actual user home instead of root
|
||||
- **🚀 Docker Desktop Integration**: Improved Docker Desktop startup and management
|
||||
- **⚡ Better Error Handling**: Enhanced error messages and troubleshooting for macOS
|
||||
|
||||
### New Features
|
||||
- **🌐 Caddy Support**: Added Caddy reverse proxy as a service option (configuration not included)
|
||||
|
||||
### Bug Fixes
|
||||
- **🔧 Fixed password generation**: Resolved `shuf` command and encoding issues on macOS
|
||||
- **🐳 Fixed container creation**: Resolved Docker Compose working directory issues
|
||||
- **🏥 Fixed healthchecks**: Improved Jellyseerr and other service health monitoring
|
||||
- **📁 Fixed file permissions**: Proper ownership of config and media directories
|
||||
|
||||
### Previous in v3.1.0-beta
|
||||
- **🔐 Encrypted Secret Management**: All passwords and sensitive data now encrypted with AES-256
|
||||
- **🛡️ Input Validation**: Comprehensive validation preventing injection attacks
|
||||
- **⚡ Privilege Separation**: Root operations separated from user operations
|
||||
- **📌 Pinned Versions**: All container images use specific versions, not `latest`
|
||||
|
||||
### New Architecture
|
||||
- **📚 Modular Libraries**: Shared code organized in `lib/` directory
|
||||
- **🔧 Enhanced Error Handling**: Better error messages and recovery mechanisms
|
||||
- **🎯 Improved Service Definitions**: Standardized service generation with validation
|
||||
- **📖 Documentation**: Complete `CLAUDE.md` for development guidance
|
||||
- **🍎 Cross-Platform Support**: Native support for Linux, macOS, and Windows (WSL2) with automatic dependency installation
|
||||
|
||||
### Installation Methods
|
||||
- **🚀 New Secure Installer**: `sudo ./setup` - Recommended method
|
||||
- **⚙️ Manual Installation**: Separate privileged and user operations
|
||||
- **🔄 Legacy Support**: Original `hops.sh` still fully supported
|
||||
|
||||
## 🎯 What is HOPS?
|
||||
|
||||
HOPS (Homelab Orchestration Provisioning Script) automates the deployment of a complete homelab infrastructure using Docker Compose. It provides an intuitive menu-driven interface for selecting, configuring, and managing services with enterprise-grade features like:
|
||||
|
||||
- **Automated dependency resolution**
|
||||
- **Security hardening and firewall configuration**
|
||||
- **Service health monitoring**
|
||||
- **Rollback capabilities on failure**
|
||||
- **Comprehensive logging**
|
||||
- **User-friendly management interface**
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
### 🚀 **Easy Installation**
|
||||
- One-command installation process
|
||||
- Automatic Docker installation and configuration
|
||||
- Interactive service selection
|
||||
- Intelligent dependency resolution
|
||||
- **NEW**: Privilege separation for enhanced security
|
||||
|
||||
### 🔒 **Security First**
|
||||
- Automatic firewall configuration
|
||||
- Secure password generation with encryption
|
||||
- File permission hardening
|
||||
- Network isolation
|
||||
- **NEW**: AES-256 encrypted secret storage
|
||||
- **NEW**: Comprehensive input validation
|
||||
- **NEW**: Pinned container versions
|
||||
|
||||
### 📊 **Management & Monitoring**
|
||||
- Real-time service status monitoring
|
||||
- Centralized log viewing
|
||||
- Easy service management (start/stop/restart)
|
||||
- Health checks and service verification
|
||||
- **NEW**: Modular architecture with shared libraries
|
||||
|
||||
### 🔄 **Reliability**
|
||||
- Error handling with automatic rollback
|
||||
- Service dependency management
|
||||
- Port conflict detection
|
||||
- System requirements validation
|
||||
- **NEW**: Enhanced error handling with detailed context
|
||||
|
||||
## 📱 Supported Services
|
||||
|
||||
### 📺 Media Management (*arr Stack)
|
||||
- **Sonarr** - TV show management
|
||||
- **Radarr** - Movie management
|
||||
- **Lidarr** - Music management
|
||||
- **Readarr** - eBook/audiobook management
|
||||
- **Bazarr** - Subtitle management
|
||||
- **Prowlarr** - Indexer management
|
||||
- **Tdarr** - Media transcoding
|
||||
- **Huntarr** - Missing media discovery and automation
|
||||
|
||||
### ⬇️ Download Clients
|
||||
- **qBittorrent** - Feature-rich BitTorrent client
|
||||
- **Transmission** - Lightweight BitTorrent client
|
||||
- **NZBGet** - Efficient Usenet downloader
|
||||
- **SABnzbd** - Popular Usenet client
|
||||
|
||||
### 🎞️ Media Servers
|
||||
- **Jellyfin** - Open-source media server
|
||||
- **Plex** - Popular media server platform
|
||||
- **Emby** - Feature-rich media server
|
||||
- **Jellystat** - Jellyfin statistics and monitoring
|
||||
|
||||
### 🎛️ Request Management
|
||||
- **Overseerr** - Media request management for Plex
|
||||
- **Jellyseerr** - Media request management for Jellyfin
|
||||
- **Ombi** - Media request platform
|
||||
|
||||
### 🔒 Reverse Proxy & Security
|
||||
- **Traefik** - Modern reverse proxy with automatic SSL
|
||||
- **Nginx Proxy Manager** - Easy-to-use reverse proxy
|
||||
- **Caddy** - Automatic HTTPS web server (*configuration not included*)
|
||||
- **Authelia** - Authentication and authorization server
|
||||
|
||||
### 📈 Monitoring & Management
|
||||
- **Portainer** - Docker container management
|
||||
- **Uptime Kuma** - Service monitoring
|
||||
- **Watchtower** - Automatic container updates
|
||||
|
||||
## 🔧 System Requirements
|
||||
|
||||
### Minimum Requirements
|
||||
- **OS**:
|
||||
- **Linux**: Ubuntu 20.04+, Debian 11+, or Linux Mint 20+
|
||||
- **macOS**: 11.0+ (Big Sur) with Intel or Apple Silicon
|
||||
- **Windows**: 10/11 with WSL2 (Ubuntu 20.04+ distribution) ⚠️ *Limited testing*
|
||||
- **RAM**: 2GB (4GB+ recommended)
|
||||
- **Storage**: 10GB free space (more for media)
|
||||
- **CPU**: 2 cores recommended
|
||||
- **Network**: Internet connection required
|
||||
|
||||
### Prerequisites
|
||||
|
||||
**Linux:**
|
||||
- Root/sudo access
|
||||
- x86_64 architecture
|
||||
- Internet connection
|
||||
|
||||
**macOS:**
|
||||
- Admin access (sudo privileges)
|
||||
- Intel (x86_64) or Apple Silicon (ARM64)
|
||||
- Internet connection
|
||||
- Homebrew will be installed automatically if not present
|
||||
- Docker Desktop will be installed automatically if not present
|
||||
|
||||
**Windows:**
|
||||
- Windows 10 (build 19041+) or Windows 11
|
||||
- WSL2 enabled with Ubuntu 20.04+ distribution
|
||||
- Docker Desktop with WSL2 backend
|
||||
- Admin access to install prerequisites
|
||||
- x86_64 processor with virtualization support
|
||||
- Hyper-V and virtualization enabled in BIOS
|
||||
|
||||
## 🪟 Windows Installation (WSL2)
|
||||
|
||||
HOPS runs on Windows through WSL2 (Windows Subsystem for Linux) with excellent compatibility and performance. This approach leverages the full Linux environment within Windows.
|
||||
|
||||
### Prerequisites Setup
|
||||
|
||||
**1. Enable WSL2:**
|
||||
```powershell
|
||||
# Run in PowerShell as Administrator
|
||||
wsl --install
|
||||
# Restart computer when prompted
|
||||
```
|
||||
|
||||
**2. Install Ubuntu Distribution:**
|
||||
```powershell
|
||||
# Install Ubuntu 22.04 LTS (recommended)
|
||||
wsl --install Ubuntu-22.04
|
||||
# Set up username and password when prompted
|
||||
```
|
||||
|
||||
**3. Install Docker Desktop:**
|
||||
- Download from [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
|
||||
- Enable WSL2 integration during installation
|
||||
- Ensure "Use WSL 2 based engine" is checked in Docker settings
|
||||
|
||||
### HOPS Installation on WSL2
|
||||
⚠️ **Note**: WSL2 support has limited testing. Please proceed with caution and ensure you have backups.
|
||||
|
||||
**1. Open WSL2 Terminal:**
|
||||
```bash
|
||||
# Launch Ubuntu from Start Menu or run:
|
||||
wsl -d Ubuntu-22.04
|
||||
```
|
||||
|
||||
**2. Install HOPS (same as Linux):**
|
||||
```bash
|
||||
# Clone inside WSL2 filesystem (important for performance)
|
||||
cd ~
|
||||
git clone https://github.com/skiercm/hops.git
|
||||
# 1. Download HOPS
|
||||
git clone https://git.canadabot.net/canadabot/hops.git
|
||||
cd hops
|
||||
chmod +x hops install uninstall setup
|
||||
chmod +x hops install uninstall
|
||||
|
||||
# Run installation
|
||||
sudo ./setup
|
||||
# 2. Run installation
|
||||
sudo ./hops
|
||||
|
||||
# 3. Follow interactive setup to select services
|
||||
|
||||
# 4. Access your services
|
||||
# URLs will be provided after installation
|
||||
```
|
||||
|
||||
### ⚠️ Important Notes for Windows Users
|
||||
**Directory Structure:**
|
||||
- `~/hops/` - Main deployment (docker-compose.yml, .env, logs)
|
||||
- `/opt/appdata/` - App configs
|
||||
- `/mnt/media/` - Media storage
|
||||
|
||||
**File Location:** Always run HOPS from the WSL2 filesystem (`~/hops/`) for optimal performance. Avoid running from `/mnt/c/` (Windows drives).
|
||||
|
||||
**Media Access:** Your Windows media folders can be accessed at:
|
||||
- `C:\Users\YourName\` → `/mnt/c/Users/YourName/`
|
||||
- External drives → `/mnt/d/`, `/mnt/e/`, etc.
|
||||
|
||||
**Docker Integration:** Services will be accessible from both Windows and WSL2:
|
||||
- Web interfaces work from Windows browsers
|
||||
- File shares accessible from Windows Explorer via `\\wsl.localhost\Ubuntu-22.04\home\username\hops\`
|
||||
|
||||
**Performance:** WSL2 provides 95% of native Linux performance when files are stored in the WSL2 filesystem.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Download HOPS
|
||||
```bash
|
||||
git clone https://github.com/skiercm/hops.git
|
||||
cd hops
|
||||
chmod +x hops install uninstall setup
|
||||
```
|
||||
|
||||
### 2. Run Installation (New Improved Method)
|
||||
```bash
|
||||
# Option 1: Use the new secure installation wrapper
|
||||
sudo ./setup
|
||||
|
||||
# Option 2: Manual two-phase installation
|
||||
sudo ./privileged-setup # Run as root
|
||||
./user-operations generate <services> # Run as user
|
||||
./user-operations deploy # Run as user
|
||||
|
||||
# Option 3: Legacy installation (still supported)
|
||||
sudo ./hops.sh
|
||||
```
|
||||
|
||||
### 3. Follow the Interactive Setup
|
||||
- Select your desired services
|
||||
- Configure directories and timezone
|
||||
- Choose security options
|
||||
- Wait for automated deployment
|
||||
|
||||
### 4. Access Your Services
|
||||
The installer will provide URLs for all deployed 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
|
||||
```
|
||||
|
||||
## 📁 Default Directory Structure
|
||||
|
||||
```
|
||||
~/hops/ # Main deployment directory
|
||||
├── docker-compose.yml # Service definitions
|
||||
├── .env # Environment variables
|
||||
└── logs/ # Application logs
|
||||
|
||||
/opt/appdata/ # Application configurations
|
||||
├── jellyfin/
|
||||
├── sonarr/
|
||||
├── radarr/
|
||||
└── ...
|
||||
|
||||
/mnt/media/ # Media storage
|
||||
├── movies/
|
||||
├── tv/
|
||||
├── music/
|
||||
└── downloads/
|
||||
```
|
||||
|
||||
## 🎛️ Management Interface
|
||||
|
||||
HOPS includes a comprehensive management interface accessible through the main script:
|
||||
## Management
|
||||
|
||||
```bash
|
||||
sudo ./hops.sh
|
||||
# Access management interface
|
||||
sudo ./hops
|
||||
|
||||
# Update HOPS
|
||||
sudo ./hops --update # Update to latest version
|
||||
sudo ./hops --check-updates # Check for updates
|
||||
./hops --version # Show version
|
||||
./hops --help # Show help
|
||||
```
|
||||
|
||||
### Available Options:
|
||||
1. **Install HOPS** - Deploy new services
|
||||
2. **Uninstall HOPS** - Complete removal with options
|
||||
3. **Manage Services** - Start/stop/restart services
|
||||
4. **Service Status** - Real-time service monitoring
|
||||
5. **Access Information** - Get service URLs and credentials
|
||||
6. **View Logs** - Centralized log viewing
|
||||
7. **Help & Documentation** - Built-in help system
|
||||
[View advanced configuration and troubleshooting →](ADVANCED.md)
|
||||
|
||||
## 🔧 Advanced Configuration
|
||||
## Support
|
||||
|
||||
### Environment Variables
|
||||
Configuration is now stored encrypted for enhanced security:
|
||||
**HOPS Issues**: [Gitea Issues](https://git.canadabot.net/canadabot/hops/issues)
|
||||
|
||||
```bash
|
||||
# NEW: Encrypted secret management
|
||||
./lib/secrets.sh init # Initialize secret management
|
||||
./lib/secrets.sh create # Create encrypted environment
|
||||
./lib/secrets.sh update DOMAIN example.com # Update values
|
||||
./lib/secrets.sh get PUID # Get values
|
||||
./lib/secrets.sh list # List all keys
|
||||
**Service Issues**: Contact individual service developers (links in [SERVICES.md](SERVICES.md))
|
||||
|
||||
# Legacy: Plaintext configuration in ~/hops/.env
|
||||
PUID=1000 # User ID
|
||||
PGID=1000 # Group ID
|
||||
TZ=America/New_York # Timezone
|
||||
**Contact HOPS for**: Installation/deployment issues, Docker Compose problems, cross-platform issues
|
||||
**Contact Service Developers for**: Service configuration, features, service-specific bugs
|
||||
|
||||
# Directory Configuration
|
||||
DATA_ROOT=/mnt/media # Media storage
|
||||
CONFIG_ROOT=/opt/appdata # App configurations
|
||||
## Documentation
|
||||
|
||||
# Security (now auto-generated and encrypted)
|
||||
DEFAULT_ADMIN_PASSWORD=... # Generated secure password
|
||||
DEFAULT_DB_PASSWORD=... # Database password
|
||||
- [INSTALLATION.md](INSTALLATION.md) - Detailed installation guide
|
||||
- [SERVICES.md](SERVICES.md) - Complete service list with support links
|
||||
- [ADVANCED.md](ADVANCED.md) - Configuration, troubleshooting, security
|
||||
- [CHANGELOG.md](CHANGELOG.md) - Version history and changes
|
||||
|
||||
# Optional: Custom domain
|
||||
DOMAIN=yourdomain.com
|
||||
ACME_EMAIL=admin@yourdomain.com
|
||||
```
|
||||
## License
|
||||
|
||||
### Service-Specific Configuration
|
||||
|
||||
#### Caddy Configuration
|
||||
**Important**: HOPS provides the Caddy container but **does not include Caddyfile configuration**. Users must provide their own Caddyfile.
|
||||
|
||||
```bash
|
||||
# Create Caddy configuration directory
|
||||
mkdir -p ~/hops/config/caddy
|
||||
|
||||
# Create your Caddyfile (example)
|
||||
cat > ~/hops/config/caddy/Caddyfile << 'EOF'
|
||||
# Example Caddyfile - customize as needed
|
||||
example.com {
|
||||
reverse_proxy jellyfin:8096
|
||||
}
|
||||
|
||||
api.example.com {
|
||||
reverse_proxy overseerr:5055
|
||||
}
|
||||
EOF
|
||||
|
||||
# Caddy will automatically handle HTTPS certificates
|
||||
# Documentation: https://caddyserver.com/docs/
|
||||
```
|
||||
|
||||
**Configuration Scope**: HOPS installs and runs the Caddy container with proper volume mounts, but all routing, SSL, and proxy configuration is the user's responsibility.
|
||||
|
||||
### Service Management Commands
|
||||
```bash
|
||||
# NEW: User operations script (runs without sudo)
|
||||
./user-operations status # View service status
|
||||
./user-operations logs <service> # View service logs
|
||||
./user-operations deploy # Deploy services
|
||||
./user-operations stop # Stop all services
|
||||
|
||||
# Legacy: Direct Docker Compose commands
|
||||
cd ~/hops
|
||||
docker compose ps # View running services
|
||||
docker compose logs -f [service-name] # View logs
|
||||
docker compose restart [service-name] # Restart specific service
|
||||
docker compose pull && docker compose up -d # Update all services
|
||||
docker compose down # Stop all services
|
||||
```
|
||||
|
||||
### New Architecture
|
||||
HOPS v3.1.0-beta introduces a modular architecture with shared libraries:
|
||||
|
||||
```
|
||||
hops/
|
||||
├── lib/ # NEW: Shared libraries
|
||||
│ ├── common.sh # Logging, UI, utilities
|
||||
│ ├── system.sh # System validation
|
||||
│ ├── docker.sh # Docker operations
|
||||
│ ├── security.sh # Security utilities
|
||||
│ ├── validation.sh # Input validation
|
||||
│ ├── secrets.sh # Secret management
|
||||
│ └── privileges.sh # Privilege management
|
||||
├── setup # NEW: Installation wrapper
|
||||
├── privileged-setup # NEW: Root-only operations
|
||||
├── user-operations # NEW: User operations
|
||||
├── services-improved # NEW: Enhanced service definitions
|
||||
└── hops.sh # Legacy main script (still supported)
|
||||
```
|
||||
|
||||
## 🔒 Security Features
|
||||
|
||||
### Automatic Security Hardening
|
||||
- **Firewall Configuration**: Automatic UFW rules for service ports
|
||||
- **Secure Passwords**: Cryptographically secure password generation
|
||||
- **File Permissions**: Restrictive permissions on sensitive files
|
||||
- **Network Isolation**: Docker network segregation
|
||||
- **SSL/TLS**: Automatic certificate management with Traefik
|
||||
- **NEW**: AES-256 encrypted secret storage with master key management
|
||||
- **NEW**: Comprehensive input validation preventing injection attacks
|
||||
- **NEW**: Privilege separation (root vs user operations)
|
||||
- **NEW**: Pinned container versions preventing supply chain attacks
|
||||
|
||||
### Post-Installation Security
|
||||
1. **Manage Encrypted Secrets**: Use `./lib/secrets.sh` for secure password management
|
||||
2. **Configure Reverse Proxy**: Set up Traefik or Nginx Proxy Manager
|
||||
3. **Enable Authentication**: Configure Authelia for additional security
|
||||
4. **Regular Updates**: Use Watchtower for automatic updates
|
||||
5. **Security Auditing**: Use `./lib/security.sh` for security checks
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Port Conflicts
|
||||
```bash
|
||||
# Check for port conflicts
|
||||
sudo lsof -i :PORT_NUMBER
|
||||
|
||||
# View HOPS service status
|
||||
sudo ./hops.sh
|
||||
# Select option 4: Service Status
|
||||
```
|
||||
|
||||
#### Service Won't Start
|
||||
```bash
|
||||
# Check service logs
|
||||
cd ~/hops
|
||||
docker compose logs [service-name]
|
||||
|
||||
# Restart service
|
||||
docker compose restart [service-name]
|
||||
```
|
||||
|
||||
#### Permission Issues
|
||||
```bash
|
||||
# Fix ownership of data directories
|
||||
sudo chown -R $USER:$USER /mnt/media /opt/appdata
|
||||
```
|
||||
|
||||
### Log Locations
|
||||
- **Installation Logs**: `/var/log/hops/`
|
||||
- **Service Logs**: `docker compose logs [service-name]`
|
||||
- **System Logs**: `journalctl -u docker`
|
||||
|
||||
### Getting Help
|
||||
1. Check the built-in help: `sudo ./hops.sh` → Option 7
|
||||
2. Review logs in `/var/log/hops/`
|
||||
3. Verify Docker status: `systemctl status docker`
|
||||
4. Check service health: `docker compose ps`
|
||||
|
||||
## 🔄 Backup and Recovery
|
||||
|
||||
### Backup Important Data
|
||||
```bash
|
||||
# Backup configurations
|
||||
sudo tar -czf hops-config-backup.tar.gz /opt/appdata
|
||||
|
||||
# Backup compose files
|
||||
cp ~/hops/.env ~/hops/docker-compose.yml /backup/location/
|
||||
```
|
||||
|
||||
### Recovery
|
||||
```bash
|
||||
# Restore configurations
|
||||
sudo tar -xzf hops-config-backup.tar.gz -C /
|
||||
|
||||
# Redeploy services
|
||||
cd ~/hops
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 📊 Performance Tuning
|
||||
|
||||
### For Low-Resource Systems
|
||||
- Start with fewer services initially
|
||||
- Monitor resource usage with Portainer
|
||||
- Consider using lightweight alternatives (Transmission vs qBittorrent)
|
||||
|
||||
### For High-Performance Systems
|
||||
- Enable GPU transcoding in Jellyfin/Plex
|
||||
- Use SSD storage for application data
|
||||
- Configure multiple download clients for redundancy
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Test thoroughly
|
||||
5. Submit a pull request
|
||||
|
||||
### Development Setup
|
||||
```bash
|
||||
git clone https://github.com/skiercm/hops.git
|
||||
cd hops
|
||||
|
||||
# Test syntax validation
|
||||
bash -n lib/*.sh
|
||||
bash -n *.sh
|
||||
|
||||
# Test service definitions
|
||||
./services-improved list
|
||||
./services-improved generate jellyfin
|
||||
|
||||
# Test new installation method
|
||||
sudo ./setup
|
||||
|
||||
# Test legacy method
|
||||
sudo ./hops.sh
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- **LinuxServer.io** for excellent Docker images
|
||||
- **Docker** for containerization platform
|
||||
- **The Servarr Team** for the *arr applications
|
||||
- **Jellyfin Project** for the open-source media server
|
||||
- All the amazing open-source projects that make HOPS possible
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### HOPS Support
|
||||
- **Documentation**: Check this README and built-in help
|
||||
- **Issues**: Report HOPS bugs via [GitHub Issues](https://github.com/skiercm/hops/issues)
|
||||
- **Community**: Join discussions in [GitHub Discussions](https://github.com/skiercm/hops/discussions)
|
||||
|
||||
### Service-Specific Support
|
||||
|
||||
**⚠️ Important**: If you encounter issues with a specific service (configuration, features, bugs), please reach out to the respective service developers directly using the links below. HOPS only handles deployment automation - the individual services are maintained by their respective teams.
|
||||
|
||||
#### 📺 Media Management (*arr Stack)
|
||||
- **Sonarr**: [github.com/Sonarr/Sonarr](https://github.com/Sonarr/Sonarr) - TV series management
|
||||
- **Radarr**: [github.com/Radarr/Radarr](https://github.com/Radarr/Radarr) - Movie collection manager
|
||||
- **Lidarr**: [github.com/Lidarr/Lidarr](https://github.com/Lidarr/Lidarr) - Music collection manager
|
||||
- **Readarr**: [github.com/Readarr/Readarr](https://github.com/Readarr/Readarr) - E-book manager ⚠️ *Project retired*
|
||||
- **Bazarr**: [github.com/morpheus65535/bazarr](https://github.com/morpheus65535/bazarr) - Subtitle management
|
||||
- **Prowlarr**: [github.com/Prowlarr/Prowlarr](https://github.com/Prowlarr/Prowlarr) - Indexer manager
|
||||
- **Tdarr**: [github.com/HaveAGitGat/Tdarr](https://github.com/HaveAGitGat/Tdarr) - Media transcoding
|
||||
- **Huntarr**: [github.com/plexguide/Huntarr.io](https://github.com/plexguide/Huntarr.io) - Missing media discovery
|
||||
|
||||
#### ⬇️ Download Clients
|
||||
- **qBittorrent**: [github.com/qbittorrent/qBittorrent](https://github.com/qbittorrent/qBittorrent) - BitTorrent client
|
||||
- **Transmission**: [github.com/transmission/transmission](https://github.com/transmission/transmission) - BitTorrent client
|
||||
- **NZBGet**: [github.com/nzbget/nzbget](https://github.com/nzbget/nzbget) - Usenet downloader
|
||||
- **SABnzbd**: [github.com/sabnzbd/sabnzbd](https://github.com/sabnzbd/sabnzbd) - Usenet downloader
|
||||
|
||||
#### 🎞️ Media Servers
|
||||
- **Jellyfin**: [github.com/jellyfin/jellyfin](https://github.com/jellyfin/jellyfin) - Free media server
|
||||
- **Plex**: [github.com/plexinc/pms-docker](https://github.com/plexinc/pms-docker) - Docker container repo
|
||||
- **Emby**: [github.com/MediaBrowser/Emby](https://github.com/MediaBrowser/Emby) - Personal media server
|
||||
|
||||
#### 🎛️ Request Management
|
||||
- **Overseerr**: [github.com/sct/overseerr](https://github.com/sct/overseerr) - Media requests for Plex
|
||||
- **Jellyseerr**: [github.com/fallenbagel/jellyseerr](https://github.com/fallenbagel/jellyseerr) - Media requests for Jellyfin/Emby/Plex
|
||||
- **Ombi**: [github.com/Ombi-app/Ombi](https://github.com/Ombi-app/Ombi) - Media request platform
|
||||
- **Jellystat**: [github.com/CyferShepard/Jellystat](https://github.com/CyferShepard/Jellystat) - Jellyfin statistics
|
||||
|
||||
#### 🔒 Network & Security
|
||||
- **Traefik**: [github.com/traefik/traefik](https://github.com/traefik/traefik) - Modern reverse proxy
|
||||
- **Nginx Proxy Manager**: [github.com/NginxProxyManager/nginx-proxy-manager](https://github.com/NginxProxyManager/nginx-proxy-manager) - Nginx proxy management
|
||||
- **Authelia**: [github.com/authelia/authelia](https://github.com/authelia/authelia) - Authentication & SSO
|
||||
|
||||
#### 📈 Monitoring & Management
|
||||
- **Portainer**: [github.com/portainer/portainer](https://github.com/portainer/portainer) - Container management
|
||||
- **Watchtower**: [github.com/containrrr/watchtower](https://github.com/containrrr/watchtower) - Automatic updates
|
||||
- **Uptime Kuma**: [github.com/louislam/uptime-kuma](https://github.com/louislam/uptime-kuma) - Uptime monitoring
|
||||
|
||||
### When to Contact HOPS vs Service Developers
|
||||
|
||||
**Contact HOPS** for:
|
||||
- Installation/deployment issues
|
||||
- Docker Compose generation problems
|
||||
- Cross-platform compatibility issues
|
||||
- Script errors or automation failures
|
||||
|
||||
**Contact Service Developers** for:
|
||||
- Service configuration help
|
||||
- Feature requests for individual services
|
||||
- Bugs within the service itself
|
||||
- Service-specific documentation
|
||||
MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ for the homelab community**
|
||||
|
||||
*HOPS - Making homelab deployment simple, secure, and reliable.*
|
||||
**Made for the homelab community**
|
||||
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
# Supported Services
|
||||
|
||||
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)
|
||||
|
||||
### Sonarr - TV Series Management
|
||||
**Purpose**: Automatic TV show downloading and management
|
||||
**Default Port**: 8989
|
||||
**Support**: [github.com/Sonarr/Sonarr](https://github.com/Sonarr/Sonarr)
|
||||
**Documentation**: [wiki.servarr.com/sonarr](https://wiki.servarr.com/sonarr)
|
||||
|
||||
### Radarr - Movie Management
|
||||
**Purpose**: Automatic movie downloading and management
|
||||
**Default Port**: 7878
|
||||
**Support**: [github.com/Radarr/Radarr](https://github.com/Radarr/Radarr)
|
||||
**Documentation**: [wiki.servarr.com/radarr](https://wiki.servarr.com/radarr)
|
||||
|
||||
### Lidarr - Music Management
|
||||
**Purpose**: Automatic music downloading and management
|
||||
**Default Port**: 8686
|
||||
**Support**: [github.com/Lidarr/Lidarr](https://github.com/Lidarr/Lidarr)
|
||||
**Documentation**: [wiki.servarr.com/lidarr](https://wiki.servarr.com/lidarr)
|
||||
|
||||
### Readarr - eBook Management
|
||||
**Purpose**: Automatic eBook and audiobook downloading
|
||||
**Default Port**: 8787
|
||||
**Support**: [github.com/Readarr/Readarr](https://github.com/Readarr/Readarr)
|
||||
**Documentation**: [wiki.servarr.com/readarr](https://wiki.servarr.com/readarr)
|
||||
**Status**: Project retired, limited support
|
||||
|
||||
### Bazarr - Subtitle Management
|
||||
**Purpose**: Automatic subtitle downloading for movies and TV
|
||||
**Default Port**: 6767
|
||||
**Support**: [github.com/morpheus65535/bazarr](https://github.com/morpheus65535/bazarr)
|
||||
**Documentation**: [wiki.bazarr.media](https://wiki.bazarr.media)
|
||||
|
||||
### Prowlarr - Indexer Management
|
||||
**Purpose**: Centralized indexer management for *arr applications
|
||||
**Default Port**: 9696
|
||||
**Support**: [github.com/Prowlarr/Prowlarr](https://github.com/Prowlarr/Prowlarr)
|
||||
**Documentation**: [wiki.servarr.com/prowlarr](https://wiki.servarr.com/prowlarr)
|
||||
|
||||
### Tdarr - Media Transcoding
|
||||
**Purpose**: Automated media transcoding and health checking
|
||||
**Default Port**: 8265
|
||||
**Support**: [github.com/HaveAGitGat/Tdarr](https://github.com/HaveAGitGat/Tdarr)
|
||||
**Documentation**: [docs.tdarr.io](https://docs.tdarr.io)
|
||||
|
||||
### Huntarr - Missing Media Discovery
|
||||
**Purpose**: Automated discovery and addition of missing media
|
||||
**Default Port**: 7879
|
||||
**Support**: [github.com/plexguide/Huntarr.io](https://github.com/plexguide/Huntarr.io)
|
||||
**Documentation**: Community-driven
|
||||
|
||||
## Download Clients
|
||||
|
||||
### qBittorrent - BitTorrent Client
|
||||
**Purpose**: Feature-rich BitTorrent client with web interface
|
||||
**Default Port**: 8080
|
||||
**Support**: [github.com/qbittorrent/qBittorrent](https://github.com/qbittorrent/qBittorrent)
|
||||
**Documentation**: [github.com/qbittorrent/qBittorrent/wiki](https://github.com/qbittorrent/qBittorrent/wiki)
|
||||
|
||||
### Transmission - Lightweight BitTorrent
|
||||
**Purpose**: Simple, lightweight BitTorrent client
|
||||
**Default Port**: 9091
|
||||
**Support**: [github.com/transmission/transmission](https://github.com/transmission/transmission)
|
||||
**Documentation**: [transmissionbt.com](https://transmissionbt.com)
|
||||
|
||||
### NZBGet - Usenet Downloader
|
||||
**Purpose**: Efficient Usenet binary newsreader
|
||||
**Default Port**: 6789
|
||||
**Support**: [github.com/nzbget/nzbget](https://github.com/nzbget/nzbget)
|
||||
**Documentation**: [nzbget.net/documentation](https://nzbget.net/documentation)
|
||||
|
||||
### SABnzbd - Usenet Client
|
||||
**Purpose**: Popular web-based Usenet client
|
||||
**Default Port**: 8081
|
||||
**Support**: [github.com/sabnzbd/sabnzbd](https://github.com/sabnzbd/sabnzbd)
|
||||
**Documentation**: [sabnzbd.org/wiki](https://sabnzbd.org/wiki)
|
||||
|
||||
## Media Servers
|
||||
|
||||
### Jellyfin - Open Source Media Server
|
||||
**Purpose**: Free, open-source media server and entertainment hub
|
||||
**Default Port**: 8096
|
||||
**Support**: [github.com/jellyfin/jellyfin](https://github.com/jellyfin/jellyfin)
|
||||
**Documentation**: [jellyfin.org/docs](https://jellyfin.org/docs)
|
||||
**Features**: No licensing fees, privacy-focused, extensive format support
|
||||
|
||||
### Plex - Media Server Platform
|
||||
**Purpose**: Popular media server with premium features
|
||||
**Default Port**: 32400
|
||||
**Support**: [github.com/plexinc/pms-docker](https://github.com/plexinc/pms-docker)
|
||||
**Documentation**: [support.plex.tv](https://support.plex.tv)
|
||||
**Features**: Remote access, premium features with Plex Pass
|
||||
|
||||
### Emby - Personal Media Server
|
||||
**Purpose**: Feature-rich personal media server
|
||||
**Default Port**: 8097
|
||||
**Support**: [github.com/MediaBrowser/Emby](https://github.com/MediaBrowser/Emby)
|
||||
**Documentation**: [emby.media/support](https://emby.media/support)
|
||||
**Features**: Premium features available, family-friendly
|
||||
|
||||
### Jellystat - Jellyfin Statistics
|
||||
**Purpose**: Advanced statistics and monitoring for Jellyfin
|
||||
**Default Port**: 3000
|
||||
**Support**: [github.com/CyferShepard/Jellystat](https://github.com/CyferShepard/Jellystat)
|
||||
**Documentation**: GitHub repository
|
||||
**Requirements**: Requires Jellyfin server
|
||||
|
||||
## Request Management
|
||||
|
||||
### Overseerr - Plex Request Management
|
||||
**Purpose**: Media discovery and request management for Plex
|
||||
**Default Port**: 5055
|
||||
**Support**: [github.com/sct/overseerr](https://github.com/sct/overseerr)
|
||||
**Documentation**: [docs.overseerr.dev](https://docs.overseerr.dev)
|
||||
**Integration**: Plex, Sonarr, Radarr
|
||||
|
||||
### Jellyseerr - Jellyfin Request Management
|
||||
**Purpose**: Media requests for Jellyfin, Emby, and Plex
|
||||
**Default Port**: 5056
|
||||
**Support**: [github.com/fallenbagel/jellyseerr](https://github.com/fallenbagel/jellyseerr)
|
||||
**Documentation**: [docs.jellyseerr.dev](https://docs.jellyseerr.dev)
|
||||
**Integration**: Jellyfin, Emby, Plex, Sonarr, Radarr
|
||||
|
||||
### Ombi - Media Request Platform
|
||||
**Purpose**: User-friendly media request and discovery platform
|
||||
**Default Port**: 3579
|
||||
**Support**: [github.com/Ombi-app/Ombi](https://github.com/Ombi-app/Ombi)
|
||||
**Documentation**: [docs.ombi.app](https://docs.ombi.app)
|
||||
**Integration**: Plex, Emby, Jellyfin
|
||||
|
||||
## Reverse Proxy & Security
|
||||
|
||||
### Traefik - Modern Reverse Proxy
|
||||
**Purpose**: Automatic reverse proxy with SSL certificate management
|
||||
**Default Ports**: 80, 443, 8080 (dashboard)
|
||||
**Support**: [github.com/traefik/traefik](https://github.com/traefik/traefik)
|
||||
**Documentation**: [doc.traefik.io/traefik](https://doc.traefik.io/traefik)
|
||||
**Features**: Automatic SSL, service discovery, load balancing
|
||||
|
||||
### Nginx Proxy Manager - Easy Reverse Proxy
|
||||
**Purpose**: User-friendly web interface for Nginx reverse proxy
|
||||
**Default Ports**: 80, 443, 81 (admin)
|
||||
**Support**: [github.com/NginxProxyManager/nginx-proxy-manager](https://github.com/NginxProxyManager/nginx-proxy-manager)
|
||||
**Documentation**: [nginxproxymanager.com/guide](https://nginxproxymanager.com/guide)
|
||||
**Features**: Web GUI, SSL automation, access lists
|
||||
|
||||
### Caddy - Automatic HTTPS Web Server
|
||||
**Purpose**: Modern web server with automatic HTTPS
|
||||
**Default Ports**: 80, 443, 2019 (admin)
|
||||
**Support**: [github.com/caddyserver/caddy](https://github.com/caddyserver/caddy)
|
||||
**Documentation**: [caddyserver.com/docs](https://caddyserver.com/docs)
|
||||
**Note**: **Configuration not included** - Users must provide their own Caddyfile
|
||||
|
||||
### Authelia - Authentication & Authorization
|
||||
**Purpose**: Multi-factor authentication and single sign-on
|
||||
**Default Port**: 9091
|
||||
**Support**: [github.com/authelia/authelia](https://github.com/authelia/authelia)
|
||||
**Documentation**: [authelia.com/integration](https://www.authelia.com/integration)
|
||||
**Features**: 2FA, LDAP, OAuth2, OIDC
|
||||
|
||||
## Monitoring & Management
|
||||
|
||||
### Portainer - Container Management
|
||||
**Purpose**: Web-based Docker container management interface
|
||||
**Default Port**: 9000
|
||||
**Support**: [github.com/portainer/portainer](https://github.com/portainer/portainer)
|
||||
**Documentation**: [docs.portainer.io](https://docs.portainer.io)
|
||||
**Features**: Container management, stack deployment, monitoring
|
||||
|
||||
### Uptime Kuma - Service Monitoring
|
||||
**Purpose**: Self-hosted uptime monitoring tool
|
||||
**Default Port**: 3001
|
||||
**Support**: [github.com/louislam/uptime-kuma](https://github.com/louislam/uptime-kuma)
|
||||
**Documentation**: [github.com/louislam/uptime-kuma/wiki](https://github.com/louislam/uptime-kuma/wiki)
|
||||
**Features**: Multiple protocols, notifications, status pages
|
||||
|
||||
### Watchtower - Automatic Updates
|
||||
**Purpose**: Automatic Docker container updating
|
||||
**Default Port**: None (background service)
|
||||
**Support**: [github.com/containrrr/watchtower](https://github.com/containrrr/watchtower)
|
||||
**Documentation**: [containrrr.dev/watchtower](https://containrrr.dev/watchtower)
|
||||
**Features**: Scheduled updates, notifications, selective updating
|
||||
|
||||
## Service Dependencies
|
||||
|
||||
### Common Dependencies
|
||||
Most services depend on:
|
||||
- **Docker** and **Docker Compose**
|
||||
- **Shared network** (`homelab` network)
|
||||
- **Volume mounts** for configuration and data
|
||||
- **Environment variables** (PUID, PGID, TZ)
|
||||
|
||||
### Integration Patterns
|
||||
|
||||
#### Media Management Workflow
|
||||
1. **Prowlarr** → Manages indexers for all *arr services
|
||||
2. **Sonarr/Radarr** → Monitors for new releases
|
||||
3. **Download Client** (qBittorrent/Transmission) → Downloads content
|
||||
4. **Media Server** (Jellyfin/Plex) → Serves content to users
|
||||
5. **Request System** (Overseerr/Jellyseerr) → User requests interface
|
||||
|
||||
#### Security & Access
|
||||
1. **Reverse Proxy** (Traefik/Nginx) → External access with SSL
|
||||
2. **Authelia** → Authentication layer
|
||||
3. **Firewall** → Network security (UFW on Linux)
|
||||
|
||||
#### Monitoring Stack
|
||||
1. **Portainer** → Container management
|
||||
2. **Uptime Kuma** → Service monitoring
|
||||
3. **Watchtower** → Automatic updates
|
||||
|
||||
## Important Service Notes
|
||||
|
||||
### Caddy Configuration
|
||||
HOPS provides the Caddy container but **does not include Caddyfile configuration**. Users must:
|
||||
1. Create their own Caddyfile in `~/hops/config/caddy/`
|
||||
2. Configure reverse proxy rules
|
||||
3. Set up SSL certificates (automatic with proper domain configuration)
|
||||
|
||||
**Example minimal Caddyfile:**
|
||||
```
|
||||
example.com {
|
||||
reverse_proxy jellyfin:8096
|
||||
}
|
||||
|
||||
radarr.example.com {
|
||||
reverse_proxy radarr:7878
|
||||
}
|
||||
```
|
||||
|
||||
### GPU Support
|
||||
- **Linux**: Intel GPU support via `/dev/dri` passthrough
|
||||
|
||||
### Service Health Checks
|
||||
All web-based services include health checks for:
|
||||
- Service startup verification
|
||||
- Automatic restart on failure
|
||||
- Status monitoring integration
|
||||
|
||||
## Service-Specific Support
|
||||
|
||||
### When to Contact Service Developers
|
||||
|
||||
**Contact individual service developers for:**
|
||||
- Service configuration help
|
||||
- Feature requests
|
||||
- Bugs within the service itself
|
||||
- Service-specific documentation
|
||||
- Advanced service setup
|
||||
|
||||
**Contact HOPS for:**
|
||||
- Docker Compose generation issues
|
||||
- Service deployment problems
|
||||
- Installation and automation issues
|
||||
|
||||
### Getting Service Help
|
||||
|
||||
1. **Check service documentation** (links provided above)
|
||||
2. **Review service GitHub issues** for known problems
|
||||
3. **Check service community forums** (Reddit, Discord, etc.)
|
||||
4. **Consult LinuxServer.io documentation** for container-specific issues
|
||||
5. **Submit issues to appropriate repositories** with proper logs and details
|
||||
|
||||
### Common Service Issues
|
||||
|
||||
#### Permission Problems
|
||||
```bash
|
||||
sudo chown -R $USER:$USER /opt/appdata/[service-name]
|
||||
```
|
||||
|
||||
#### Service Won't Start
|
||||
```bash
|
||||
# Check service logs
|
||||
docker compose logs [service-name]
|
||||
|
||||
# Restart service
|
||||
docker compose restart [service-name]
|
||||
```
|
||||
|
||||
#### Configuration Issues
|
||||
Service configuration is stored in `/opt/appdata/[service-name]/`.
|
||||
|
||||
---
|
||||
|
||||
For installation help, see [INSTALLATION.md](INSTALLATION.md).
|
||||
For advanced configuration, see [ADVANCED.md](ADVANCED.md).
|
||||
@@ -0,0 +1,286 @@
|
||||
# HOPS TODO
|
||||
|
||||
Generated by codebase audit (2026-06-10). Ranked by severity.
|
||||
|
||||
## Decisions (2026-06-10)
|
||||
|
||||
- **Canonical pipeline**: Path A (`hops` -> `install` -> `services`). Path B deleted.
|
||||
- **Deleted**: `setup`, `privileged-setup`, `user-operations`, `services-improved`,
|
||||
`lib/privileges.sh` -- all Path B artifacts, gone.
|
||||
- **Service catalog**: `services` is the single source of truth. Latest tags kept.
|
||||
- **`lib/secrets.sh`**: keep and fix. Goal is to encrypt the `.env` file at rest
|
||||
(passwords/API keys written by `install`). Fix the broken AES-GCM crypto and
|
||||
wire encryption/decryption into the install flow.
|
||||
- **macOS**: future roadmap. Linux is the target for now.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL BUGS (breaks primary use cases)
|
||||
|
||||
### B1 -- Infinite recursion in `services` on Linux [CRITICAL] -- RESOLVED
|
||||
- File: `services:25-46`
|
||||
- `get_timezone_mount()` and `get_gpu_devices()` called themselves on the non-Darwin
|
||||
branch. Fixed: both functions now return literal YAML strings directly.
|
||||
|
||||
### B2 -- Brace mismatch in `lib/privileges.sh` [CRITICAL] -- RESOLVED: delete file
|
||||
- File: `lib/privileges.sh:429,612`
|
||||
- Moot -- `lib/privileges.sh` is Path B dead code, scheduled for deletion (see A3).
|
||||
|
||||
---
|
||||
|
||||
## HIGH BUGS
|
||||
|
||||
### B3 -- Glob stored as string, directory detection always fails [HIGH] -- RESOLVED
|
||||
- Files: `hops:154-166`, `uninstall:127-147`
|
||||
- Glob removed from array; expanded separately via `compgen -G "/home/*/hops"`.
|
||||
Also fixed `eval echo "~$SUDO_USER"` -> `getent passwd` in `uninstall`.
|
||||
|
||||
### B4 -- Missing service definitions file reference [HIGH] -- RESOLVED
|
||||
- File: `install:916`
|
||||
- Corrected source path from `hops_service_definitions.sh` to `services`.
|
||||
|
||||
### B5 -- `((x++))` aborts script under `set -e` [HIGH] -- RESOLVED
|
||||
- Files: `hops`, `install`
|
||||
- All `((x++))` occurrences replaced with `x=$((x + 1))`.
|
||||
|
||||
### B6 -- `hops` entry point is Linux-only despite macOS library support [HIGH] -- RESOLVED
|
||||
- File: `hops`
|
||||
- Added Linux-only guard at top of script; exits immediately with a clear error on non-Linux.
|
||||
|
||||
### B7 -- Port collisions not detected within a selection [HIGH]
|
||||
- File: `services` (port map)
|
||||
- sabnzbd and traefik dashboard both use 8080; traefik and nginx-proxy-manager
|
||||
both bind 80/443; authelia and transmission both use 9091.
|
||||
- `check_all_ports` only checks host listeners, not intra-selection conflicts,
|
||||
so users can generate an un-startable compose silently.
|
||||
- Fix: add intra-selection conflict check before compose generation.
|
||||
|
||||
---
|
||||
|
||||
## MEDIUM BUGS
|
||||
|
||||
### B8 -- Watchtower assigned a bogus port [MED]
|
||||
- Files: `lib/docker.sh:47`, `services-improved:90`
|
||||
- Watchtower has no web UI. Assigning it port 8080 emits a spurious `ports:`
|
||||
block and broken healthcheck in the generated compose.
|
||||
|
||||
### B9 -- Update backup copies into itself [MED]
|
||||
- File: `hops:586-595`
|
||||
- `update_hops` does `cp -r "$SCRIPT_DIR" "$backup_dir"` where `$backup_dir`
|
||||
is inside `$SCRIPT_DIR`. Results in recursive self-copy including `.git/`.
|
||||
- Fix: create the backup dir outside the script directory.
|
||||
|
||||
### B10 -- `secure_delete` `stat` flag wrong on macOS [MED]
|
||||
- File: `lib/secrets.sh:146`
|
||||
- Uses `stat -c%s` (GNU) which fails on macOS (`stat -f%z`).
|
||||
Manual-overwrite fallback silently no-ops on macOS.
|
||||
|
||||
### B11 -- `jellystat` generated with wrong template in `services-improved` [MED]
|
||||
- File: `services-improved:422`
|
||||
- Routed through the generic media-server template; gets no postgres DB and no
|
||||
JWT_SECRET, so it cannot run. The hand-written `services` version is correct.
|
||||
|
||||
### B12 -- Empty-password detection regex broken [LOW]
|
||||
- File: `lib/security.sh:361-384`
|
||||
- `grep "PASSWORD=\s*$"` without `-E` or `-P` means `\s` is matched literally,
|
||||
not as whitespace. Empty-password detection is dead.
|
||||
|
||||
---
|
||||
|
||||
## SECURITY
|
||||
|
||||
### S1 -- Broken/unauthenticated encryption [HIGH]
|
||||
- File: `lib/secrets.sh:85,115`
|
||||
- `openssl enc -aes-256-gcm` via the CLI does not handle the GCM auth tag.
|
||||
This is not authenticated encryption and round-trips unreliably.
|
||||
- Fix: use a supported openssl mode or switch to `gpg --symmetric`.
|
||||
|
||||
### S2 -- Passphrases/keys exposed in process list [HIGH]
|
||||
- Files: `lib/secrets.sh:85,115`, `lib/security.sh:140,156,175,204,416,442`
|
||||
- `-pass pass:"$key"` and `--passphrase "$x"` on the command line are visible
|
||||
to any local user via `ps`.
|
||||
- Fix: use `-pass fd:N` or `--passphrase-fd N` with a file descriptor.
|
||||
|
||||
### S3 -- Committed default Authelia credential [HIGH]
|
||||
- File: `services:1148-1157`
|
||||
- `users_database.yml` ships a default admin account with a known password hash
|
||||
(hash of literal "password"). Every Authelia deploy has this credential.
|
||||
- Fix: force password change on first login or generate the hash at deploy time.
|
||||
|
||||
### S4 -- Traefik dashboard exposed with no auth [MED]
|
||||
- File: `services:672-673,684`
|
||||
- `api.insecure=true` exposes the Traefik dashboard on :8080 with no auth.
|
||||
Consider disabling or requiring middleware auth.
|
||||
|
||||
### S5 -- `eval` on environment-derived value [MED]
|
||||
- Files: `install:598,671`, `uninstall:136,462`, `lib/system.sh:306`, others
|
||||
- `eval echo "~$SUDO_USER"` expands an env-sourced value through eval.
|
||||
- Fix: `getent passwd "$SUDO_USER" | cut -d: -f6`
|
||||
|
||||
### S6 -- Predictable temp file paths [MED]
|
||||
- Files: `lib/secrets.sh:16,188,288`, `uninstall:374`
|
||||
- `/tmp/hops_env_$$` etc. in world-writable `/tmp` are symlink-race targets
|
||||
before the `chmod 600` runs.
|
||||
- Fix: use `mktemp` and assign before use.
|
||||
|
||||
### S7 -- Commands built as strings, run unquoted [MED]
|
||||
- File: `install:731-736,755,773-779`
|
||||
- `pull_cmd="sudo -u $SUDO_USER docker compose pull"` run as `$pull_cmd`
|
||||
is fragile with unusual usernames and bypasses quoting.
|
||||
- Fix: use bash arrays.
|
||||
|
||||
### S9 -- Non-idempotent sysctl append [LOW]
|
||||
- File: `privileged-setup:224`
|
||||
- `echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf` appended every run.
|
||||
Accumulates duplicate lines.
|
||||
- Fix: check before appending (`grep -q ... || echo ... >>`)
|
||||
|
||||
---
|
||||
|
||||
## ARCHITECTURE / DESIGN
|
||||
|
||||
### A1 -- Two divergent install pipelines [HIGH] -- RESOLVED
|
||||
- Path A chosen. Delete: `setup`, `privileged-setup`, `user-operations`,
|
||||
`services-improved`.
|
||||
|
||||
### A2 -- Three sources of truth for the service catalog [HIGH] -- DO FIRST
|
||||
- `services`: `get_service_ports()` + inline image strings (CANONICAL)
|
||||
- `services-improved`: scheduled for deletion (Path B)
|
||||
- `lib/docker.sh`: `HOPS_SERVICES` array -- reconcile or remove duplicates
|
||||
- Fix: `lib/docker.sh` service maps must match `services`; remove anything
|
||||
only used by Path B.
|
||||
- Must be done before port collision fix (B7) so there is one authoritative map.
|
||||
|
||||
### A3 -- `lib/privileges.sh` is dead code [MED] -- RESOLVED: delete
|
||||
- Path B artifact. Delete it.
|
||||
|
||||
### A4 -- `lib/secrets.sh` crypto needs fixing and wiring in [MED]
|
||||
- Goal: encrypt the `.env` file at rest after `install` writes it.
|
||||
- Fix broken AES-GCM (use `gpg --symmetric` or a supported openssl mode).
|
||||
- Fix passphrase-on-command-line exposure (S1, S2).
|
||||
- Wire encrypt/decrypt calls into `install` flow.
|
||||
|
||||
### A5 -- `hops` duplicates functions from `lib/common.sh` [HIGH] -- RESOLVED
|
||||
- `log`, `error_exit`, `warning`, `success`, `info`, `validate_timezone`,
|
||||
`validate_password`, `generate_secure_password`, `create_docker_networks`
|
||||
removed from `hops`, `uninstall`, and `install`. Canonical copies kept in
|
||||
`lib/common.sh`, `lib/security.sh`, `lib/validation.sh`, `lib/docker.sh`.
|
||||
- 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]
|
||||
- `services` defines `generate_caddy` but the `select_services` menu in
|
||||
`install` never lists caddy as a selectable option.
|
||||
|
||||
### A7 -- Committed dev artifacts [LOW]
|
||||
- `summary7-19.txt` and `discord-header.md` should not be in the repo.
|
||||
Add to `.gitignore` or delete.
|
||||
|
||||
---
|
||||
|
||||
## MISSING / INCOMPLETE
|
||||
|
||||
### M1 -- RESOLVED (Path B deleted)
|
||||
|
||||
### M2 -- RESOLVED (Path B deleted)
|
||||
|
||||
### M3 -- RESOLVED (Path B 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
|
||||
|
||||
### P2 -- `uninstall` is Linux-only [HIGH] -- deferred (Linux-first)
|
||||
- Unconditional `apt-get`, `dpkg`, `systemctl`, `groupdel`, `ufw` with no
|
||||
OS branching. Acceptable for now; revisit when macOS support is scoped.
|
||||
|
||||
### P3 -- RESOLVED (Path B deleted)
|
||||
|
||||
### P4 -- No WSL2 detection [MED]
|
||||
- README claims WSL2 support but there is no WSL2 detection.
|
||||
`systemctl`-based service management fails on WSL distros without systemd.
|
||||
|
||||
### P5 -- Inconsistent port-check tools [MED]
|
||||
- `lib/common.sh` uses `ss`; `install` uses `lsof`. `ss` is absent on macOS.
|
||||
|
||||
### P6 -- Hardcoded render GID for Jellyfin GPU [LOW]
|
||||
- File: `services:435`
|
||||
- `group_add: "109"` is the render GID on a specific distro, wrong on most
|
||||
systems and meaningless on macOS.
|
||||
|
||||
---
|
||||
|
||||
## CODE QUALITY
|
||||
|
||||
### Q1 -- Three separate error-handling implementations [HIGH] -- RESOLVED
|
||||
- Covered by A5; all resolved.
|
||||
|
||||
### Q2 -- `set -e` + intentional non-zero returns is a minefield [MED]
|
||||
- `validate_password` returns 1/2/3, `check_port` returns 1 -- these work only
|
||||
because they happen to be in conditionals. Combined with B5 this is fragile.
|
||||
Consider `set -euo pipefail` with explicit `|| true` where non-zero is intended.
|
||||
|
||||
### Q3 -- Debug `echo` statements left in production code [MED] -- DO FIRST
|
||||
- Files: `lib/system.sh:605,823,1043,1046,1084,1089,1149-1156`
|
||||
- `DEBUG:` prefixed echo lines should be removed or gated behind a `$DEBUG` flag.
|
||||
- Clean these before bug fixes so signal isn't buried in debug noise.
|
||||
|
||||
### Q4 -- `services-improved` leaks `set -e` when sourced [LOW]
|
||||
- File: `services-improved` top of file
|
||||
- File sets `set -e` then is sourced by `user-operations` and `privileges`,
|
||||
leaking the option into the caller's shell.
|
||||
|
||||
---
|
||||
|
||||
## SUGGESTED ORDER OF ATTACK
|
||||
|
||||
### Cleanup first (do before any bug fixes)
|
||||
1. [DONE] Delete Path B files (A1/A3)
|
||||
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
|
||||
4. Remove debug echo statements from `lib/system.sh` (Q3) -- reduce noise
|
||||
|
||||
### Bug fixes
|
||||
5. [DONE] Fix B1 (infinite recursion in `services`)
|
||||
6. [DONE] Fix B5 (`((x++))` under `set -e`)
|
||||
7. [DONE] Fix B3 (glob directory detection)
|
||||
8. [DONE] Fix B4 (wrong filename in firewall setup)
|
||||
9. Fix B7 (intra-selection port collision detection)
|
||||
|
||||
### Security pass
|
||||
10. S3 (default Authelia cred), S5 (eval on env var), S6 (mktemp),
|
||||
S7 (string-built commands), S2 (passphrase on cmdline)
|
||||
11. Fix and wire in `lib/secrets.sh`: replace broken crypto, hook into install
|
||||
flow to encrypt `.env` at rest (A4/S1/S2)
|
||||
|
||||
### Remaining
|
||||
12. Fix B12 empty-password regex, B8 watchtower port, B9 backup self-copy
|
||||
13. macOS / WSL2 support (B6, P4) -- future roadmap
|
||||
@@ -0,0 +1,95 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Docker Not Starting
|
||||
|
||||
```bash
|
||||
sudo systemctl status docker
|
||||
sudo systemctl restart docker
|
||||
journalctl -u docker --since "1 hour ago"
|
||||
```
|
||||
|
||||
If Docker is missing or broken:
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
## 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
|
||||
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
|
||||
```
|
||||
|
||||
## 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
|
||||
cd ~/hops
|
||||
docker compose logs [service-name]
|
||||
docker compose ps
|
||||
docker compose restart [service-name]
|
||||
```
|
||||
|
||||
## Port Already in Use
|
||||
|
||||
```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
|
||||
lsb_release -a
|
||||
uname -a
|
||||
docker --version
|
||||
docker compose version
|
||||
./hops --version
|
||||
cd ~/hops && docker compose ps
|
||||
```
|
||||
|
||||
Report at: [git.canadabot.net/canadabot/hops/issues](https://git.canadabot.net/canadabot/hops/issues)
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
# HOPS - Homelab Orchestration Provisioning Script
|
||||
# Primary Management Script
|
||||
# Version: 3.2.0
|
||||
# Version: 1.0.0
|
||||
|
||||
# Exit on any error
|
||||
set -e
|
||||
|
||||
# Script version and metadata
|
||||
readonly SCRIPT_VERSION="3.2.0"
|
||||
if [[ "$(uname -s)" != "Linux" ]]; then
|
||||
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_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
@@ -17,61 +21,15 @@ readonly INSTALLER_SCRIPT="$SCRIPT_DIR/install"
|
||||
readonly UNINSTALLER_SCRIPT="$SCRIPT_DIR/uninstall"
|
||||
readonly SERVICE_DEFINITIONS="$SCRIPT_DIR/services"
|
||||
|
||||
# Color codes for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly BLUE='\033[0;34m'
|
||||
readonly PURPLE='\033[0;35m'
|
||||
readonly CYAN='\033[0;36m'
|
||||
readonly WHITE='\033[1;37m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Load system utilities
|
||||
# Load shared utilities
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/system.sh"
|
||||
|
||||
# Logging setup (will be set by setup_logging)
|
||||
LOG_DIR=""
|
||||
LOG_FILE=""
|
||||
|
||||
# Initialize logging
|
||||
init_logging() {
|
||||
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
|
||||
show_header() {
|
||||
clear
|
||||
@@ -159,11 +117,11 @@ get_installation_status() {
|
||||
local status="not_installed"
|
||||
local homelab_dirs=(
|
||||
"$HOME/hops"
|
||||
"/home/*/hops"
|
||||
"/opt/hops"
|
||||
"/srv/hops"
|
||||
)
|
||||
|
||||
while IFS= read -r d; do homelab_dirs+=("$d"); done < <(compgen -G "/home/*/hops" 2>/dev/null)
|
||||
|
||||
# Check for existing installation
|
||||
for dir in "${homelab_dirs[@]}"; do
|
||||
if [[ -f "$dir/docker-compose.yml" ]]; then
|
||||
@@ -299,14 +257,14 @@ show_service_status() {
|
||||
local service_port="${service_info#*:}"
|
||||
|
||||
if docker ps --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
||||
((total_count++))
|
||||
total_count=$((total_count + 1))
|
||||
local status_symbol="${GREEN}●${NC}"
|
||||
local status_text="Running"
|
||||
|
||||
# Check if port is accessible
|
||||
if curl -sSf --max-time 2 --connect-timeout 1 "http://localhost:${service_port}" >/dev/null 2>&1; then
|
||||
status_text="Running & Accessible"
|
||||
((running_count++))
|
||||
running_count=$((running_count + 1))
|
||||
else
|
||||
status_text="Running (starting up)"
|
||||
status_symbol="${YELLOW}●${NC}"
|
||||
@@ -314,7 +272,7 @@ show_service_status() {
|
||||
|
||||
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
|
||||
((total_count++))
|
||||
total_count=$((total_count + 1))
|
||||
printf " %s %-20s %s\n" "${RED}●${NC}" "$service_name" "Stopped"
|
||||
fi
|
||||
done
|
||||
@@ -479,7 +437,7 @@ show_access_info() {
|
||||
if docker ps --format "{{.Names}}" | grep -qi "${service_name,,}"; then
|
||||
local url="http://${local_ip}:${service_port}${service_path}"
|
||||
printf " ${GREEN}●${NC} %-15s %s\n" "$service_name" "$url"
|
||||
((active_services++))
|
||||
active_services=$((active_services + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -526,7 +484,7 @@ show_logs() {
|
||||
local date=$(stat -c %y "$log_file" | cut -d' ' -f1)
|
||||
|
||||
printf " %d) %-40s (%s, %s)\n" "$count" "$basename_log" "$size" "$date"
|
||||
((count++))
|
||||
count=$((count + 1))
|
||||
done
|
||||
|
||||
echo -e "\n${WHITE}Select a log file to view [1-${#log_files[@]}] or 0 to go back: ${NC}"
|
||||
@@ -546,6 +504,109 @@ show_logs() {
|
||||
read -r
|
||||
}
|
||||
|
||||
# Check for updates
|
||||
check_for_updates() {
|
||||
info "Checking for updates..."
|
||||
|
||||
# Check if we're in a git repository
|
||||
if ! git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
||||
warning "Not in a git repository. Cannot check for updates."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Fetch latest changes
|
||||
if ! git -C "$SCRIPT_DIR" fetch origin main >/dev/null 2>&1; then
|
||||
warning "Failed to fetch updates from remote repository."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if we're behind
|
||||
local local_commit=$(git -C "$SCRIPT_DIR" rev-parse HEAD)
|
||||
local remote_commit=$(git -C "$SCRIPT_DIR" rev-parse origin/main)
|
||||
|
||||
if [[ "$local_commit" == "$remote_commit" ]]; then
|
||||
success "HOPS is up to date (v$SCRIPT_VERSION)"
|
||||
return 0
|
||||
else
|
||||
local commits_behind=$(git -C "$SCRIPT_DIR" rev-list --count HEAD..origin/main)
|
||||
warning "HOPS is $commits_behind commits behind. Update available!"
|
||||
|
||||
# Show what's new
|
||||
echo -e "\n${BLUE}📋 Recent changes:${NC}"
|
||||
git -C "$SCRIPT_DIR" log --oneline --max-count=5 HEAD..origin/main | sed 's/^/ • /'
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Update HOPS
|
||||
update_hops() {
|
||||
show_header
|
||||
echo -e "${WHITE}🔄 HOPS Update${NC}\n"
|
||||
|
||||
# Check if we're in a git repository
|
||||
if ! git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
||||
error_exit "Not in a git repository. Cannot update automatically."
|
||||
fi
|
||||
|
||||
# Check for local changes
|
||||
if ! git -C "$SCRIPT_DIR" diff-index --quiet HEAD --; then
|
||||
warning "Local changes detected. These will be backed up before updating."
|
||||
|
||||
# Create backup
|
||||
local backup_dir="$SCRIPT_DIR/.backup-$(date +%Y%m%d-%H%M%S)"
|
||||
info "Creating backup at: $backup_dir"
|
||||
|
||||
if ! cp -r "$SCRIPT_DIR" "$backup_dir"; then
|
||||
error_exit "Failed to create backup"
|
||||
fi
|
||||
|
||||
success "Backup created successfully"
|
||||
fi
|
||||
|
||||
# Fetch and show what will be updated
|
||||
info "Fetching latest changes..."
|
||||
if ! git -C "$SCRIPT_DIR" fetch origin main; then
|
||||
error_exit "Failed to fetch updates from remote repository"
|
||||
fi
|
||||
|
||||
# Check if update is needed
|
||||
if ! check_for_updates; then
|
||||
echo -e "\n${WHITE}Continue with update? [y/N]: ${NC}"
|
||||
read -r update_choice
|
||||
if [[ ! "$update_choice" =~ ^[Yy]$ ]]; then
|
||||
info "Update cancelled"
|
||||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||||
read -r
|
||||
return
|
||||
fi
|
||||
else
|
||||
info "Already up to date"
|
||||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||||
read -r
|
||||
return
|
||||
fi
|
||||
|
||||
# Perform the update
|
||||
info "Updating HOPS..."
|
||||
if git -C "$SCRIPT_DIR" pull origin main; then
|
||||
success "HOPS updated successfully!"
|
||||
|
||||
# Source the updated script to get new version
|
||||
if [[ -f "$SCRIPT_DIR/hops" ]]; then
|
||||
local new_version=$(grep '^readonly SCRIPT_VERSION=' "$SCRIPT_DIR/lib/common.sh" | cut -d'"' -f2)
|
||||
success "Updated to version $new_version"
|
||||
fi
|
||||
|
||||
echo -e "\n${YELLOW}💡 Note: Please restart HOPS to use the updated version${NC}"
|
||||
else
|
||||
error_exit "Update failed. Your installation may be in an inconsistent state."
|
||||
fi
|
||||
|
||||
echo -e "\n${WHITE}Press Enter to exit (restart HOPS to use new version)...${NC}"
|
||||
read -r
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Show help information
|
||||
show_help() {
|
||||
show_header
|
||||
@@ -624,11 +685,12 @@ show_main_menu() {
|
||||
echo -e " 5) Access Information ${YELLOW}(not installed)${NC}"
|
||||
fi
|
||||
|
||||
echo -e " 6) View Logs"
|
||||
echo -e " 7) Help & Documentation"
|
||||
echo -e " 8) Exit"
|
||||
echo -e " 6) Check for Updates"
|
||||
echo -e " 7) View Logs"
|
||||
echo -e " 8) Help & Documentation"
|
||||
echo -e " 9) Exit"
|
||||
|
||||
echo -e "\n${WHITE}Select an option [1-8]: ${NC}"
|
||||
echo -e "\n${WHITE}Select an option [1-9]: ${NC}"
|
||||
}
|
||||
|
||||
# Main program loop
|
||||
@@ -662,24 +724,87 @@ main() {
|
||||
show_access_info
|
||||
;;
|
||||
6)
|
||||
show_logs
|
||||
# Check for updates and optionally update
|
||||
if check_for_updates; then
|
||||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||||
read -r
|
||||
else
|
||||
echo -e "\n${WHITE}Would you like to update now? [y/N]: ${NC}"
|
||||
read -r update_choice
|
||||
if [[ "$update_choice" =~ ^[Yy]$ ]]; then
|
||||
update_hops
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
7)
|
||||
show_help
|
||||
show_logs
|
||||
;;
|
||||
8)
|
||||
show_help
|
||||
;;
|
||||
9)
|
||||
info "Thank you for using HOPS!"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
warning "Invalid option. Please select 1-8."
|
||||
warning "Invalid option. Please select 1-9."
|
||||
sleep 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Handle command line arguments
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--update)
|
||||
init_logging
|
||||
check_root
|
||||
update_hops
|
||||
exit $?
|
||||
;;
|
||||
--check-updates)
|
||||
init_logging
|
||||
if check_for_updates; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--version)
|
||||
echo "HOPS v$SCRIPT_VERSION"
|
||||
exit 0
|
||||
;;
|
||||
--help|-h)
|
||||
echo "HOPS - Homelab Orchestration Provisioning Script v$SCRIPT_VERSION"
|
||||
echo ""
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --update Update HOPS to the latest version"
|
||||
echo " --check-updates Check if updates are available (exit 1 if updates available)"
|
||||
echo " --version Show version information"
|
||||
echo " --help, -h Show this help message"
|
||||
echo ""
|
||||
echo "When run without options, HOPS starts in interactive mode."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Use --help for usage information."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
# Script entry point
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
# Parse command line arguments first
|
||||
parse_args "$@"
|
||||
|
||||
# If no arguments provided, run main interactive mode
|
||||
main
|
||||
fi
|
||||
@@ -8,12 +8,14 @@ install_hops() {
|
||||
set -e
|
||||
|
||||
# Script version for update tracking
|
||||
local SCRIPT_VERSION="3.2.0"
|
||||
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/system.sh"
|
||||
source "$SCRIPT_DIR/lib/validation.sh"
|
||||
source "$SCRIPT_DIR/lib/security.sh"
|
||||
source "$SCRIPT_DIR/lib/docker.sh"
|
||||
|
||||
# --------------------------------------------
|
||||
# LOGGING SETUP
|
||||
@@ -174,8 +176,7 @@ EOF
|
||||
if [[ "$keep_tz" =~ ^[Nn]$ ]]; then
|
||||
echo -e "Enter timezone (e.g., America/New_York, Europe/London): "
|
||||
read -r user_timezone
|
||||
validate_timezone "$user_timezone"
|
||||
TIMEZONE="$user_timezone"
|
||||
TIMEZONE=$(validate_timezone "$user_timezone")
|
||||
else
|
||||
TIMEZONE=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "America/New_York")
|
||||
fi
|
||||
@@ -210,42 +211,6 @@ EOF
|
||||
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() {
|
||||
local PORT=$1
|
||||
local SERVICE=$2
|
||||
@@ -315,42 +280,6 @@ EOF
|
||||
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
|
||||
# --------------------------------------------
|
||||
@@ -642,22 +571,6 @@ EOF
|
||||
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
|
||||
# --------------------------------------------
|
||||
@@ -665,7 +578,13 @@ EOF
|
||||
log "🚀 Starting deployment..."
|
||||
|
||||
# Ensure we're in the correct directory
|
||||
local HOMELAB_DIR="$HOME/hops"
|
||||
# When running with sudo, use the original user's home directory
|
||||
local HOMELAB_DIR
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
HOMELAB_DIR="$(eval echo ~$SUDO_USER)/hops"
|
||||
else
|
||||
HOMELAB_DIR="$HOME/hops"
|
||||
fi
|
||||
if [[ ! -d "$HOMELAB_DIR" ]]; then
|
||||
error_exit_with_rollback "Homelab directory not found: $HOMELAB_DIR"
|
||||
fi
|
||||
@@ -907,8 +826,8 @@ EOF
|
||||
fi
|
||||
|
||||
# Allow service ports based on selection
|
||||
if [[ -f "$SCRIPT_DIR/hops_service_definitions.sh" ]]; then
|
||||
source "$SCRIPT_DIR/hops_service_definitions.sh"
|
||||
if [[ -f "$SCRIPT_DIR/services" ]]; then
|
||||
source "$SCRIPT_DIR/services"
|
||||
|
||||
for svc in "${SERVICES[@]}"; do
|
||||
local ports=$(get_service_ports "$svc")
|
||||
@@ -1009,7 +928,7 @@ EOF
|
||||
local main_port=$(echo $ports | cut -d' ' -f1)
|
||||
if [[ -n "$main_port" ]]; then
|
||||
echo " • $svc: http://$(get_primary_ip):$main_port"
|
||||
((service_count++))
|
||||
service_count=$((service_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
+3
-2
@@ -2,7 +2,6 @@
|
||||
|
||||
# HOPS - Common Utility Functions
|
||||
# Shared functions for logging, error handling, and UI
|
||||
# Version: 3.2.0
|
||||
|
||||
# Prevent multiple sourcing
|
||||
if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
||||
@@ -10,6 +9,8 @@ if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
||||
fi
|
||||
readonly HOPS_COMMON_LOADED=1
|
||||
|
||||
readonly SCRIPT_VERSION="1.0.1"
|
||||
|
||||
# Color codes for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
@@ -101,7 +102,7 @@ show_hops_header() {
|
||||
local subtitle="$2"
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
version="3.2.0"
|
||||
version="1.0.0"
|
||||
fi
|
||||
|
||||
clear
|
||||
|
||||
+3
-3
@@ -2,11 +2,11 @@
|
||||
|
||||
# HOPS - Docker Service Management
|
||||
# Functions for Docker service management and monitoring
|
||||
# Version: 3.1.0-beta
|
||||
# Version: 1.0.0
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$LIB_DIR/common.sh"
|
||||
|
||||
# Service definitions with pinned versions
|
||||
declare -A HOPS_SERVICES=(
|
||||
|
||||
@@ -1,747 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS - Privilege Management System
|
||||
# Split operations into privileged and non-privileged components
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# Operations that require root privileges
|
||||
PRIVILEGED_OPERATIONS=(
|
||||
"install_docker"
|
||||
"configure_firewall"
|
||||
"create_system_directories"
|
||||
"install_packages"
|
||||
"configure_systemd"
|
||||
"setup_secrets_directory"
|
||||
"modify_system_files"
|
||||
)
|
||||
|
||||
# Operations that can run as regular user
|
||||
NON_PRIVILEGED_OPERATIONS=(
|
||||
"generate_docker_compose"
|
||||
"pull_docker_images"
|
||||
"start_containers"
|
||||
"stop_containers"
|
||||
"view_logs"
|
||||
"check_service_status"
|
||||
"validate_configuration"
|
||||
"backup_user_data"
|
||||
)
|
||||
|
||||
# Check if operation requires privileges
|
||||
requires_privileges() {
|
||||
local operation="$1"
|
||||
|
||||
for priv_op in "${PRIVILEGED_OPERATIONS[@]}"; do
|
||||
if [[ "$operation" == "$priv_op" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if operation can run as regular user
|
||||
can_run_as_user() {
|
||||
local operation="$1"
|
||||
|
||||
for user_op in "${NON_PRIVILEGED_OPERATIONS[@]}"; do
|
||||
if [[ "$operation" == "$user_op" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Get current user information
|
||||
get_current_user_info() {
|
||||
local -A user_info
|
||||
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
user_info["username"]="$SUDO_USER"
|
||||
user_info["uid"]=$(id -u "$SUDO_USER")
|
||||
user_info["gid"]=$(id -g "$SUDO_USER")
|
||||
user_info["home"]=$(eval echo "~$SUDO_USER")
|
||||
user_info["is_sudo"]="true"
|
||||
else
|
||||
user_info["username"]="$USER"
|
||||
user_info["uid"]=$(id -u)
|
||||
user_info["gid"]=$(id -g)
|
||||
user_info["home"]="$HOME"
|
||||
user_info["is_sudo"]="false"
|
||||
fi
|
||||
|
||||
# Return as key=value pairs
|
||||
for key in "${!user_info[@]}"; do
|
||||
echo "${key}=${user_info[$key]}"
|
||||
done
|
||||
}
|
||||
|
||||
# Drop privileges to regular user
|
||||
drop_privileges() {
|
||||
local command="$1"
|
||||
shift
|
||||
local args=("$@")
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
debug "Already running as non-root user"
|
||||
exec "$command" "${args[@]}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [[ -z "$SUDO_USER" ]]; then
|
||||
error_exit "Cannot drop privileges: SUDO_USER not set"
|
||||
fi
|
||||
|
||||
local user_info
|
||||
user_info=$(get_current_user_info)
|
||||
|
||||
local uid=$(echo "$user_info" | grep "uid=" | cut -d= -f2)
|
||||
local gid=$(echo "$user_info" | grep "gid=" | cut -d= -f2)
|
||||
local home=$(echo "$user_info" | grep "home=" | cut -d= -f2)
|
||||
|
||||
debug "Dropping privileges to user: $SUDO_USER (uid=$uid, gid=$gid)"
|
||||
|
||||
# Set environment variables for the user
|
||||
local env_vars=(
|
||||
"HOME=$home"
|
||||
"USER=$SUDO_USER"
|
||||
"LOGNAME=$SUDO_USER"
|
||||
"PATH=/usr/local/bin:/usr/bin:/bin"
|
||||
)
|
||||
|
||||
# Execute command as user
|
||||
sudo -u "$SUDO_USER" env "${env_vars[@]}" "$command" "${args[@]}"
|
||||
}
|
||||
|
||||
# Run operation with appropriate privileges
|
||||
run_with_privileges() {
|
||||
local operation="$1"
|
||||
local command="$2"
|
||||
shift 2
|
||||
local args=("$@")
|
||||
|
||||
if requires_privileges "$operation"; then
|
||||
debug "Operation '$operation' requires root privileges"
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error_exit "Operation '$operation' requires root privileges. Please run with sudo."
|
||||
fi
|
||||
|
||||
# Run as root
|
||||
exec "$command" "${args[@]}"
|
||||
elif can_run_as_user "$operation"; then
|
||||
debug "Operation '$operation' can run as regular user"
|
||||
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
# Drop privileges
|
||||
drop_privileges "$command" "${args[@]}"
|
||||
else
|
||||
# Run as current user
|
||||
exec "$command" "${args[@]}"
|
||||
fi
|
||||
else
|
||||
error_exit "Unknown operation: $operation"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create privileged setup script
|
||||
create_privileged_setup() {
|
||||
local setup_script="$1"
|
||||
|
||||
cat > "$setup_script" << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS Privileged Setup Script
|
||||
# This script handles operations that require root privileges
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/system.sh"
|
||||
source "$SCRIPT_DIR/lib/security.sh"
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "privileged-setup"
|
||||
|
||||
# Check root privileges
|
||||
check_root
|
||||
|
||||
# Install Docker if not present
|
||||
install_docker() {
|
||||
info "🐳 Installing Docker..."
|
||||
|
||||
if command_exists docker; then
|
||||
success "Docker already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Update package index
|
||||
apt-get update
|
||||
|
||||
# Install prerequisites
|
||||
apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
|
||||
# Add Docker GPG key
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
# Add Docker repository
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Update package index with Docker packages
|
||||
apt-get update
|
||||
|
||||
# Install Docker
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# Start and enable Docker service
|
||||
systemctl start docker
|
||||
systemctl enable docker
|
||||
|
||||
success "Docker installed successfully"
|
||||
}
|
||||
|
||||
# Configure firewall
|
||||
configure_firewall() {
|
||||
info "🔥 Configuring firewall..."
|
||||
|
||||
# Install UFW if not present
|
||||
if ! command_exists ufw; then
|
||||
apt-get update
|
||||
apt-get install -y ufw
|
||||
fi
|
||||
|
||||
# Reset firewall to defaults
|
||||
ufw --force reset
|
||||
|
||||
# Set default policies
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
|
||||
# Allow SSH (prevent lockout)
|
||||
ufw allow ssh
|
||||
|
||||
# Allow HTTP and HTTPS
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
|
||||
# Enable firewall
|
||||
ufw --force enable
|
||||
|
||||
success "Firewall configured successfully"
|
||||
}
|
||||
|
||||
# Create system directories
|
||||
create_system_directories() {
|
||||
info "📁 Creating system directories..."
|
||||
|
||||
local directories=(
|
||||
"/opt/appdata"
|
||||
"/mnt/media"
|
||||
"/mnt/media/movies"
|
||||
"/mnt/media/tv"
|
||||
"/mnt/media/music"
|
||||
"/mnt/media/downloads"
|
||||
"/var/log/hops"
|
||||
)
|
||||
|
||||
for dir in "${directories[@]}"; do
|
||||
if mkdir -p "$dir"; then
|
||||
success "Created directory: $dir"
|
||||
else
|
||||
error_exit "Failed to create directory: $dir"
|
||||
fi
|
||||
done
|
||||
|
||||
# Set ownership to the user who ran sudo
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
local user_info
|
||||
user_info=$(get_user_info)
|
||||
|
||||
local uid=$(echo "$user_info" | grep "uid=" | cut -d= -f2)
|
||||
local gid=$(echo "$user_info" | grep "gid=" | cut -d= -f2)
|
||||
|
||||
chown -R "$uid:$gid" /opt/appdata /mnt/media
|
||||
success "Set ownership of directories to $SUDO_USER"
|
||||
fi
|
||||
}
|
||||
|
||||
# Add user to docker group
|
||||
add_user_to_docker_group() {
|
||||
if [[ -z "$SUDO_USER" ]]; then
|
||||
warning "No SUDO_USER set, skipping docker group addition"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "👥 Adding user to docker group..."
|
||||
|
||||
if usermod -aG docker "$SUDO_USER"; then
|
||||
success "User $SUDO_USER added to docker group"
|
||||
warning "User must log out and back in for group changes to take effect"
|
||||
else
|
||||
error_exit "Failed to add user to docker group"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install required packages
|
||||
install_packages() {
|
||||
info "📦 Installing required packages..."
|
||||
|
||||
apt-get update
|
||||
|
||||
local packages=(
|
||||
"curl"
|
||||
"wget"
|
||||
"git"
|
||||
"jq"
|
||||
"htop"
|
||||
"tree"
|
||||
"unzip"
|
||||
"gnupg"
|
||||
"software-properties-common"
|
||||
"apt-transport-https"
|
||||
"ca-certificates"
|
||||
"lsb-release"
|
||||
)
|
||||
|
||||
for package in "${packages[@]}"; do
|
||||
if apt-get install -y "$package"; then
|
||||
success "Installed package: $package"
|
||||
else
|
||||
warning "Failed to install package: $package"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Setup secrets directory
|
||||
setup_secrets_directory() {
|
||||
info "🔐 Setting up secrets directory..."
|
||||
|
||||
local secrets_dir="/etc/hops/secrets"
|
||||
|
||||
if mkdir -p "$secrets_dir"; then
|
||||
chmod 700 "$secrets_dir"
|
||||
success "Secrets directory created: $secrets_dir"
|
||||
else
|
||||
error_exit "Failed to create secrets directory"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure system settings
|
||||
configure_system() {
|
||||
info "⚙️ Configuring system settings..."
|
||||
|
||||
# Set timezone if not already set
|
||||
if [[ -n "$TZ" ]]; then
|
||||
timedatectl set-timezone "$TZ" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Enable IP forwarding for Docker
|
||||
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
||||
sysctl -p /etc/sysctl.conf
|
||||
|
||||
success "System configuration completed"
|
||||
}
|
||||
|
||||
# Main privileged setup
|
||||
main() {
|
||||
info "🚀 Starting privileged setup..."
|
||||
|
||||
# System checks
|
||||
detect_os
|
||||
check_system_requirements
|
||||
|
||||
# Install packages
|
||||
install_packages
|
||||
|
||||
# Install Docker
|
||||
install_docker
|
||||
|
||||
# Configure firewall
|
||||
configure_firewall
|
||||
|
||||
# Create directories
|
||||
create_system_directories
|
||||
|
||||
# Add user to docker group
|
||||
add_user_to_docker_group
|
||||
|
||||
# Setup secrets
|
||||
setup_secrets_directory
|
||||
|
||||
# Configure system
|
||||
configure_system
|
||||
|
||||
success "Privileged setup completed successfully"
|
||||
success "Please log out and back in for group changes to take effect"
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x "$setup_script"
|
||||
success "Privileged setup script created: $setup_script"
|
||||
}
|
||||
|
||||
# Create non-privileged user script
|
||||
create_user_script() {
|
||||
local user_script="$1"
|
||||
|
||||
cat > "$user_script" << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS User Script
|
||||
# This script handles operations that can run as regular user
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/docker.sh"
|
||||
source "$SCRIPT_DIR/lib/validation.sh"
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "user-operations"
|
||||
|
||||
# Check if user is in docker group
|
||||
check_docker_access() {
|
||||
if ! groups "\$USER" | grep -q docker; then
|
||||
error_exit "User not in docker group. Please run the privileged setup first and log out/in."
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
error_exit "Cannot access Docker daemon. Please ensure Docker is running."
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate Docker Compose configuration
|
||||
generate_docker_compose() {
|
||||
local services=("$@")
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
info "📝 Generating Docker Compose configuration..."
|
||||
|
||||
# Create homelab directory
|
||||
mkdir -p "$HOME/hops"
|
||||
|
||||
# Generate compose file header
|
||||
cat > "$compose_file" << EOF
|
||||
services:
|
||||
EOF
|
||||
|
||||
# Generate service definitions
|
||||
for service in "${services[@]}"; do
|
||||
if "$SCRIPT_DIR/services-improved" generate "$service" >> "$compose_file"; then
|
||||
success "Added service: $service"
|
||||
else
|
||||
error_exit "Failed to generate service definition for: $service"
|
||||
fi
|
||||
done
|
||||
|
||||
# Add networks section
|
||||
cat >> "$compose_file" << EOF
|
||||
|
||||
networks:
|
||||
homelab:
|
||||
driver: bridge
|
||||
traefik:
|
||||
driver: bridge
|
||||
database:
|
||||
driver: bridge
|
||||
EOF
|
||||
|
||||
success "Docker Compose configuration generated: $compose_file"
|
||||
}
|
||||
|
||||
# Deploy services
|
||||
deploy_services() {
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error_exit "Docker Compose file not found: $compose_file"
|
||||
fi
|
||||
|
||||
info "🚀 Deploying services..."
|
||||
|
||||
cd "$HOME/hops"
|
||||
|
||||
# Pull images
|
||||
if docker compose pull; then
|
||||
success "Docker images pulled successfully"
|
||||
else
|
||||
error_exit "Failed to pull Docker images"
|
||||
fi
|
||||
|
||||
# Start services
|
||||
if docker compose up -d; then
|
||||
success "Services deployed successfully"
|
||||
else
|
||||
error_exit "Failed to deploy services"
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop services
|
||||
stop_services() {
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error_exit "Docker Compose file not found: $compose_file"
|
||||
fi
|
||||
|
||||
info "🛑 Stopping services..."
|
||||
|
||||
cd "$HOME/hops"
|
||||
|
||||
if docker compose down; then
|
||||
success "Services stopped successfully"
|
||||
else
|
||||
error_exit "Failed to stop services"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show service status
|
||||
show_service_status() {
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error_exit "Docker Compose file not found: $compose_file"
|
||||
fi
|
||||
|
||||
info "📊 Service status:"
|
||||
|
||||
cd "$HOME/hops"
|
||||
docker compose ps
|
||||
}
|
||||
|
||||
# Main user operations
|
||||
main() {
|
||||
local action="$1"
|
||||
shift
|
||||
|
||||
# Check Docker access
|
||||
check_docker_access
|
||||
|
||||
case "$action" in
|
||||
"generate")
|
||||
if [[ $# -eq 0 ]]; then
|
||||
error_exit "Usage: $0 generate <service1> [service2] ..."
|
||||
fi
|
||||
generate_docker_compose "$@"
|
||||
;;
|
||||
|
||||
"deploy")
|
||||
deploy_services
|
||||
;;
|
||||
|
||||
"stop")
|
||||
stop_services
|
||||
;;
|
||||
|
||||
"status")
|
||||
show_service_status
|
||||
;;
|
||||
|
||||
"logs")
|
||||
if [[ $# -eq 0 ]]; then
|
||||
error_exit "Usage: $0 logs <service_name>"
|
||||
fi
|
||||
cd "$HOME/hops"
|
||||
docker compose logs -f "$1"
|
||||
;;
|
||||
|
||||
*)
|
||||
error_exit "Unknown action: $action"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x "$user_script"
|
||||
success "User script created: $user_script"
|
||||
|
||||
# Create installation wrapper
|
||||
create_installation_wrapper() {
|
||||
local wrapper_script="$1"
|
||||
|
||||
cat > "$wrapper_script" << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS Installation Wrapper
|
||||
# Orchestrates privileged and non-privileged installation steps
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "installation-wrapper"
|
||||
|
||||
# Show header
|
||||
show_hops_header "3.1.0" "Installation Wrapper"
|
||||
|
||||
# Check if we're running as root
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
if [[ -z "$SUDO_USER" ]]; then
|
||||
error_exit "Please run with sudo, not as root directly"
|
||||
fi
|
||||
else
|
||||
error_exit "This script must be run with sudo"
|
||||
fi
|
||||
|
||||
# Phase 1: Privileged setup
|
||||
info "📋 Phase 1: Privileged setup (requires root)"
|
||||
if "$SCRIPT_DIR/privileged-setup"; then
|
||||
success "Privileged setup completed"
|
||||
else
|
||||
error_exit "Privileged setup failed"
|
||||
fi
|
||||
|
||||
# Phase 2: User setup
|
||||
info "📋 Phase 2: User setup (running as $SUDO_USER)"
|
||||
|
||||
# Drop privileges and run user setup
|
||||
sudo -u "$SUDO_USER" bash << 'USERSCRIPT'
|
||||
cd "$HOME"
|
||||
echo "Running as user: $(whoami)"
|
||||
|
||||
# Interactive service selection
|
||||
echo "Select services to install:"
|
||||
echo "1) Media Server Stack (Jellyfin, Sonarr, Radarr, Prowlarr)"
|
||||
echo "2) Download Client Stack (qBittorrent, Transmission)"
|
||||
echo "3) Monitoring Stack (Portainer, Uptime Kuma)"
|
||||
echo "4) Custom selection"
|
||||
|
||||
read -p "Enter your choice (1-4): " choice
|
||||
|
||||
case "$choice" in
|
||||
1)
|
||||
services=("jellyfin" "sonarr" "radarr" "prowlarr")
|
||||
;;
|
||||
2)
|
||||
services=("qbittorrent" "transmission")
|
||||
;;
|
||||
3)
|
||||
services=("portainer" "uptime-kuma")
|
||||
;;
|
||||
4)
|
||||
echo "Available services:"
|
||||
"$SCRIPT_DIR/services-improved" list
|
||||
read -p "Enter service names (space-separated): " -a services
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Generate and deploy
|
||||
if "$SCRIPT_DIR/user-operations" generate "${services[@]}"; then
|
||||
echo "Configuration generated successfully"
|
||||
|
||||
if "$SCRIPT_DIR/user-operations" deploy; then
|
||||
echo "Services deployed successfully"
|
||||
else
|
||||
echo "Deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Configuration generation failed"
|
||||
exit 1
|
||||
fi
|
||||
USERSCRIPT
|
||||
|
||||
success "Installation completed successfully"
|
||||
success "Services are now running. Check status with: ./user-operations status"
|
||||
EOF
|
||||
|
||||
chmod +x "$wrapper_script"
|
||||
success "Installation wrapper created: $wrapper_script"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
local action="$1"
|
||||
shift
|
||||
|
||||
case "$action" in
|
||||
"create-setup")
|
||||
create_privileged_setup "$1"
|
||||
;;
|
||||
|
||||
"create-user")
|
||||
create_user_script "$1"
|
||||
;;
|
||||
|
||||
"create-wrapper")
|
||||
create_installation_wrapper "$1"
|
||||
;;
|
||||
|
||||
"create-all")
|
||||
create_privileged_setup "privileged-setup"
|
||||
create_user_script "user-operations"
|
||||
create_installation_wrapper "setup"
|
||||
;;
|
||||
|
||||
"run")
|
||||
local operation="$1"
|
||||
local command="$2"
|
||||
shift 2
|
||||
run_with_privileges "$operation" "$command" "$@"
|
||||
;;
|
||||
|
||||
"help"|"--help"|"-h")
|
||||
cat <<EOF
|
||||
HOPS Privilege Management System
|
||||
|
||||
Usage: $0 <action> [options]
|
||||
|
||||
Actions:
|
||||
create-setup <file> Create privileged setup script
|
||||
create-user <file> Create non-privileged user script
|
||||
create-wrapper <file> Create installation wrapper
|
||||
create-all Create all scripts
|
||||
run <op> <cmd> [args] Run operation with appropriate privileges
|
||||
help Show this help message
|
||||
|
||||
Examples:
|
||||
$0 create-all
|
||||
$0 run install_docker /usr/bin/apt-get install docker-ce
|
||||
$0 run generate_docker_compose ./compose-gen.sh
|
||||
|
||||
EOF
|
||||
;;
|
||||
|
||||
*)
|
||||
error_exit "Unknown action: $action. Use 'help' for usage information."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# If script is run directly (not sourced)
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
setup_logging "privileges"
|
||||
main "$@"
|
||||
fi
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
# HOPS - Secret Management System
|
||||
# Secure encryption and management of sensitive configuration data
|
||||
# Version: 3.1.0-beta
|
||||
# Version: 1.0.0
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -190,7 +190,7 @@ create_encrypted_environment() {
|
||||
cat > "$temp_env_file" << EOF
|
||||
# HOPS Environment Configuration
|
||||
# Generated on: $(date)
|
||||
# Version: 3.1.0-beta
|
||||
# Version: 1.0.0
|
||||
|
||||
# Core Configuration
|
||||
PUID=$puid
|
||||
|
||||
+14
-4
@@ -2,17 +2,27 @@
|
||||
|
||||
# HOPS - Security Functions
|
||||
# Password generation, validation, and security utilities
|
||||
# Version: 3.2.0
|
||||
# Version: 1.0.0
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$LIB_DIR/common.sh"
|
||||
|
||||
# Password validation
|
||||
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
|
||||
|
||||
# Check minimum length
|
||||
if [[ ${#password} -lt $min_length ]]; then
|
||||
debug "Password too short: ${#password} < $min_length"
|
||||
|
||||
+76
-13
@@ -2,7 +2,7 @@
|
||||
|
||||
# HOPS - System Validation Functions
|
||||
# Functions for system checks, OS detection, and requirements validation
|
||||
# Version: 3.2.0
|
||||
# Version: 1.0.0
|
||||
|
||||
# Source common functions
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -602,6 +602,7 @@ get_primary_ip() {
|
||||
|
||||
# Remove existing Docker installation on Linux
|
||||
remove_docker_linux() {
|
||||
echo "DEBUG: remove_docker_linux() function called"
|
||||
info "🗑️ Removing existing Docker installation..."
|
||||
|
||||
# Stop Docker service if running
|
||||
@@ -622,6 +623,23 @@ remove_docker_linux() {
|
||||
systemctl disable docker
|
||||
fi
|
||||
|
||||
# Remove Docker repository and GPG keys FIRST to prevent apt errors
|
||||
info "🗑️ Removing Docker repository and keys..."
|
||||
rm -f /etc/apt/sources.list.d/docker.list
|
||||
rm -f /etc/apt/sources.list.d/*docker*
|
||||
rm -f /etc/apt/keyrings/docker.gpg
|
||||
rm -f /usr/share/keyrings/docker*
|
||||
rm -f /etc/apt/keyrings/docker*
|
||||
|
||||
# Also check for and remove any repositories that might contain Linux Mint codenames
|
||||
if grep -r "download.docker.com.*xia\|download.docker.com.*vera\|download.docker.com.*vanessa" /etc/apt/sources.list.d/ 2>/dev/null; then
|
||||
info "🗑️ Found Docker repositories with Linux Mint codenames, removing..."
|
||||
grep -l "download.docker.com.*xia\|download.docker.com.*vera\|download.docker.com.*vanessa" /etc/apt/sources.list.d/* 2>/dev/null | xargs rm -f
|
||||
fi
|
||||
|
||||
# Clean apt cache to remove any cached repository data
|
||||
apt-get clean
|
||||
|
||||
# Remove Docker packages
|
||||
info "🗑️ Removing Docker packages..."
|
||||
apt-get remove -y docker docker-engine docker.io containerd runc docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 2>/dev/null || true
|
||||
@@ -657,16 +675,7 @@ remove_docker_linux() {
|
||||
groupdel docker 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Remove Docker repository
|
||||
if [[ -f "/etc/apt/sources.list.d/docker.list" ]]; then
|
||||
info "🗑️ Removing Docker repository..."
|
||||
rm -f "/etc/apt/sources.list.d/docker.list"
|
||||
fi
|
||||
|
||||
# Remove Docker GPG key
|
||||
if [[ -f "/etc/apt/keyrings/docker.gpg" ]]; then
|
||||
rm -f "/etc/apt/keyrings/docker.gpg"
|
||||
fi
|
||||
# Repository and GPG keys already removed above
|
||||
|
||||
# Remove any remaining Docker processes
|
||||
pkill -f docker 2>/dev/null || true
|
||||
@@ -811,6 +820,7 @@ check_existing_docker_macos() {
|
||||
|
||||
# Install Docker for the current platform
|
||||
install_docker() {
|
||||
echo "DEBUG: install_docker() function called from lib/system.sh"
|
||||
info "🐳 Installing Docker..."
|
||||
|
||||
case "$OS_NAME_LOWER" in
|
||||
@@ -1030,8 +1040,10 @@ install_docker() {
|
||||
fi
|
||||
;;
|
||||
"ubuntu"|"debian"|"linuxmint"|"mint")
|
||||
echo "DEBUG: Linux/Ubuntu/Mint Docker installation path"
|
||||
# Check for existing Docker installation
|
||||
if command_exists docker; then
|
||||
echo "DEBUG: Existing Docker installation detected"
|
||||
local docker_version=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
|
||||
|
||||
warning "Existing Docker installation detected:"
|
||||
@@ -1069,10 +1081,13 @@ install_docker() {
|
||||
fi
|
||||
|
||||
success "Existing Docker installation is compatible"
|
||||
echo "DEBUG: Returning early - skipping Linux Mint repository fix"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "DEBUG: No existing Docker found, proceeding with fresh installation"
|
||||
|
||||
# Check for and handle conflicting files
|
||||
local conflicting_files=()
|
||||
local potential_conflicts=(
|
||||
@@ -1119,9 +1134,57 @@ install_docker() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install fresh Docker using the official script
|
||||
# Install fresh Docker using manual repository setup
|
||||
info "📦 Installing Docker Engine..."
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Install required packages
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl gnupg lsb-release
|
||||
|
||||
# Add Docker GPG key
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
# Add Docker repository with proper Ubuntu codename detection for Linux Mint
|
||||
local ubuntu_codename
|
||||
echo "DEBUG: Configuring Docker repository"
|
||||
echo "DEBUG: Detected OS: $(lsb_release -is)"
|
||||
if [[ "$(lsb_release -is)" == "Linuxmint" ]]; then
|
||||
echo "DEBUG: Linux Mint detected, checking for UBUNTU_CODENAME"
|
||||
# Linux Mint provides UBUNTU_CODENAME in /etc/os-release
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
ubuntu_codename=$(grep '^UBUNTU_CODENAME=' /etc/os-release | cut -d= -f2)
|
||||
echo "DEBUG: Found UBUNTU_CODENAME=$ubuntu_codename in /etc/os-release"
|
||||
fi
|
||||
|
||||
# Fallback to version mapping if UBUNTU_CODENAME not found
|
||||
if [[ -z "$ubuntu_codename" ]]; then
|
||||
case "$(lsb_release -rs)" in
|
||||
"22"|"22.1"|"22.2"|"22.3")
|
||||
ubuntu_codename="noble" # Ubuntu 24.04
|
||||
;;
|
||||
"21"|"21.1"|"21.2"|"21.3")
|
||||
ubuntu_codename="jammy" # Ubuntu 22.04
|
||||
;;
|
||||
"20"|"20.1"|"20.2"|"20.3")
|
||||
ubuntu_codename="focal" # Ubuntu 20.04
|
||||
;;
|
||||
*)
|
||||
ubuntu_codename="noble" # Default to latest LTS
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
ubuntu_codename=$(lsb_release -cs)
|
||||
fi
|
||||
|
||||
info "Using Ubuntu codename: $ubuntu_codename for Docker repository"
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $ubuntu_codename stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Update package index with Docker packages
|
||||
apt-get update
|
||||
|
||||
# Install Docker
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# Add user to docker group if we're running with sudo
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
|
||||
+12
-18
@@ -2,11 +2,11 @@
|
||||
|
||||
# HOPS - Input Validation and Sanitization Functions
|
||||
# Comprehensive input validation and sanitization utilities
|
||||
# Version: 3.1.0-beta
|
||||
# Version: 1.0.0
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$LIB_DIR/common.sh"
|
||||
|
||||
# Validate and sanitize directory path
|
||||
validate_directory_path() {
|
||||
@@ -60,24 +60,18 @@ validate_directory_path() {
|
||||
echo "$path"
|
||||
}
|
||||
|
||||
# Validate timezone
|
||||
# Validate timezone -- returns the timezone string, defaulting to America/New_York if invalid
|
||||
validate_timezone() {
|
||||
local timezone="$1"
|
||||
|
||||
if [[ -z "$timezone" ]]; then
|
||||
error_exit "Timezone cannot be empty"
|
||||
local default="America/New_York"
|
||||
|
||||
if [[ -z "$timezone" ]] || \
|
||||
[[ ! "$timezone" =~ ^[A-Za-z_]+(/[A-Za-z_]+)*$ ]] || \
|
||||
! timedatectl list-timezones 2>/dev/null | grep -qx "$timezone"; then
|
||||
warning "Timezone '${timezone}' invalid, defaulting to '${default}'"
|
||||
timezone="$default"
|
||||
fi
|
||||
|
||||
# Basic format validation
|
||||
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
|
||||
|
||||
|
||||
echo "$timezone"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS Privileged Setup Script
|
||||
# This script handles operations that require root privileges
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/system.sh"
|
||||
source "$SCRIPT_DIR/lib/security.sh"
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "privileged-setup"
|
||||
|
||||
# Check root privileges
|
||||
check_root
|
||||
|
||||
# Install Docker if not present
|
||||
install_docker() {
|
||||
info "🐳 Installing Docker..."
|
||||
|
||||
if command_exists docker; then
|
||||
success "Docker already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Update package index
|
||||
apt-get update
|
||||
|
||||
# Install prerequisites
|
||||
apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
|
||||
# Add Docker GPG key
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
# Add Docker repository
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Update package index with Docker packages
|
||||
apt-get update
|
||||
|
||||
# Install Docker
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# Start and enable Docker service
|
||||
systemctl start docker
|
||||
systemctl enable docker
|
||||
|
||||
success "Docker installed successfully"
|
||||
}
|
||||
|
||||
# Configure firewall
|
||||
configure_firewall() {
|
||||
info "🔥 Configuring firewall..."
|
||||
|
||||
# Install UFW if not present
|
||||
if ! command_exists ufw; then
|
||||
apt-get update
|
||||
apt-get install -y ufw
|
||||
fi
|
||||
|
||||
# Reset firewall to defaults
|
||||
ufw --force reset
|
||||
|
||||
# Set default policies
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
|
||||
# Allow SSH (prevent lockout)
|
||||
ufw allow ssh
|
||||
|
||||
# Allow HTTP and HTTPS
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
|
||||
# Enable firewall
|
||||
ufw --force enable
|
||||
|
||||
success "Firewall configured successfully"
|
||||
}
|
||||
|
||||
# Create system directories
|
||||
create_system_directories() {
|
||||
info "📁 Creating system directories..."
|
||||
|
||||
local directories=(
|
||||
"/opt/appdata"
|
||||
"/mnt/media"
|
||||
"/mnt/media/movies"
|
||||
"/mnt/media/tv"
|
||||
"/mnt/media/music"
|
||||
"/mnt/media/downloads"
|
||||
"/var/log/hops"
|
||||
)
|
||||
|
||||
for dir in "${directories[@]}"; do
|
||||
if mkdir -p "$dir"; then
|
||||
success "Created directory: $dir"
|
||||
else
|
||||
error_exit "Failed to create directory: $dir"
|
||||
fi
|
||||
done
|
||||
|
||||
# Set ownership to the user who ran sudo
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
local user_info
|
||||
user_info=$(get_user_info)
|
||||
|
||||
local uid=$(echo "$user_info" | grep "uid=" | cut -d= -f2)
|
||||
local gid=$(echo "$user_info" | grep "gid=" | cut -d= -f2)
|
||||
|
||||
chown -R "$uid:$gid" /opt/appdata /mnt/media
|
||||
success "Set ownership of directories to $SUDO_USER"
|
||||
fi
|
||||
}
|
||||
|
||||
# Add user to docker group
|
||||
add_user_to_docker_group() {
|
||||
if [[ -z "$SUDO_USER" ]]; then
|
||||
warning "No SUDO_USER set, skipping docker group addition"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "👥 Adding user to docker group..."
|
||||
|
||||
if usermod -aG docker "$SUDO_USER"; then
|
||||
success "User $SUDO_USER added to docker group"
|
||||
warning "User must log out and back in for group changes to take effect"
|
||||
else
|
||||
error_exit "Failed to add user to docker group"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install required packages
|
||||
install_packages() {
|
||||
info "📦 Installing required packages..."
|
||||
|
||||
apt-get update
|
||||
|
||||
local packages=(
|
||||
"curl"
|
||||
"wget"
|
||||
"git"
|
||||
"jq"
|
||||
"htop"
|
||||
"tree"
|
||||
"unzip"
|
||||
"gnupg"
|
||||
"software-properties-common"
|
||||
"apt-transport-https"
|
||||
"ca-certificates"
|
||||
"lsb-release"
|
||||
)
|
||||
|
||||
for package in "${packages[@]}"; do
|
||||
if apt-get install -y "$package"; then
|
||||
success "Installed package: $package"
|
||||
else
|
||||
warning "Failed to install package: $package"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Setup secrets directory
|
||||
setup_secrets_directory() {
|
||||
info "🔐 Setting up secrets directory..."
|
||||
|
||||
local secrets_dir="/etc/hops/secrets"
|
||||
|
||||
if mkdir -p "$secrets_dir"; then
|
||||
chmod 700 "$secrets_dir"
|
||||
success "Secrets directory created: $secrets_dir"
|
||||
else
|
||||
error_exit "Failed to create secrets directory"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure system settings
|
||||
configure_system() {
|
||||
info "⚙️ Configuring system settings..."
|
||||
|
||||
# Set timezone if not already set
|
||||
if [[ -n "$TZ" ]]; then
|
||||
timedatectl set-timezone "$TZ" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Enable IP forwarding for Docker
|
||||
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
||||
sysctl -p /etc/sysctl.conf
|
||||
|
||||
success "System configuration completed"
|
||||
}
|
||||
|
||||
# Main privileged setup
|
||||
main() {
|
||||
info "🚀 Starting privileged setup..."
|
||||
|
||||
# System checks
|
||||
detect_os
|
||||
check_system_requirements
|
||||
|
||||
# Install packages
|
||||
install_packages
|
||||
|
||||
# Install Docker
|
||||
install_docker
|
||||
|
||||
# Configure firewall
|
||||
configure_firewall
|
||||
|
||||
# Create directories
|
||||
create_system_directories
|
||||
|
||||
# Add user to docker group
|
||||
add_user_to_docker_group
|
||||
|
||||
# Setup secrets
|
||||
setup_secrets_directory
|
||||
|
||||
# Configure system
|
||||
configure_system
|
||||
|
||||
success "Privileged setup completed successfully"
|
||||
success "Please log out and back in for group changes to take effect"
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# HOPS Service Definitions
|
||||
# Contains all Docker Compose service configurations
|
||||
# Version: 3.2.0
|
||||
# Version: 1.0.0
|
||||
|
||||
# This script provides functions to generate Docker Compose service definitions
|
||||
# Usage: Source this script and call generate_service_definition <service_name>
|
||||
@@ -24,24 +24,18 @@ EOF
|
||||
# Get timezone mount path for current platform
|
||||
get_timezone_mount() {
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
# macOS doesn't need timezone mount, use TZ environment variable
|
||||
echo ""
|
||||
else
|
||||
# Linux timezone mount
|
||||
echo "$(get_timezone_mount)"
|
||||
echo " - /etc/localtime:/etc/localtime:ro"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get GPU device access for current platform
|
||||
get_gpu_devices() {
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
# macOS doesn't support GPU passthrough to Docker containers
|
||||
echo ""
|
||||
else
|
||||
# Linux GPU device access
|
||||
cat <<EOF
|
||||
$(get_gpu_devices)
|
||||
EOF
|
||||
printf " devices:\n - /dev/dri:/dev/dri\n"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1349,7 +1343,7 @@ list_available_services() {
|
||||
# Usage information
|
||||
show_usage() {
|
||||
cat <<EOF
|
||||
HOPS Service Definitions Script v3.2.0
|
||||
HOPS Service Definitions Script v1.0.0
|
||||
|
||||
Usage:
|
||||
source services
|
||||
|
||||
@@ -1,572 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS Service Definitions - Improved Version
|
||||
# Contains all Docker Compose service configurations with error handling and pinned versions
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
# Exit on any error
|
||||
set -e
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
echo "ERROR: Common library not found. Please ensure lib/common.sh exists." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$SCRIPT_DIR/lib/security.sh" ]]; then
|
||||
source "$SCRIPT_DIR/lib/security.sh"
|
||||
else
|
||||
echo "ERROR: Security library not found. Please ensure lib/security.sh exists." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Service definitions with pinned versions (from docker.sh)
|
||||
declare -A SERVICE_IMAGES=(
|
||||
# Media Management (*arr Stack)
|
||||
["sonarr"]="lscr.io/linuxserver/sonarr:4.0.10"
|
||||
["radarr"]="lscr.io/linuxserver/radarr:5.8.3"
|
||||
["lidarr"]="lscr.io/linuxserver/lidarr:2.5.3"
|
||||
["readarr"]="lscr.io/linuxserver/readarr:0.3.32-develop"
|
||||
["bazarr"]="lscr.io/linuxserver/bazarr:1.4.3"
|
||||
["prowlarr"]="lscr.io/linuxserver/prowlarr:1.24.3"
|
||||
["tdarr"]="ghcr.io/haveagitgat/tdarr:2.26.01"
|
||||
|
||||
# Download Clients
|
||||
["qbittorrent"]="lscr.io/linuxserver/qbittorrent:4.6.7"
|
||||
["transmission"]="lscr.io/linuxserver/transmission:4.0.6"
|
||||
["nzbget"]="lscr.io/linuxserver/nzbget:24.3"
|
||||
["sabnzbd"]="lscr.io/linuxserver/sabnzbd:4.3.3"
|
||||
|
||||
# Media Servers
|
||||
["jellyfin"]="lscr.io/linuxserver/jellyfin:10.9.11"
|
||||
["plex"]="lscr.io/linuxserver/plex:1.40.5"
|
||||
["emby"]="lscr.io/linuxserver/emby:4.8.8"
|
||||
["jellystat"]="cyfershepard/jellystat:1.1.0"
|
||||
|
||||
# Request Management
|
||||
["overseerr"]="lscr.io/linuxserver/overseerr:1.33.2"
|
||||
["jellyseerr"]="fallenbagel/jellyseerr:1.9.2"
|
||||
["ombi"]="lscr.io/linuxserver/ombi:4.43.5"
|
||||
|
||||
# Reverse Proxy & Security
|
||||
["traefik"]="traefik:v3.1.6"
|
||||
["nginx-proxy-manager"]="jc21/nginx-proxy-manager:2.11.3"
|
||||
["authelia"]="authelia/authelia:4.38.16"
|
||||
|
||||
# Monitoring & Management
|
||||
["portainer"]="portainer/portainer-ce:2.21.4"
|
||||
["uptime-kuma"]="louislam/uptime-kuma:1.23.15"
|
||||
["watchtower"]="containrrr/watchtower:1.7.1"
|
||||
)
|
||||
|
||||
# Service port mapping
|
||||
declare -A SERVICE_PORTS=(
|
||||
["sonarr"]="8989"
|
||||
["radarr"]="7878"
|
||||
["lidarr"]="8686"
|
||||
["readarr"]="8787"
|
||||
["bazarr"]="6767"
|
||||
["prowlarr"]="9696"
|
||||
["tdarr"]="8265"
|
||||
["qbittorrent"]="8082"
|
||||
["transmission"]="9091"
|
||||
["nzbget"]="6789"
|
||||
["sabnzbd"]="8080"
|
||||
["jellyfin"]="8096"
|
||||
["plex"]="32400"
|
||||
["emby"]="8096"
|
||||
["jellystat"]="3000"
|
||||
["overseerr"]="5055"
|
||||
["jellyseerr"]="5056"
|
||||
["ombi"]="3579"
|
||||
["traefik"]="8080"
|
||||
["nginx-proxy-manager"]="81"
|
||||
["authelia"]="9091"
|
||||
["portainer"]="9000"
|
||||
["uptime-kuma"]="3001"
|
||||
["watchtower"]="8080"
|
||||
)
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "service-definitions"
|
||||
|
||||
# Validate service name
|
||||
validate_service_name_internal() {
|
||||
local service_name="$1"
|
||||
|
||||
if [[ -z "$service_name" ]]; then
|
||||
error_exit "Service name is required"
|
||||
fi
|
||||
|
||||
if ! validate_service_name "$service_name"; then
|
||||
error_exit "Invalid service name: $service_name"
|
||||
fi
|
||||
|
||||
if [[ -z "${SERVICE_IMAGES[$service_name]}" ]]; then
|
||||
error_exit "Unknown service: $service_name"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get service image with validation
|
||||
get_service_image() {
|
||||
local service_name="$1"
|
||||
validate_service_name_internal "$service_name"
|
||||
echo "${SERVICE_IMAGES[$service_name]}"
|
||||
}
|
||||
|
||||
# Get service port with validation
|
||||
get_service_port() {
|
||||
local service_name="$1"
|
||||
validate_service_name_internal "$service_name"
|
||||
echo "${SERVICE_PORTS[$service_name]}"
|
||||
}
|
||||
|
||||
# Common environment variables for LinuxServer containers
|
||||
get_linuxserver_env() {
|
||||
cat <<EOF
|
||||
- PUID=\${PUID:-1000}
|
||||
- PGID=\${PGID:-1000}
|
||||
- TZ=\${TZ:-UTC}
|
||||
- UMASK=002
|
||||
EOF
|
||||
}
|
||||
|
||||
# Common restart policy
|
||||
get_restart_policy() {
|
||||
echo " restart: unless-stopped"
|
||||
}
|
||||
|
||||
# Common network configuration
|
||||
get_homelab_network() {
|
||||
cat <<EOF
|
||||
networks:
|
||||
- homelab
|
||||
EOF
|
||||
}
|
||||
|
||||
# Common healthcheck for web services
|
||||
get_web_healthcheck() {
|
||||
local service_name="$1"
|
||||
local path="${2:-/}"
|
||||
|
||||
if [[ -z "$service_name" ]]; then
|
||||
error_exit "Service name required for healthcheck"
|
||||
fi
|
||||
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
cat <<EOF
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:$port$path || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
EOF
|
||||
}
|
||||
|
||||
# Common volume configuration
|
||||
get_common_volumes() {
|
||||
local service_name="$1"
|
||||
|
||||
if [[ -z "$service_name" ]]; then
|
||||
error_exit "Service name required for volumes"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
volumes:
|
||||
- \${CONFIG_ROOT:-/opt/appdata}/$service_name:/config
|
||||
- \${DATA_ROOT:-/mnt/media}:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
EOF
|
||||
}
|
||||
|
||||
# Common Traefik labels
|
||||
get_traefik_labels() {
|
||||
local service_name="$1"
|
||||
|
||||
if [[ -z "$service_name" ]]; then
|
||||
error_exit "Service name required for Traefik labels"
|
||||
fi
|
||||
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
cat <<EOF
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.$service_name.rule=Host(\`$service_name.\${DOMAIN:-localhost}\`)"
|
||||
- "traefik.http.routers.$service_name.entrypoints=websecure"
|
||||
- "traefik.http.routers.$service_name.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.$service_name.loadbalancer.server.port=$port"
|
||||
EOF
|
||||
}
|
||||
|
||||
# --------------------------------------------
|
||||
# SERVICE GENERATORS
|
||||
# --------------------------------------------
|
||||
|
||||
# Generate *arr service (template for Sonarr, Radarr, etc.)
|
||||
generate_arr_service() {
|
||||
local service_name="$1"
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
local image=$(get_service_image "$service_name")
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
cat <<EOF
|
||||
$service_name:
|
||||
image: $image
|
||||
container_name: $service_name
|
||||
$(get_restart_policy)
|
||||
ports:
|
||||
- "$port:$port"
|
||||
environment:
|
||||
$(get_linuxserver_env)
|
||||
$(get_common_volumes "$service_name")
|
||||
$(get_web_healthcheck "$service_name")
|
||||
$(get_homelab_network)
|
||||
$(get_traefik_labels "$service_name")
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Generate media server service
|
||||
generate_media_server() {
|
||||
local service_name="$1"
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
local image=$(get_service_image "$service_name")
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
# Special handling for Plex
|
||||
local additional_config=""
|
||||
if [[ "$service_name" == "plex" ]]; then
|
||||
additional_config=" - PLEX_CLAIM=\${PLEX_CLAIM:-}
|
||||
- ADVERTISE_IP=\${ADVERTISE_IP:-}"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
$service_name:
|
||||
image: $image
|
||||
container_name: $service_name
|
||||
$(get_restart_policy)
|
||||
ports:
|
||||
- "$port:$port"
|
||||
environment:
|
||||
$(get_linuxserver_env)
|
||||
$additional_config
|
||||
$(get_common_volumes "$service_name")
|
||||
$(get_web_healthcheck "$service_name")
|
||||
$(get_homelab_network)
|
||||
$(get_traefik_labels "$service_name")
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Generate download client service
|
||||
generate_download_client() {
|
||||
local service_name="$1"
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
local image=$(get_service_image "$service_name")
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
# Special handling for qBittorrent
|
||||
local additional_config=""
|
||||
if [[ "$service_name" == "qbittorrent" ]]; then
|
||||
additional_config=" - WEBUI_PORT=$port"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
$service_name:
|
||||
image: $image
|
||||
container_name: $service_name
|
||||
$(get_restart_policy)
|
||||
ports:
|
||||
- "$port:$port"
|
||||
environment:
|
||||
$(get_linuxserver_env)
|
||||
$additional_config
|
||||
$(get_common_volumes "$service_name")
|
||||
$(get_web_healthcheck "$service_name")
|
||||
$(get_homelab_network)
|
||||
$(get_traefik_labels "$service_name")
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Generate monitoring service
|
||||
generate_monitoring_service() {
|
||||
local service_name="$1"
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
local image=$(get_service_image "$service_name")
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
# Special handling for Portainer
|
||||
local additional_config=""
|
||||
local volume_config=""
|
||||
|
||||
if [[ "$service_name" == "portainer" ]]; then
|
||||
volume_config=" volumes:
|
||||
- \${CONFIG_ROOT:-/opt/appdata}/$service_name:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /etc/localtime:/etc/localtime:ro"
|
||||
elif [[ "$service_name" == "watchtower" ]]; then
|
||||
volume_config=" volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /etc/localtime:/etc/localtime:ro"
|
||||
additional_config=" - WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_SCHEDULE=0 0 4 * * *"
|
||||
else
|
||||
volume_config=$(get_common_volumes "$service_name")
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
$service_name:
|
||||
image: $image
|
||||
container_name: $service_name
|
||||
$(get_restart_policy)
|
||||
ports:
|
||||
- "$port:$port"
|
||||
environment:
|
||||
$(get_linuxserver_env)
|
||||
$additional_config
|
||||
$volume_config
|
||||
$(get_web_healthcheck "$service_name")
|
||||
$(get_homelab_network)
|
||||
$(get_traefik_labels "$service_name")
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Generate Traefik service
|
||||
generate_traefik() {
|
||||
local service_name="traefik"
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
local image=$(get_service_image "$service_name")
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
cat <<EOF
|
||||
traefik:
|
||||
image: $image
|
||||
container_name: traefik
|
||||
$(get_restart_policy)
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "$port:$port"
|
||||
environment:
|
||||
- CF_API_EMAIL=\${CF_API_EMAIL:-}
|
||||
- CF_API_KEY=\${CF_API_KEY:-}
|
||||
command:
|
||||
- "--api.dashboard=true"
|
||||
- "--api.insecure=true"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email=\${ACME_EMAIL:-admin@localhost}"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
volumes:
|
||||
- \${CONFIG_ROOT:-/opt/appdata}/traefik:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
networks:
|
||||
- homelab
|
||||
- traefik
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(\`traefik.\${DOMAIN:-localhost}\`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.traefik.loadbalancer.server.port=$port"
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main service generator function
|
||||
generate_service_definition() {
|
||||
local service_name="$1"
|
||||
|
||||
if [[ -z "$service_name" ]]; then
|
||||
error_exit "Service name is required"
|
||||
fi
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
debug "Generating service definition for: $service_name"
|
||||
|
||||
case "$service_name" in
|
||||
# Media Management (*arr Stack)
|
||||
"sonarr"|"radarr"|"lidarr"|"readarr"|"bazarr"|"prowlarr")
|
||||
generate_arr_service "$service_name"
|
||||
;;
|
||||
|
||||
# Download Clients
|
||||
"qbittorrent"|"transmission"|"nzbget"|"sabnzbd")
|
||||
generate_download_client "$service_name"
|
||||
;;
|
||||
|
||||
# Media Servers
|
||||
"jellyfin"|"plex"|"emby"|"jellystat")
|
||||
generate_media_server "$service_name"
|
||||
;;
|
||||
|
||||
# Request Management
|
||||
"overseerr"|"jellyseerr"|"ombi")
|
||||
generate_arr_service "$service_name" # They use similar patterns
|
||||
;;
|
||||
|
||||
# Reverse Proxy & Security
|
||||
"traefik")
|
||||
generate_traefik
|
||||
;;
|
||||
|
||||
# Monitoring & Management
|
||||
"portainer"|"uptime-kuma"|"watchtower")
|
||||
generate_monitoring_service "$service_name"
|
||||
;;
|
||||
|
||||
# Other services
|
||||
"nginx-proxy-manager"|"authelia"|"tdarr")
|
||||
generate_arr_service "$service_name" # Use generic template
|
||||
;;
|
||||
|
||||
*)
|
||||
error_exit "Unknown service: $service_name"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Generate multiple services
|
||||
generate_multiple_services() {
|
||||
local services=("$@")
|
||||
|
||||
if [[ ${#services[@]} -eq 0 ]]; then
|
||||
error_exit "No services specified"
|
||||
fi
|
||||
|
||||
debug "Generating definitions for ${#services[@]} services"
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
generate_service_definition "$service"
|
||||
done
|
||||
}
|
||||
|
||||
# List all available services
|
||||
list_available_services() {
|
||||
echo "Available services:"
|
||||
echo
|
||||
|
||||
local categories=(
|
||||
"Media Management:sonarr,radarr,lidarr,readarr,bazarr,prowlarr,tdarr"
|
||||
"Download Clients:qbittorrent,transmission,nzbget,sabnzbd"
|
||||
"Media Servers:jellyfin,plex,emby,jellystat"
|
||||
"Request Management:overseerr,jellyseerr,ombi"
|
||||
"Reverse Proxy & Security:traefik,nginx-proxy-manager,authelia"
|
||||
"Monitoring & Management:portainer,uptime-kuma,watchtower"
|
||||
)
|
||||
|
||||
for category in "${categories[@]}"; do
|
||||
local category_name="${category%%:*}"
|
||||
local services="${category#*:}"
|
||||
|
||||
echo -e "${CYAN}${category_name}:${NC}"
|
||||
IFS=',' read -ra service_list <<< "$services"
|
||||
|
||||
for service in "${service_list[@]}"; do
|
||||
local port=$(get_service_port "$service")
|
||||
local image=$(get_service_image "$service")
|
||||
printf " %-20s Port: %-6s Image: %s\n" "$service" "$port" "$image"
|
||||
done
|
||||
echo
|
||||
done
|
||||
}
|
||||
|
||||
# Validate service configuration
|
||||
validate_service_config() {
|
||||
local service_name="$1"
|
||||
|
||||
validate_service_name_internal "$service_name"
|
||||
|
||||
local image=$(get_service_image "$service_name")
|
||||
local port=$(get_service_port "$service_name")
|
||||
|
||||
# Check if image exists (basic validation)
|
||||
if [[ -z "$image" ]]; then
|
||||
error_exit "No image defined for service: $service_name"
|
||||
fi
|
||||
|
||||
# Check if port is valid
|
||||
if [[ ! "$port" =~ ^[0-9]+$ ]] || [[ "$port" -lt 1 ]] || [[ "$port" -gt 65535 ]]; then
|
||||
error_exit "Invalid port for service $service_name: $port"
|
||||
fi
|
||||
|
||||
success "Service configuration valid: $service_name"
|
||||
}
|
||||
|
||||
# Main function for command line usage
|
||||
main() {
|
||||
local action="$1"
|
||||
shift
|
||||
|
||||
case "$action" in
|
||||
"generate")
|
||||
if [[ $# -eq 0 ]]; then
|
||||
error_exit "Usage: $0 generate <service_name> [service_name...]"
|
||||
fi
|
||||
generate_multiple_services "$@"
|
||||
;;
|
||||
|
||||
"list")
|
||||
list_available_services
|
||||
;;
|
||||
|
||||
"validate")
|
||||
if [[ $# -eq 0 ]]; then
|
||||
error_exit "Usage: $0 validate <service_name>"
|
||||
fi
|
||||
validate_service_config "$1"
|
||||
;;
|
||||
|
||||
"help"|"--help"|"-h")
|
||||
cat <<EOF
|
||||
HOPS Service Definitions - Improved Version
|
||||
|
||||
Usage: $0 <action> [options]
|
||||
|
||||
Actions:
|
||||
generate <service>... Generate service definitions
|
||||
list List all available services
|
||||
validate <service> Validate service configuration
|
||||
help Show this help message
|
||||
|
||||
Examples:
|
||||
$0 generate sonarr radarr jellyfin
|
||||
$0 list
|
||||
$0 validate traefik
|
||||
|
||||
EOF
|
||||
;;
|
||||
|
||||
*)
|
||||
error_exit "Unknown action: $action. Use 'help' for usage information."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# If script is run directly (not sourced)
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS Installation Wrapper
|
||||
# Orchestrates privileged and non-privileged installation steps
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "installation-wrapper"
|
||||
|
||||
# Show header
|
||||
show_hops_header "3.1.0-beta" "Installation Wrapper"
|
||||
|
||||
# Check if we're running as root
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
if [[ -z "$SUDO_USER" ]]; then
|
||||
error_exit "Please run with sudo, not as root directly"
|
||||
fi
|
||||
else
|
||||
error_exit "This script must be run with sudo"
|
||||
fi
|
||||
|
||||
# Phase 1: Privileged setup
|
||||
info "📋 Phase 1: Privileged setup (requires root)"
|
||||
if "$SCRIPT_DIR/privileged-setup"; then
|
||||
success "Privileged setup completed"
|
||||
else
|
||||
error_exit "Privileged setup failed"
|
||||
fi
|
||||
|
||||
# Phase 2: User setup
|
||||
info "📋 Phase 2: User setup (running as $SUDO_USER)"
|
||||
|
||||
# Drop privileges and run user setup
|
||||
sudo -u "$SUDO_USER" bash << 'USERSCRIPT'
|
||||
cd "$HOME"
|
||||
echo "Running as user: $(whoami)"
|
||||
|
||||
# Interactive service selection
|
||||
echo "Select services to install:"
|
||||
echo "1) Media Server Stack (Jellyfin, Sonarr, Radarr, Prowlarr)"
|
||||
echo "2) Download Client Stack (qBittorrent, Transmission)"
|
||||
echo "3) Monitoring Stack (Portainer, Uptime Kuma)"
|
||||
echo "4) Custom selection"
|
||||
|
||||
read -p "Enter your choice (1-4): " choice
|
||||
|
||||
case "$choice" in
|
||||
1)
|
||||
services=("jellyfin" "sonarr" "radarr" "prowlarr")
|
||||
;;
|
||||
2)
|
||||
services=("qbittorrent" "transmission")
|
||||
;;
|
||||
3)
|
||||
services=("portainer" "uptime-kuma")
|
||||
;;
|
||||
4)
|
||||
echo "Available services:"
|
||||
"$SCRIPT_DIR/services-improved" list
|
||||
read -p "Enter service names (space-separated): " -a services
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Generate and deploy
|
||||
if "$SCRIPT_DIR/user-operations" generate "${services[@]}"; then
|
||||
echo "Configuration generated successfully"
|
||||
|
||||
if "$SCRIPT_DIR/user-operations" deploy; then
|
||||
echo "Services deployed successfully"
|
||||
else
|
||||
echo "Deployment failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Configuration generation failed"
|
||||
exit 1
|
||||
fi
|
||||
USERSCRIPT
|
||||
|
||||
success "Installation completed successfully"
|
||||
success "Services are now running. Check status with: ./user-operations status"
|
||||
@@ -8,29 +8,15 @@ uninstall_hops() {
|
||||
set +e
|
||||
|
||||
# Script version for consistency
|
||||
local SCRIPT_VERSION="3.1.0-beta"
|
||||
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
|
||||
# --------------------------------------------
|
||||
local LOG_DIR="/var/log/hops"
|
||||
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"
|
||||
}
|
||||
setup_logging "hops-uninstall"
|
||||
|
||||
# --------------------------------------------
|
||||
# HEADER
|
||||
@@ -126,17 +112,17 @@ EOF
|
||||
find_homelab_directory() {
|
||||
local POSSIBLE_DIRS=(
|
||||
"$HOME/hops"
|
||||
"/home/*/hops"
|
||||
"/opt/hops"
|
||||
"/srv/hops"
|
||||
)
|
||||
|
||||
# Try to find from running user's home first
|
||||
while IFS= read -r d; do POSSIBLE_DIRS+=("$d"); done < <(compgen -G "/home/*/hops" 2>/dev/null)
|
||||
|
||||
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[@]}")
|
||||
fi
|
||||
|
||||
|
||||
HOMELAB_DIR=""
|
||||
for dir in "${POSSIBLE_DIRS[@]}"; do
|
||||
if [[ -f "$dir/docker-compose.yml" ]]; then
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# HOPS User Script
|
||||
# This script handles operations that can run as regular user
|
||||
# Version: 3.1.0-beta
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/docker.sh"
|
||||
source "$SCRIPT_DIR/lib/validation.sh"
|
||||
|
||||
# Initialize logging
|
||||
setup_logging "user-operations"
|
||||
|
||||
# Check if user is in docker group
|
||||
check_docker_access() {
|
||||
if ! groups "$USER" | grep -q docker; then
|
||||
error_exit "User not in docker group. Please run the privileged setup first and log out/in."
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
error_exit "Cannot access Docker daemon. Please ensure Docker is running."
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate Docker Compose configuration
|
||||
generate_docker_compose() {
|
||||
local services=("$@")
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
info "📝 Generating Docker Compose configuration..."
|
||||
|
||||
# Create homelab directory
|
||||
mkdir -p "$HOME/hops"
|
||||
|
||||
# Generate compose file header
|
||||
cat > "$compose_file" << EOF
|
||||
services:
|
||||
EOF
|
||||
|
||||
# Generate service definitions
|
||||
for service in "${services[@]}"; do
|
||||
if "$SCRIPT_DIR/services-improved" generate "$service" >> "$compose_file"; then
|
||||
success "Added service: $service"
|
||||
else
|
||||
error_exit "Failed to generate service definition for: $service"
|
||||
fi
|
||||
done
|
||||
|
||||
# Add networks section
|
||||
cat >> "$compose_file" << EOF
|
||||
|
||||
networks:
|
||||
homelab:
|
||||
driver: bridge
|
||||
traefik:
|
||||
driver: bridge
|
||||
database:
|
||||
driver: bridge
|
||||
EOF
|
||||
|
||||
success "Docker Compose configuration generated: $compose_file"
|
||||
}
|
||||
|
||||
# Deploy services
|
||||
deploy_services() {
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error_exit "Docker Compose file not found: $compose_file"
|
||||
fi
|
||||
|
||||
info "🚀 Deploying services..."
|
||||
|
||||
cd "$HOME/hops"
|
||||
|
||||
# Pull images
|
||||
if docker compose pull; then
|
||||
success "Docker images pulled successfully"
|
||||
else
|
||||
error_exit "Failed to pull Docker images"
|
||||
fi
|
||||
|
||||
# Start services
|
||||
if docker compose up -d; then
|
||||
success "Services deployed successfully"
|
||||
else
|
||||
error_exit "Failed to deploy services"
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop services
|
||||
stop_services() {
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error_exit "Docker Compose file not found: $compose_file"
|
||||
fi
|
||||
|
||||
info "🛑 Stopping services..."
|
||||
|
||||
cd "$HOME/hops"
|
||||
|
||||
if docker compose down; then
|
||||
success "Services stopped successfully"
|
||||
else
|
||||
error_exit "Failed to stop services"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show service status
|
||||
show_service_status() {
|
||||
local compose_file="$HOME/hops/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
error_exit "Docker Compose file not found: $compose_file"
|
||||
fi
|
||||
|
||||
info "📊 Service status:"
|
||||
|
||||
cd "$HOME/hops"
|
||||
docker compose ps
|
||||
}
|
||||
|
||||
# Main user operations
|
||||
main() {
|
||||
local action="$1"
|
||||
shift
|
||||
|
||||
# Check Docker access
|
||||
check_docker_access
|
||||
|
||||
case "$action" in
|
||||
"generate")
|
||||
if [[ $# -eq 0 ]]; then
|
||||
error_exit "Usage: $0 generate <service1> [service2] ..."
|
||||
fi
|
||||
generate_docker_compose "$@"
|
||||
;;
|
||||
|
||||
"deploy")
|
||||
deploy_services
|
||||
;;
|
||||
|
||||
"stop")
|
||||
stop_services
|
||||
;;
|
||||
|
||||
"status")
|
||||
show_service_status
|
||||
;;
|
||||
|
||||
"logs")
|
||||
if [[ $# -eq 0 ]]; then
|
||||
error_exit "Usage: $0 logs <service_name>"
|
||||
fi
|
||||
cd "$HOME/hops"
|
||||
docker compose logs -f "$1"
|
||||
;;
|
||||
|
||||
*)
|
||||
error_exit "Unknown action: $action"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user