cd30d45fbf
### New Features - Added Caddy reverse proxy as a service option - Proper Docker container configuration with ports 80, 443, 2019 - Health check monitoring via Caddy admin API - Volume mounts for Caddyfile, site content, and data persistence - Integration with existing service selection and categorization ### Configuration Scope - HOPS provides: Container setup, volume mounts, networking, health checks - User provides: Caddyfile configuration, routing rules, SSL settings - Clear documentation about configuration responsibilities - Example Caddyfile provided in README ### Documentation Updates - Updated README.md with Caddy service listing and configuration guide - Updated CLAUDE.md with Caddy in supported services - Added comprehensive configuration scope documentation - Updated version references to 3.2.0 ### Technical Implementation - Added generate_caddy() function to services file - Integrated Caddy into service selection switch - Added port mapping for conflict detection (80, 443, 2019) - Categorized under proxy & security services - Added to available services listing This addition provides users with another reverse proxy option while maintaining HOPS' philosophy of providing infrastructure while allowing users to maintain control over their specific configuration needs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1371 lines
37 KiB
Bash
Executable File
1371 lines
37 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# HOPS Service Definitions
|
|
# Contains all Docker Compose service configurations
|
|
# Version: 3.2.0
|
|
|
|
# This script provides functions to generate Docker Compose service definitions
|
|
# Usage: Source this script and call generate_service_definition <service_name>
|
|
|
|
# --------------------------------------------
|
|
# COMMON CONFIGURATIONS
|
|
# --------------------------------------------
|
|
|
|
# Common environment variables for LinuxServer containers
|
|
get_linuxserver_env() {
|
|
cat <<EOF
|
|
- PUID=\${PUID}
|
|
- PGID=\${PGID}
|
|
- TZ=\${TZ}
|
|
- UMASK=002
|
|
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)"
|
|
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
|
|
fi
|
|
}
|
|
|
|
# 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 port=$1
|
|
local path=${2:-"/"}
|
|
cat <<EOF
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -f http://localhost:$port$path || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 90s
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# MEDIA MANAGEMENT SERVICES (*ARR STACK)
|
|
# --------------------------------------------
|
|
|
|
generate_sonarr() {
|
|
cat <<EOF
|
|
sonarr:
|
|
image: lscr.io/linuxserver/sonarr:latest
|
|
container_name: sonarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8989:8989"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/sonarr:/config
|
|
- \${DATA_ROOT}:/data
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 8989)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.sonarr.rule=Host(\`sonarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.sonarr.entrypoints=websecure"
|
|
- "traefik.http.routers.sonarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_radarr() {
|
|
cat <<EOF
|
|
radarr:
|
|
image: lscr.io/linuxserver/radarr:latest
|
|
container_name: radarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "7878:7878"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/radarr:/config
|
|
- \${DATA_ROOT}:/data
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 7878)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.radarr.rule=Host(\`radarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.radarr.entrypoints=websecure"
|
|
- "traefik.http.routers.radarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_lidarr() {
|
|
cat <<EOF
|
|
lidarr:
|
|
image: lscr.io/linuxserver/lidarr:latest
|
|
container_name: lidarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8686:8686"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/lidarr:/config
|
|
- \${DATA_ROOT}:/data
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 8686)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.lidarr.rule=Host(\`lidarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.lidarr.entrypoints=websecure"
|
|
- "traefik.http.routers.lidarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.lidarr.loadbalancer.server.port=8686"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_readarr() {
|
|
cat <<EOF
|
|
readarr:
|
|
image: lscr.io/linuxserver/readarr:develop
|
|
container_name: readarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8787:8787"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/readarr:/config
|
|
- \${DATA_ROOT}:/data
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 8787)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.readarr.rule=Host(\`readarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.readarr.entrypoints=websecure"
|
|
- "traefik.http.routers.readarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.readarr.loadbalancer.server.port=8787"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_bazarr() {
|
|
cat <<EOF
|
|
bazarr:
|
|
image: lscr.io/linuxserver/bazarr:latest
|
|
container_name: bazarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "6767:6767"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/bazarr:/config
|
|
- \${DATA_ROOT}:/data
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 6767)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.bazarr.rule=Host(\`bazarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.bazarr.entrypoints=websecure"
|
|
- "traefik.http.routers.bazarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.bazarr.loadbalancer.server.port=6767"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_prowlarr() {
|
|
cat <<EOF
|
|
prowlarr:
|
|
image: lscr.io/linuxserver/prowlarr:latest
|
|
container_name: prowlarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "9696:9696"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/prowlarr:/config
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 9696)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.prowlarr.rule=Host(\`prowlarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.prowlarr.entrypoints=websecure"
|
|
- "traefik.http.routers.prowlarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_tdarr() {
|
|
cat <<EOF
|
|
tdarr:
|
|
image: ghcr.io/haveagitgat/tdarr:latest
|
|
container_name: tdarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8265:8265"
|
|
- "8266:8266"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
- serverIP=0.0.0.0
|
|
- serverPort=8266
|
|
- webUIPort=8265
|
|
- internalNode=true
|
|
- nodeName=MainNode
|
|
volumes:
|
|
- \${CONFIG_ROOT}/tdarr/server:/app/server
|
|
- \${CONFIG_ROOT}/tdarr/configs:/app/configs
|
|
- \${CONFIG_ROOT}/tdarr/logs:/app/logs
|
|
- \${DATA_ROOT}/media:/media
|
|
- \${DATA_ROOT}/downloads/tdarr:/temp
|
|
$(get_gpu_devices)
|
|
$(get_web_healthcheck 8265)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.tdarr.rule=Host(\`tdarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.tdarr.entrypoints=websecure"
|
|
- "traefik.http.routers.tdarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.tdarr.loadbalancer.server.port=8265"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_huntarr() {
|
|
cat <<EOF
|
|
huntarr:
|
|
image: ghcr.io/plexguide/huntarr:latest
|
|
container_name: huntarr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "9705:9705"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
- BASE_URL=\${BASE_URL:-}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/huntarr:/config
|
|
- \${DATA_ROOT}:/data
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 9705 "/health")
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.huntarr.rule=Host(\`huntarr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.huntarr.entrypoints=websecure"
|
|
- "traefik.http.routers.huntarr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.huntarr.loadbalancer.server.port=9705"
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# DOWNLOAD CLIENTS
|
|
# --------------------------------------------
|
|
|
|
generate_qbittorrent() {
|
|
cat <<EOF
|
|
qbittorrent:
|
|
image: lscr.io/linuxserver/qbittorrent:latest
|
|
container_name: qbittorrent
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8082:8082"
|
|
- "6881:6881"
|
|
- "6881:6881/udp"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
- WEBUI_PORT=8082
|
|
volumes:
|
|
- \${CONFIG_ROOT}/qbittorrent:/config
|
|
- \${DATA_ROOT}/downloads/torrents:/data/torrents
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 8082)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.qbittorrent.rule=Host(\`qbittorrent.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.qbittorrent.entrypoints=websecure"
|
|
- "traefik.http.routers.qbittorrent.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.qbittorrent.loadbalancer.server.port=8082"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_transmission() {
|
|
cat <<EOF
|
|
transmission:
|
|
image: lscr.io/linuxserver/transmission:latest
|
|
container_name: transmission
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "9091:9091"
|
|
- "51413:51413"
|
|
- "51413:51413/udp"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
- USER=admin
|
|
- PASS=\${DEFAULT_ADMIN_PASSWORD}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/transmission:/config
|
|
- \${DATA_ROOT}/downloads/torrents:/data/torrents
|
|
- \${DATA_ROOT}/downloads/torrents/watch:/watch
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 9091 "/transmission/web/")
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.transmission.rule=Host(\`transmission.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.transmission.entrypoints=websecure"
|
|
- "traefik.http.routers.transmission.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.transmission.loadbalancer.server.port=9091"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_nzbget() {
|
|
cat <<EOF
|
|
nzbget:
|
|
image: lscr.io/linuxserver/nzbget:latest
|
|
container_name: nzbget
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "6789:6789"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/nzbget:/config
|
|
- \${DATA_ROOT}/downloads/usenet:/data/usenet
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 6789)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.nzbget.rule=Host(\`nzbget.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.nzbget.entrypoints=websecure"
|
|
- "traefik.http.routers.nzbget.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.nzbget.loadbalancer.server.port=6789"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_sabnzbd() {
|
|
cat <<EOF
|
|
sabnzbd:
|
|
image: lscr.io/linuxserver/sabnzbd:latest
|
|
container_name: sabnzbd
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8080:8080"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/sabnzbd:/config
|
|
- \${DATA_ROOT}/downloads/usenet:/data/usenet
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 8080)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.sabnzbd.rule=Host(\`sabnzbd.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.sabnzbd.entrypoints=websecure"
|
|
- "traefik.http.routers.sabnzbd.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.sabnzbd.loadbalancer.server.port=8080"
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# MEDIA SERVERS
|
|
# --------------------------------------------
|
|
|
|
generate_jellyfin() {
|
|
cat <<EOF
|
|
jellyfin:
|
|
image: jellyfin/jellyfin:latest
|
|
container_name: jellyfin
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8096:8096"
|
|
- "8920:8920" # HTTPS
|
|
- "7359:7359/udp" # Auto-discovery
|
|
- "1900:1900/udp" # DLNA
|
|
environment:
|
|
- JELLYFIN_PublishedServerUrl=http://\${DOMAIN:-localhost}:8096
|
|
volumes:
|
|
- \${CONFIG_ROOT}/jellyfin:/config
|
|
- \${CONFIG_ROOT}/jellyfin/cache:/cache
|
|
- \${DATA_ROOT}/media:/media:ro
|
|
$(get_timezone_mount)
|
|
$(get_gpu_devices)
|
|
group_add:
|
|
- "109" # render group for GPU access
|
|
$(get_web_healthcheck 8096 "/health")
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.jellyfin.rule=Host(\`jellyfin.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.jellyfin.entrypoints=websecure"
|
|
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_plex() {
|
|
cat <<EOF
|
|
plex:
|
|
image: plexinc/pms-docker:latest
|
|
container_name: plex
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "32400:32400"
|
|
- "1900:1900/udp" # DLNA
|
|
- "3005:3005" # Plex Companion
|
|
- "5353:5353/udp" # Bonjour/Avahi
|
|
- "8324:8324" # Roku via Plex Companion
|
|
- "32410:32410/udp" # GDM Network Discovery
|
|
- "32412:32412/udp" # GDM Network Discovery
|
|
- "32413:32413/udp" # GDM Network Discovery
|
|
- "32414:32414/udp" # GDM Network Discovery
|
|
- "32469:32469" # Plex DLNA Server
|
|
environment:
|
|
- PLEX_CLAIM=\${PLEX_CLAIM_TOKEN:-}
|
|
- PLEX_UID=\${PUID}
|
|
- PLEX_GID=\${PGID}
|
|
- TZ=\${TZ}
|
|
- HOSTNAME=PlexServer
|
|
- ADVERTISE_IP=http://\${DOMAIN:-localhost}:32400/
|
|
volumes:
|
|
- \${CONFIG_ROOT}/plex:/config
|
|
- \${CONFIG_ROOT}/plex/transcode:/transcode
|
|
- \${DATA_ROOT}/media:/data:ro
|
|
$(get_timezone_mount)
|
|
$(get_gpu_devices)
|
|
$(get_web_healthcheck 32400 "/identity")
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.plex.rule=Host(\`plex.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.plex.entrypoints=websecure"
|
|
- "traefik.http.routers.plex.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.plex.loadbalancer.server.port=32400"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_emby() {
|
|
cat <<EOF
|
|
emby:
|
|
image: emby/embyserver:latest
|
|
container_name: emby
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "8097:8096"
|
|
- "8920:8920" # HTTPS
|
|
environment:
|
|
- UID=\${PUID}
|
|
- GID=\${PGID}
|
|
- GIDLIST=\${PGID}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/emby:/config
|
|
- \${DATA_ROOT}/media:/data:ro
|
|
$(get_timezone_mount)
|
|
$(get_gpu_devices)
|
|
$(get_web_healthcheck 8096)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.emby.rule=Host(\`emby.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.emby.entrypoints=websecure"
|
|
- "traefik.http.routers.emby.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.emby.loadbalancer.server.port=8096"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_jellystat() {
|
|
cat <<EOF
|
|
jellystat-db:
|
|
image: postgres:15
|
|
container_name: jellystat-db
|
|
$(get_restart_policy)
|
|
environment:
|
|
- POSTGRES_DB=jfstat
|
|
- POSTGRES_USER=postgres
|
|
- POSTGRES_PASSWORD=\${DEFAULT_DB_PASSWORD}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
networks:
|
|
- database
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
jellystat:
|
|
image: cyfershepard/jellystat:latest
|
|
container_name: jellystat
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "3000:3000"
|
|
environment:
|
|
- POSTGRES_USER=postgres
|
|
- POSTGRES_PASSWORD=\${DEFAULT_DB_PASSWORD}
|
|
- POSTGRES_IP=jellystat-db
|
|
- POSTGRES_PORT=5432
|
|
- JWT_SECRET=\${DEFAULT_ADMIN_PASSWORD}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/jellystat/backup-data:/app/backend/backup-data
|
|
depends_on:
|
|
- jellystat-db
|
|
$(get_web_healthcheck 3000)
|
|
networks:
|
|
- homelab
|
|
- database
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.jellystat.rule=Host(\`jellystat.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.jellystat.entrypoints=websecure"
|
|
- "traefik.http.routers.jellystat.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.jellystat.loadbalancer.server.port=3000"
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# REQUEST MANAGEMENT
|
|
# --------------------------------------------
|
|
|
|
generate_overseerr() {
|
|
cat <<EOF
|
|
overseerr:
|
|
image: sctx/overseerr:latest
|
|
container_name: overseerr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "5055:5055"
|
|
environment:
|
|
- LOG_LEVEL=debug
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/overseerr:/app/config
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 5055)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.overseerr.rule=Host(\`overseerr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.overseerr.entrypoints=websecure"
|
|
- "traefik.http.routers.overseerr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.overseerr.loadbalancer.server.port=5055"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_jellyseerr() {
|
|
cat <<EOF
|
|
jellyseerr:
|
|
image: fallenbagel/jellyseerr:latest
|
|
container_name: jellyseerr
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "5056:5055"
|
|
environment:
|
|
- LOG_LEVEL=debug
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/jellyseerr:/app/config
|
|
$(get_timezone_mount)
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -f http://localhost:5055/ || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 120s
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.jellyseerr.rule=Host(\`jellyseerr.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.jellyseerr.entrypoints=websecure"
|
|
- "traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.jellyseerr.loadbalancer.server.port=5055"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_ombi() {
|
|
cat <<EOF
|
|
ombi:
|
|
image: lscr.io/linuxserver/ombi:latest
|
|
container_name: ombi
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "3579:3579"
|
|
environment:
|
|
$(get_linuxserver_env)
|
|
volumes:
|
|
- \${CONFIG_ROOT}/ombi:/config
|
|
$(get_timezone_mount)
|
|
$(get_web_healthcheck 3579)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.ombi.rule=Host(\`ombi.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.ombi.entrypoints=websecure"
|
|
- "traefik.http.routers.ombi.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.ombi.loadbalancer.server.port=3579"
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# REVERSE PROXY & SECURITY
|
|
# --------------------------------------------
|
|
|
|
generate_traefik() {
|
|
cat <<EOF
|
|
traefik:
|
|
image: traefik:v3.0
|
|
container_name: traefik
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "8080:8080" # Dashboard
|
|
environment:
|
|
- TRAEFIK_API_DASHBOARD=true
|
|
- TRAEFIK_API_INSECURE=true
|
|
- TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80
|
|
- TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS=:443
|
|
- TRAEFIK_PROVIDERS_DOCKER=true
|
|
- TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=false
|
|
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=\${ACME_EMAIL:-admin@localhost}
|
|
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE=/letsencrypt/acme.json
|
|
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT=web
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- \${CONFIG_ROOT}/traefik/letsencrypt:/letsencrypt
|
|
- \${CONFIG_ROOT}/traefik:/etc/traefik
|
|
networks:
|
|
- traefik
|
|
- homelab
|
|
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=8080"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_nginx-proxy-manager() {
|
|
cat <<EOF
|
|
nginx-proxy-manager:
|
|
image: jc21/nginx-proxy-manager:latest
|
|
container_name: nginx-proxy-manager
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "81:81" # Admin interface
|
|
environment:
|
|
- DB_SQLITE_FILE=/data/database.sqlite
|
|
volumes:
|
|
- \${CONFIG_ROOT}/nginx-proxy-manager/data:/data
|
|
- \${CONFIG_ROOT}/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
|
|
$(get_web_healthcheck 81)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.npm.rule=Host(\`npm.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.npm.entrypoints=websecure"
|
|
- "traefik.http.routers.npm.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.npm.loadbalancer.server.port=81"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_caddy() {
|
|
cat <<EOF
|
|
# Caddy Reverse Proxy
|
|
# NOTE: HOPS provides the container only - Caddyfile configuration is user responsibility
|
|
# Place your Caddyfile in \${CONFIG_ROOT}/caddy/Caddyfile
|
|
# Documentation: https://caddyserver.com/docs/
|
|
caddy:
|
|
image: caddy:latest
|
|
container_name: caddy
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "2019:2019" # Admin API
|
|
environment:
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/caddy/Caddyfile:/etc/caddy/Caddyfile
|
|
- \${CONFIG_ROOT}/caddy/site:/srv
|
|
- \${CONFIG_ROOT}/caddy/data:/data
|
|
- \${CONFIG_ROOT}/caddy/config:/config
|
|
$(get_web_healthcheck 2019 "/config/")
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.caddy.rule=Host(\`caddy.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.caddy.entrypoints=websecure"
|
|
- "traefik.http.routers.caddy.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.caddy.loadbalancer.server.port=2019"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_authelia() {
|
|
cat <<EOF
|
|
authelia:
|
|
image: authelia/authelia:latest
|
|
container_name: authelia
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "9091:9091"
|
|
environment:
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/authelia:/config
|
|
command: --config /config/configuration.yml
|
|
$(get_web_healthcheck 9091)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.authelia.rule=Host(\`auth.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.authelia.entrypoints=websecure"
|
|
- "traefik.http.routers.authelia.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.authelia.loadbalancer.server.port=9091"
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# MONITORING & MANAGEMENT
|
|
# --------------------------------------------
|
|
|
|
generate_portainer() {
|
|
cat <<EOF
|
|
portainer:
|
|
image: portainer/portainer-ce:latest
|
|
container_name: portainer
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "9000:9000"
|
|
- "9443:9443" # HTTPS
|
|
environment:
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- \${CONFIG_ROOT}/portainer:/data
|
|
command: --admin-password-file /data/admin_password
|
|
$(get_web_healthcheck 9000)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.portainer.rule=Host(\`portainer.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.portainer.entrypoints=websecure"
|
|
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_watchtower() {
|
|
cat <<EOF
|
|
watchtower:
|
|
image: containrrr/watchtower:latest
|
|
container_name: watchtower
|
|
$(get_restart_policy)
|
|
environment:
|
|
- TZ=\${TZ}
|
|
- WATCHTOWER_CLEANUP=true
|
|
- WATCHTOWER_SCHEDULE=0 0 4 * * * # 4 AM daily
|
|
- WATCHTOWER_NOTIFICATIONS=email
|
|
- WATCHTOWER_NOTIFICATION_EMAIL_FROM=\${WATCHTOWER_EMAIL_FROM:-watchtower@localhost}
|
|
- WATCHTOWER_NOTIFICATION_EMAIL_TO=\${WATCHTOWER_EMAIL_TO:-admin@localhost}
|
|
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER=\${WATCHTOWER_EMAIL_SERVER:-}
|
|
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=\${WATCHTOWER_EMAIL_PORT:-587}
|
|
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=\${WATCHTOWER_EMAIL_USER:-}
|
|
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=\${WATCHTOWER_EMAIL_PASSWORD:-}
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
networks:
|
|
- homelab
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_uptime-kuma() {
|
|
cat <<EOF
|
|
uptime-kuma:
|
|
image: louislam/uptime-kuma:1
|
|
container_name: uptime-kuma
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "3001:3001"
|
|
environment:
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- \${CONFIG_ROOT}/uptime-kuma:/app/data
|
|
$(get_web_healthcheck 3001)
|
|
$(get_homelab_network)
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.uptime-kuma.rule=Host(\`uptime.\${DOMAIN:-localhost}\`)"
|
|
- "traefik.http.routers.uptime-kuma.entrypoints=websecure"
|
|
- "traefik.http.routers.uptime-kuma.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.uptime-kuma.loadbalancer.server.port=3001"
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# DATABASE SERVICES
|
|
# --------------------------------------------
|
|
|
|
generate_postgres() {
|
|
cat <<EOF
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
container_name: postgres
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "5432:5432"
|
|
environment:
|
|
- POSTGRES_DB=homelab
|
|
- POSTGRES_USER=postgres
|
|
- POSTGRES_PASSWORD=\${DEFAULT_DB_PASSWORD}
|
|
- PGDATA=/var/lib/postgresql/data/pgdata
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
- \${CONFIG_ROOT}/postgres/init:/docker-entrypoint-initdb.d
|
|
networks:
|
|
- database
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_redis() {
|
|
cat <<EOF
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: redis
|
|
$(get_restart_policy)
|
|
ports:
|
|
- "6379:6379"
|
|
environment:
|
|
- TZ=\${TZ}
|
|
volumes:
|
|
- redis_data:/data
|
|
- \${CONFIG_ROOT}/redis/redis.conf:/usr/local/etc/redis/redis.conf
|
|
command: redis-server /usr/local/etc/redis/redis.conf --requirepass \${DEFAULT_DB_PASSWORD}
|
|
networks:
|
|
- database
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
EOF
|
|
}
|
|
|
|
# --------------------------------------------
|
|
# UTILITY FUNCTIONS
|
|
# --------------------------------------------
|
|
|
|
# Generate service definition based on service name
|
|
generate_service_definition() {
|
|
local service_name="$1"
|
|
|
|
case "$service_name" in
|
|
# Media Management (*arr stack)
|
|
"sonarr") generate_sonarr ;;
|
|
"radarr") generate_radarr ;;
|
|
"lidarr") generate_lidarr ;;
|
|
"readarr") generate_readarr ;;
|
|
"bazarr") generate_bazarr ;;
|
|
"prowlarr") generate_prowlarr ;;
|
|
"tdarr") generate_tdarr ;;
|
|
"huntarr") generate_huntarr ;;
|
|
|
|
# Download Clients
|
|
"qbittorrent") generate_qbittorrent ;;
|
|
"transmission") generate_transmission ;;
|
|
"nzbget") generate_nzbget ;;
|
|
"sabnzbd") generate_sabnzbd ;;
|
|
|
|
# Media Servers
|
|
"jellyfin") generate_jellyfin ;;
|
|
"plex") generate_plex ;;
|
|
"emby") generate_emby ;;
|
|
"jellystat") generate_jellystat ;;
|
|
|
|
# Request Management
|
|
"overseerr") generate_overseerr ;;
|
|
"jellyseerr") generate_jellyseerr ;;
|
|
"ombi") generate_ombi ;;
|
|
|
|
# Reverse Proxy & Security
|
|
"traefik") generate_traefik ;;
|
|
"nginx-proxy-manager") generate_nginx-proxy-manager ;;
|
|
"caddy") generate_caddy ;;
|
|
"authelia") generate_authelia ;;
|
|
|
|
# Monitoring & Management
|
|
"portainer") generate_portainer ;;
|
|
"watchtower") generate_watchtower ;;
|
|
"uptime-kuma") generate_uptime-kuma ;;
|
|
|
|
# Database Services
|
|
"postgres") generate_postgres ;;
|
|
"redis") generate_redis ;;
|
|
|
|
*)
|
|
echo "# Service '$service_name' not found"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Generate complete docker-compose.yml file
|
|
generate_complete_compose() {
|
|
local services=("$@")
|
|
local compose_file="docker-compose.yml"
|
|
|
|
# Start with the base compose structure
|
|
cat > "$compose_file" <<EOF
|
|
networks:
|
|
homelab:
|
|
driver: bridge
|
|
ipam:
|
|
config:
|
|
- subnet: \${DOCKER_SUBNET:-172.20.0.0/16}
|
|
traefik:
|
|
external: true
|
|
name: traefik
|
|
database:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
|
|
services:
|
|
EOF
|
|
|
|
# Add each selected service
|
|
for service in "${services[@]}"; do
|
|
echo " # --- $service ---" >> "$compose_file"
|
|
if generate_service_definition "$service" >> "$compose_file"; then
|
|
echo "✅ Added service: $service"
|
|
else
|
|
echo "⚠️ Failed to add service: $service"
|
|
fi
|
|
done
|
|
|
|
echo "📝 Docker Compose file generated with ${#services[@]} services"
|
|
}
|
|
|
|
# Create service-specific configuration directories and files
|
|
create_service_configs() {
|
|
local services=("$@")
|
|
local config_root="${CONFIG_ROOT:-/opt/appdata}"
|
|
|
|
for service in "${services[@]}"; do
|
|
local service_dir="$config_root/$service"
|
|
mkdir -p "$service_dir"
|
|
|
|
case "$service" in
|
|
"portainer")
|
|
# Create admin password file for Portainer
|
|
if [[ -n "${DEFAULT_ADMIN_PASSWORD}" ]] && command -v htpasswd &>/dev/null; then
|
|
echo -n "${DEFAULT_ADMIN_PASSWORD}" | htpasswd -niB admin | cut -d: -f2 > "$service_dir/admin_password"
|
|
fi
|
|
;;
|
|
"traefik")
|
|
mkdir -p "$service_dir/letsencrypt"
|
|
mkdir -p "$service_dir/dynamic"
|
|
# Create basic traefik config
|
|
cat > "$service_dir/traefik.yml" <<EOF
|
|
api:
|
|
dashboard: true
|
|
insecure: true
|
|
|
|
entryPoints:
|
|
web:
|
|
address: ":80"
|
|
http:
|
|
redirections:
|
|
entryPoint:
|
|
to: websecure
|
|
scheme: https
|
|
websecure:
|
|
address: ":443"
|
|
|
|
providers:
|
|
docker:
|
|
endpoint: "unix://$(get_docker_socket_path)"
|
|
exposedByDefault: false
|
|
file:
|
|
directory: /etc/traefik/dynamic
|
|
watch: true
|
|
|
|
certificatesResolvers:
|
|
letsencrypt:
|
|
acme:
|
|
email: \${ACME_EMAIL:-admin@localhost}
|
|
storage: /letsencrypt/acme.json
|
|
httpChallenge:
|
|
entryPoint: web
|
|
EOF
|
|
;;
|
|
"redis")
|
|
# Create basic Redis config
|
|
cat > "$service_dir/redis.conf" <<EOF
|
|
# Redis configuration for HOPS
|
|
bind 0.0.0.0
|
|
port 6379
|
|
timeout 300
|
|
keepalive 60
|
|
maxmemory 256mb
|
|
maxmemory-policy allkeys-lru
|
|
save 900 1
|
|
save 300 10
|
|
save 60 10000
|
|
EOF
|
|
;;
|
|
"postgres")
|
|
mkdir -p "$service_dir/init"
|
|
# Create initialization script for additional databases
|
|
cat > "$service_dir/init/create_databases.sql" <<EOF
|
|
-- Create additional databases for services that need them
|
|
CREATE DATABASE IF NOT EXISTS jellyfin_db;
|
|
CREATE DATABASE IF NOT EXISTS authelia_db;
|
|
EOF
|
|
;;
|
|
"authelia")
|
|
# Create basic Authelia configuration
|
|
cat > "$service_dir/configuration.yml" <<EOF
|
|
# Authelia configuration
|
|
host: 0.0.0.0
|
|
port: 9091
|
|
log_level: info
|
|
theme: dark
|
|
jwt_secret: \${DEFAULT_ADMIN_PASSWORD}
|
|
default_redirection_url: https://\${DOMAIN:-localhost}
|
|
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 9091
|
|
|
|
authentication_backend:
|
|
file:
|
|
path: /config/users_database.yml
|
|
password:
|
|
algorithm: argon2id
|
|
iterations: 1
|
|
salt_length: 16
|
|
parallelism: 8
|
|
memory: 64
|
|
|
|
access_control:
|
|
default_policy: deny
|
|
rules:
|
|
- domain: "*.localhost"
|
|
policy: one_factor
|
|
|
|
session:
|
|
name: authelia_session
|
|
secret: \${DEFAULT_ADMIN_PASSWORD}
|
|
expiration: 3600
|
|
inactivity: 300
|
|
domain: localhost
|
|
|
|
regulation:
|
|
max_retries: 3
|
|
find_time: 120
|
|
ban_time: 300
|
|
|
|
storage:
|
|
local:
|
|
path: /config/db.sqlite3
|
|
|
|
notifier:
|
|
filesystem:
|
|
filename: /config/notification.txt
|
|
EOF
|
|
|
|
# Create users database
|
|
cat > "$service_dir/users_database.yml" <<EOF
|
|
users:
|
|
admin:
|
|
displayname: "Administrator"
|
|
password: "\$argon2id\$v=19\$m=65536,t=3,p=4\$c29tZXNhbHQ\$MNzk5BtR2vUhrp6qQEjRNw" # password
|
|
email: admin@localhost
|
|
groups:
|
|
- admins
|
|
- dev
|
|
EOF
|
|
;;
|
|
esac
|
|
|
|
# Set proper ownership if running as root
|
|
if [[ $EUID -eq 0 && -n "${PUID}" && -n "${PGID}" ]]; then
|
|
chown -R "${PUID}:${PGID}" "$service_dir" 2>/dev/null || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Get service dependencies
|
|
get_service_dependencies() {
|
|
local service="$1"
|
|
|
|
case "$service" in
|
|
"jellystat")
|
|
echo "postgres"
|
|
;;
|
|
"authelia")
|
|
echo "redis"
|
|
;;
|
|
*)
|
|
# No dependencies
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Get all dependencies for a list of services
|
|
resolve_dependencies() {
|
|
local services=("$@")
|
|
local all_services=()
|
|
local processed=()
|
|
|
|
# Add services and their dependencies
|
|
for service in "${services[@]}"; do
|
|
if [[ ! " ${processed[*]} " =~ " ${service} " ]]; then
|
|
all_services+=("$service")
|
|
processed+=("$service")
|
|
|
|
# Add dependencies
|
|
local deps=$(get_service_dependencies "$service")
|
|
for dep in $deps; do
|
|
if [[ ! " ${processed[*]} " =~ " ${dep} " ]]; then
|
|
all_services+=("$dep")
|
|
processed+=("$dep")
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
|
|
echo "${all_services[@]}"
|
|
}
|
|
|
|
# Get service ports for conflict checking
|
|
get_service_ports() {
|
|
local service="$1"
|
|
|
|
case "$service" in
|
|
"sonarr") echo "8989" ;;
|
|
"radarr") echo "7878" ;;
|
|
"lidarr") echo "8686" ;;
|
|
"readarr") echo "8787" ;;
|
|
"bazarr") echo "6767" ;;
|
|
"prowlarr") echo "9696" ;;
|
|
"tdarr") echo "8265 8266" ;;
|
|
"qbittorrent") echo "8082 6881 6881/udp" ;;
|
|
"transmission") echo "9091 51413 51413/udp" ;;
|
|
"nzbget") echo "6789" ;;
|
|
"sabnzbd") echo "8080" ;;
|
|
"jellyfin") echo "8096 8920 7359/udp 1900/udp" ;;
|
|
"plex") echo "32400 1900/udp 3005 5353/udp 8324 32410/udp 32412/udp 32413/udp 32414/udp 32469" ;;
|
|
"emby") echo "8097 8920" ;;
|
|
"jellystat") echo "3000" ;;
|
|
"overseerr") echo "5055" ;;
|
|
"jellyseerr") echo "5056" ;;
|
|
"ombi") echo "3579" ;;
|
|
"traefik") echo "80 443 8080" ;;
|
|
"nginx-proxy-manager") echo "80 443 81" ;;
|
|
"caddy") echo "80 443 2019" ;;
|
|
"authelia") echo "9091" ;;
|
|
"portainer") echo "9000 9443" ;;
|
|
"uptime-kuma") echo "3001" ;;
|
|
"postgres") echo "5432" ;;
|
|
"redis") echo "6379" ;;
|
|
*) echo "" ;;
|
|
esac
|
|
}
|
|
|
|
# Print service summary
|
|
print_service_summary() {
|
|
local services=("$@")
|
|
|
|
echo "==========================="
|
|
echo "HOPS SERVICE SUMMARY"
|
|
echo "==========================="
|
|
echo "Selected services: ${#services[@]}"
|
|
echo
|
|
|
|
# Categorize services
|
|
local media_mgmt=()
|
|
local download_clients=()
|
|
local media_servers=()
|
|
local request_mgmt=()
|
|
local proxy_security=()
|
|
local monitoring=()
|
|
local databases=()
|
|
|
|
for service in "${services[@]}"; do
|
|
case "$service" in
|
|
sonarr|radarr|lidarr|readarr|bazarr|prowlarr|tdarr)
|
|
media_mgmt+=("$service") ;;
|
|
qbittorrent|transmission|nzbget|sabnzbd)
|
|
download_clients+=("$service") ;;
|
|
jellyfin|plex|emby|jellystat)
|
|
media_servers+=("$service") ;;
|
|
overseerr|jellyseerr|ombi)
|
|
request_mgmt+=("$service") ;;
|
|
traefik|nginx-proxy-manager|caddy|authelia)
|
|
proxy_security+=("$service") ;;
|
|
portainer|watchtower|uptime-kuma)
|
|
monitoring+=("$service") ;;
|
|
postgres|redis)
|
|
databases+=("$service") ;;
|
|
esac
|
|
done
|
|
|
|
[[ ${#media_mgmt[@]} -gt 0 ]] && echo "📺 Media Management: ${media_mgmt[*]}"
|
|
[[ ${#download_clients[@]} -gt 0 ]] && echo "⬇️ Download Clients: ${download_clients[*]}"
|
|
[[ ${#media_servers[@]} -gt 0 ]] && echo "🎞️ Media Servers: ${media_servers[*]}"
|
|
[[ ${#request_mgmt[@]} -gt 0 ]] && echo "🎛️ Request Management: ${request_mgmt[*]}"
|
|
[[ ${#proxy_security[@]} -gt 0 ]] && echo "🔒 Proxy & Security: ${proxy_security[*]}"
|
|
[[ ${#monitoring[@]} -gt 0 ]] && echo "📈 Monitoring: ${monitoring[*]}"
|
|
[[ ${#databases[@]} -gt 0 ]] && echo "🗄️ Databases: ${databases[*]}"
|
|
|
|
echo
|
|
}
|
|
|
|
# Main function to generate everything
|
|
generate_hops_stack() {
|
|
local services=("$@")
|
|
|
|
if [[ ${#services[@]} -eq 0 ]]; then
|
|
echo "Error: No services specified"
|
|
return 1
|
|
fi
|
|
|
|
echo "Generating HOPS stack for: ${services[*]}"
|
|
|
|
# Resolve dependencies
|
|
local all_services=($(resolve_dependencies "${services[@]}"))
|
|
|
|
echo "Services with dependencies: ${all_services[*]}"
|
|
|
|
# Print summary
|
|
print_service_summary "${all_services[@]}"
|
|
|
|
# Generate docker-compose.yml
|
|
generate_complete_compose "${all_services[@]}"
|
|
|
|
# Create service configurations
|
|
create_service_configs "${all_services[@]}"
|
|
|
|
echo "HOPS stack generation complete!"
|
|
}
|
|
|
|
# Helper function to list all available services
|
|
list_available_services() {
|
|
echo "Available HOPS services:"
|
|
echo
|
|
echo "📺 MEDIA MANAGEMENT:"
|
|
echo " sonarr radarr lidarr readarr bazarr prowlarr tdarr"
|
|
echo
|
|
echo "⬇️ DOWNLOAD CLIENTS:"
|
|
echo " qbittorrent transmission nzbget sabnzbd"
|
|
echo
|
|
echo "🎞️ MEDIA SERVERS:"
|
|
echo " jellyfin plex emby jellystat"
|
|
echo
|
|
echo "🎛️ REQUEST MANAGEMENT:"
|
|
echo " overseerr jellyseerr ombi"
|
|
echo
|
|
echo "🔒 PROXY & SECURITY:"
|
|
echo " traefik nginx-proxy-manager caddy authelia"
|
|
echo
|
|
echo "📈 MONITORING:"
|
|
echo " portainer watchtower uptime-kuma"
|
|
echo
|
|
echo "🗄️ DATABASES:"
|
|
echo " postgres redis"
|
|
}
|
|
|
|
# Usage information
|
|
show_usage() {
|
|
cat <<EOF
|
|
HOPS Service Definitions Script v3.2.0
|
|
|
|
Usage:
|
|
source services
|
|
generate_hops_stack service1 service2 service3...
|
|
|
|
Examples:
|
|
generate_hops_stack sonarr radarr jellyfin qbittorrent
|
|
generate_hops_stack plex overseerr traefik portainer
|
|
|
|
Functions:
|
|
generate_service_definition <service> - Generate single service
|
|
generate_complete_compose <services> - Generate docker-compose.yml
|
|
create_service_configs <services> - Create config directories
|
|
list_available_services - Show all available services
|
|
resolve_dependencies <services> - Add required dependencies
|
|
get_service_ports <service> - Get service port mappings
|
|
|
|
EOF
|
|
} |