Files
Containr/scripts/auto-deploy.sh
T
2026-04-10 12:02:36 +02:00

601 lines
19 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# One-Click Docker Template Auto-Deployment
# Fully automated deployment with no user interaction required
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Configuration
TEMPLATE_DIR="$(dirname "$0")/../templates"
DEPLOYMENT_BASE_DIR="$(dirname "$0")/../deployments"
LOG_FILE="/tmp/docker-auto-deploy.log"
# Auto-configuration
AUTO_GENERATE_SECRETS=true
AUTO_CREATE_DIRECTORIES=true
AUTO_PULL_IMAGES=true
AUTO_START_SERVICES=true
AUTO_OPEN_BROWSER=false
# Template configurations with auto-generated defaults
declare -A TEMPLATE_CONFIGS=(
["umami"]="port:3000,database:postgres,secrets:APP_SECRET"
["plex"]="port:32400,database:none,secrets:none"
["immich"]="port:2283,database:postgres,secrets:DATABASE_PASSWORD"
["n8n"]="port:5678,database:postgres,secrets:N8N_ENCRYPTION_KEY"
["supabase"]="port:8000,database:postgres,secrets:JWT_SECRET"
["home-assistant"]="port:8123,database:postgres,secrets:none"
["uptime-kuma"]="port:3001,database:none,secrets:none"
["grafana"]="port:3000,database:postgres,secrets:GF_SECURITY_ADMIN_PASSWORD"
["traefik"]="port:80,database:none,secrets:none"
["memos"]="port:5230,database:sqlite,secrets:none"
["meilisearch"]="port:7700,database:none,secrets:MEILI_MASTER_KEY"
["vaultwarden"]="port:8080,database:postgres,secrets:ADMIN_TOKEN"
["pihole"]="port:80,database:none,secrets:FTLCONF_webserver_api_password"
["appwrite"]="port:80,database:postgres,secrets:_APP_OPENSSL_KEY_V1"
["gitea"]="port:3000,database:postgres,secrets:SECRET_KEY"
["mastodon"]="port:3000,database:postgres,secrets:SECRET_KEY_BASE"
["jellyfin"]="port:8096,database:none,secrets:none"
["nextcloud"]="port:8080,database:postgres,secrets:NEXTCLOUD_ADMIN_PASSWORD"
["glance"]="port:8080,database:none,secrets:none"
["cloudreve"]="port:5212,database:postgres,secrets:CR_CONF_Database_Password"
)
print_header() {
echo -e "${PURPLE}========================================${NC}"
echo -e "${PURPLE}$1${NC}"
echo -e "${PURPLE}========================================${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE}$1${NC}"
}
print_step() {
echo -e "${CYAN}🔄 $1${NC}"
}
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
check_dependencies() {
print_step "Checking dependencies..."
log_message "Starting dependency check"
# Check Docker
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed"
print_info "Installing Docker automatically..."
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
print_success "Docker installed"
else
print_success "Docker is installed"
fi
# Check Docker Compose
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
print_error "Docker Compose is not installed"
print_info "Installing Docker Compose automatically..."
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
print_success "Docker Compose installed"
else
print_success "Docker Compose is installed"
fi
log_message "Dependency check completed"
}
generate_secret() {
local length=${1:-32}
openssl rand -base64 $length | tr -d "=+/" | cut -c1-$length
}
generate_password() {
local length=${1:-16}
openssl rand -base64 $length | tr -d "=+/" | cut -c1-$length
}
auto_generate_secrets() {
local template="$1"
local env_file="$2"
print_step "Auto-generating secrets for $template..."
local config="${TEMPLATE_CONFIGS[$template]}"
local secrets=$(echo "$config" | cut -d',' -f3 | cut -d':' -f2)
if [[ "$secrets" == "none" ]]; then
return
fi
# Read existing .env file
local temp_env=$(mktemp)
cp "$env_file" "$temp_env"
# Generate secrets for each required variable
case "$template" in
"umami")
sed -i "s/APP_SECRET=change_me/APP_SECRET=$(generate_secret)/g" "$temp_env"
;;
"immich")
sed -i "s/DATABASE_PASSWORD=change_me/DATABASE_PASSWORD=$(generate_password)/g" "$temp_env"
;;
"n8n")
sed -i "s/N8N_ENCRYPTION_KEY=change_me/N8N_ENCRYPTION_KEY=$(generate_secret)/g" "$temp_env"
;;
"supabase")
sed -i "s/JWT_SECRET=change_me/JWT_SECRET=$(generate_secret)/g" "$temp_env"
;;
"grafana")
sed -i "s/GF_SECURITY_ADMIN_PASSWORD=change_me/GF_SECURITY_ADMIN_PASSWORD=$(generate_password)/g" "$temp_env"
;;
"vaultwarden")
sed -i "s/ADMIN_TOKEN=change_me/ADMIN_TOKEN=$(generate_secret)/g" "$temp_env"
;;
"pihole")
sed -i "s/FTLCONF_webserver_api_password=change_me/FTLCONF_webserver_api_password=$(generate_password)/g" "$temp_env"
;;
"appwrite")
sed -i "s/_APP_OPENSSL_KEY_V1=change_me/_APP_OPENSSL_KEY_V1=$(generate_secret)/g" "$temp_env"
;;
"gitea")
sed -i "s/SECRET_KEY=change_me/SECRET_KEY=$(generate_secret)/g" "$temp_env"
;;
"mastodon")
sed -i "s/SECRET_KEY_BASE=change_me/SECRET_KEY_BASE=$(generate_secret)/g" "$temp_env"
sed -i "s/OTP_SECRET=change_me/OTP_SECRET=$(generate_secret)/g" "$temp_env"
sed -i "s/VAPID_PRIVATE_KEY=change_me/VAPID_PRIVATE_KEY=$(openssl ecparam -name prime256v1 -genkey -noout | openssl ec -outform DER | tail -c +8 | head -c 32 | base64)/g" "$temp_env"
sed -i "s/VAPID_PUBLIC_KEY=change_me/VAPID_PUBLIC_KEY=$(openssl ecparam -name prime256v1 -genkey -noout | openssl ec -outform DER -pubout -outform DER | tail -c +8 | head -c 32 | base64)/g" "$temp_env"
;;
"nextcloud")
sed -i "s/NEXTCLOUD_ADMIN_PASSWORD=change_me/NEXTCLOUD_ADMIN_PASSWORD=$(generate_password)/g" "$temp_env"
;;
"cloudreve")
sed -i "s/CR_CONF_Database_Password=change_me/CR_CONF_Database_Password=$(generate_password)/g" "$temp_env"
;;
esac
mv "$temp_env" "$env_file"
print_success "Secrets auto-generated"
log_message "Secrets generated for $template"
}
auto_create_directories() {
local template="$1"
local project_dir="$2"
print_step "Auto-creating directories for $template..."
# Create common directories
local dirs=("data" "logs" "config" "uploads")
case "$template" in
"plex")
dirs+=("movies" "tvshows" "music" "photos")
;;
"immich")
dirs+=("uploads" "library")
;;
"jellyfin")
dirs+=("media" "movies" "tvshows" "music" "photos")
;;
"nextcloud")
dirs+=("nextcloud-data" "nextcloud-config")
;;
"pihole")
dirs+=("pihole-config" "pihole-dnsmasq" "pihole-logs")
;;
"vaultwarden")
dirs+=("vaultwarden-data")
;;
"gitea")
dirs+=("gitea-data" "gitea-db")
;;
"mastodon")
dirs+=("mastodon-data" "postgres-data" "redis-data")
;;
esac
for dir in "${dirs[@]}"; do
mkdir -p "$project_dir/$dir"
print_success "Created directory: $dir"
done
log_message "Directories created for $template"
}
extract_docker_compose() {
local template_file="$1"
local project_dir="$2"
print_step "Extracting docker-compose.yml..."
# Extract docker-compose.yml from markdown file
sed -n '/```yaml/,/```/p' "$template_file" | sed '1d;$d' > "$project_dir/docker-compose.yml"
if [[ ! -s "$project_dir/docker-compose.yml" ]]; then
print_error "Failed to extract docker-compose.yml"
exit 1
fi
# Auto-fix common issues
fix_docker_compose "$project_dir/docker-compose.yml" "$template"
print_success "docker-compose.yml extracted and optimized"
log_message "docker-compose.yml extracted for $template"
}
fix_docker_compose() {
local compose_file="$1"
local template="$2"
# Fix volume paths to use auto-created directories
case "$template" in
"plex")
sed -i 's|/path/to/media|./media|g' "$compose_file"
sed -i 's|/path/to/tvseries|./tvshows|g' "$compose_file"
sed -i 's|/path/to/movies|./movies|g' "$compose_file"
;;
"immich")
sed -i 's|/path/to/local-storage|./uploads|g' "$compose_file"
;;
"jellyfin")
sed -i 's|/path/to/media|./media|g' "$compose_file"
;;
"nextcloud")
sed -i 's|/path/to/nextcloud-data|./nextcloud-data|g' "$compose_file"
;;
"vaultwarden")
sed -i 's|vaultwarden-data|./data|g' "$compose_file"
;;
esac
# Fix port conflicts by auto-assigning available ports
auto_assign_ports "$compose_file" "$template"
}
auto_assign_ports() {
local compose_file="$1"
local template="$2"
local config="${TEMPLATE_CONFIGS[$template]}"
local default_port=$(echo "$config" | cut -d',' -f1 | cut -d':' -f2)
# Check if default port is available
if ! netstat -tuln 2>/dev/null | grep -q ":$default_port "; then
print_info "Port $default_port is available"
return
fi
# Find next available port
local new_port=$default_port
while netstat -tuln 2>/dev/null | grep -q ":$new_port "; do
new_port=$((new_port + 1))
done
if [[ $new_port -ne $default_port ]]; then
print_warning "Port $default_port is busy, using $new_port"
sed -i "s/- \"$default_port:/- \"$new_port:/g" "$compose_file"
sed -i "s/- $default_port:/- $new_port:/g" "$compose_file"
log_message "Port changed from $default_port to $new_port for $template"
fi
}
create_env_file() {
local template="$1"
local project_dir="$2"
local env_file="$project_dir/.env"
print_step "Creating .env file..."
# Extract environment variables from template
local env_vars=$(sed -n '/## Environment Variables/,/##/p' "$TEMPLATE_DIR/$template.md" | grep -E '^- `[^`]+`')
if [[ -n "$env_vars" ]]; then
echo "# Auto-generated environment variables for $template" > "$env_file"
echo "# Generated on $(date)" >> "$env_file"
echo "" >> "$env_file"
echo "$env_vars" | while read -r line; do
if [[ $line =~ -\s*\`([^`]+)\`.* ]]; then
local var_name="${BASH_REMATCH[1]}"
echo "$var_name=change_me" >> "$env_file"
fi
done
# Auto-generate secrets
if [[ "$AUTO_GENERATE_SECRETS" == "true" ]]; then
auto_generate_secrets "$template" "$env_file"
fi
print_success ".env file created with $(wc -l < "$env_file") variables"
else
# Create minimal .env file
echo "# Auto-generated environment variables for $template" > "$env_file"
echo "# Generated on $(date)" >> "$env_file"
echo "" >> "$env_file"
echo "# No environment variables required" >> "$env_file"
print_success ".env file created (no variables required)"
fi
log_message ".env file created for $template"
}
auto_deploy() {
local template="$1"
local project_dir="$2"
print_step "Auto-deploying $template..."
cd "$project_dir"
# Pull images
if [[ "$AUTO_PULL_IMAGES" == "true" ]]; then
print_info "Pulling Docker images..."
docker-compose pull > /dev/null 2>&1
print_success "Images pulled"
fi
# Start services
if [[ "$AUTO_START_SERVICES" == "true" ]]; then
print_info "Starting services..."
docker-compose up -d > /dev/null 2>&1
# Wait for services to be ready
print_info "Waiting for services to start..."
sleep 10
# Check service status
if docker-compose ps | grep -q "Up"; then
print_success "Services started successfully"
else
print_error "Some services failed to start"
docker-compose ps
return 1
fi
fi
log_message "$template deployed successfully"
}
show_access_info() {
local template="$1"
local project_dir="$2"
print_header "🎉 Deployment Complete!"
cd "$project_dir"
# Extract ports from docker-compose.yml
local ports=$(grep -E "^\s*-\s*\"[0-9]+:[0-9]+\"" "$project_dir/docker-compose.yml")
if [[ -n "$ports" ]]; then
print_success "Access URLs:"
echo "$ports" | while read -r line; do
if [[ $line =~ -\s*\"([0-9]+):([0-9]+)\" ]]; then
local host_port="${BASH_REMATCH[1]}"
local container_port="${BASH_REMATCH[2]}"
echo " 🌐 http://localhost:$host_port (port: $container_port)"
fi
done
echo ""
fi
print_success "Management Commands:"
echo " 📋 View logs: docker-compose logs -f"
echo " 🛑 Stop services: docker-compose down"
echo " 🔄 Restart: docker-compose restart"
echo " 📊 Status: docker-compose ps"
echo " 🗑️ Cleanup: docker-compose down -v"
echo ""
print_info "Deployment directory: $project_dir"
print_info "Log file: $LOG_FILE"
# Auto-open browser if enabled
if [[ "$AUTO_OPEN_BROWSER" == "true" ]]; then
local first_port=$(echo "$ports" | head -1 | grep -o -E '[0-9]+' | head -1)
if [[ -n "$first_port" ]]; then
print_info "Opening browser..."
xdg-open "http://localhost:$first_port" 2>/dev/null || open "http://localhost:$first_port" 2>/dev/null || echo "Could not open browser automatically"
fi
fi
}
auto_deploy_template() {
local template="$1"
local template_file="$TEMPLATE_DIR/$template.md"
local project_dir="$DEPLOYMENT_BASE_DIR/$template"
print_header "🚀 Auto-Deploying $template"
# Validate template
if [[ ! -f "$template_file" ]]; then
print_error "Template not found: $template"
print_info "Available templates:"
ls -1 "$TEMPLATE_DIR" | sed 's/.md$//' | head -10
exit 1
fi
# Create deployment directory
mkdir -p "$DEPLOYMENT_BASE_DIR"
if [[ -d "$project_dir" ]]; then
print_warning "Deployment directory exists: $project_dir"
print_info "Cleaning up previous deployment..."
cd "$project_dir"
docker-compose down -v 2>/dev/null || true
cd ..
rm -rf "$project_dir"
fi
mkdir -p "$project_dir"
print_success "Created deployment directory: $project_dir"
# Setup deployment
extract_docker_compose "$template_file" "$project_dir"
create_env_file "$template" "$project_dir"
if [[ "$AUTO_CREATE_DIRECTORIES" == "true" ]]; then
auto_create_directories "$template" "$project_dir"
fi
# Deploy
if auto_deploy "$template" "$project_dir"; then
show_access_info "$template" "$project_dir"
print_success "🎉 $template deployed successfully!"
else
print_error "$template deployment failed"
exit 1
fi
}
deploy_all_templates() {
print_header "🚀 Auto-Deploying All Templates"
local failed=()
local success=0
for template in "${!TEMPLATE_CONFIGS[@]}"; do
echo ""
print_step "Deploying $template..."
if auto_deploy_template "$template"; then
((success++))
else
failed+=("$template")
fi
done
echo ""
print_header "📊 Deployment Summary"
print_success "Successfully deployed: $success templates"
if [[ ${#failed[@]} -gt 0 ]]; then
print_error "Failed deployments: ${#failed[@]} templates"
print_info "Failed templates: ${failed[*]}"
fi
}
show_help() {
echo "🚀 Docker Template Auto-Deployment"
echo ""
echo "Usage: $0 [OPTIONS] [TEMPLATE]"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -l, --list List available templates"
echo " -a, --all Deploy all templates"
echo " --no-secrets Don't auto-generate secrets"
echo " --no-dirs Don't auto-create directories"
echo " --no-pull Don't auto-pull images"
echo " --no-start Don't auto-start services"
echo " --open-browser Auto-open browser after deployment"
echo ""
echo "Templates:"
for template in "${!TEMPLATE_CONFIGS[@]}"; do
echo "$template"
done
echo ""
echo "Examples:"
echo " $0 # Interactive mode"
echo " $0 umami # Auto-deploy umami"
echo " $0 --all # Auto-deploy all templates"
echo " $0 --open-browser umami # Deploy and open browser"
}
# Main execution
main() {
# Create deployment directory
mkdir -p "$DEPLOYMENT_BASE_DIR"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-l|--list)
print_header "Available Templates"
for template in "${!TEMPLATE_CONFIGS[@]}"; do
echo "$template - ${TEMPLATE_CONFIGS[$template]}"
done
exit 0
;;
-a|--all)
check_dependencies
deploy_all_templates
exit 0
;;
--no-secrets)
AUTO_GENERATE_SECRETS=false
shift
;;
--no-dirs)
AUTO_CREATE_DIRECTORIES=false
shift
;;
--no-pull)
AUTO_PULL_IMAGES=false
shift
;;
--no-start)
AUTO_START_SERVICES=false
shift
;;
--open-browser)
AUTO_OPEN_BROWSER=true
shift
;;
-*)
print_error "Unknown option: $1"
show_help
exit 1
;;
*)
SELECTED_TEMPLATE="$1"
shift
;;
esac
done
# Check dependencies
check_dependencies
# Execute deployment
if [[ -n "$SELECTED_TEMPLATE" ]]; then
auto_deploy_template "$SELECTED_TEMPLATE"
else
# Interactive mode - deploy a simple template by default
print_info "No template specified, deploying Glance (simple dashboard)..."
auto_deploy_template "glance"
fi
}
# Run main function
main "$@"