| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788 |
- #!/usr/bin/env bash
- set -e
- # Colors for output
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- NC='\033[0m' # No Color
- # Script version
- VERSION="1.1.0"
- # Logging functions (defined early for use in parse_args)
- log_info() {
- echo -e "${BLUE}[INFO]${NC} $1"
- }
- log_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
- }
- log_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
- }
- log_error() {
- echo -e "${RED}[ERROR]${NC} $1"
- }
- # Global variables
- SUFFIX=""
- ADMIN_TOKEN=""
- DB_PASSWORD=""
- DEPLOY_DIR=""
- OS_TYPE=""
- IMAGE_TAG="latest"
- BRANCH_NAME="main"
- APP_PORT="23000"
- # CLI argument variables
- BRANCH_ARG=""
- PORT_ARG=""
- TOKEN_ARG=""
- DIR_ARG=""
- DOMAIN_ARG=""
- ENABLE_CADDY=false
- NON_INTERACTIVE=false
- show_help() {
- cat << EOF
- Claude Code Hub - One-Click Deployment Script v${VERSION}
- Usage: $0 [OPTIONS]
- Options:
- -b, --branch <name> Branch to deploy: main (default) or dev
- -p, --port <port> App external port (default: 23000)
- -t, --admin-token <token> Custom admin token (default: auto-generated)
- -d, --deploy-dir <path> Custom deployment directory
- --domain <domain> Domain for Caddy HTTPS (enables Caddy automatically)
- --enable-caddy Enable Caddy reverse proxy without HTTPS (HTTP only)
- -y, --yes Non-interactive mode (skip prompts, use defaults)
- -h, --help Show this help message
- Examples:
- $0 # Interactive deployment
- $0 -y # Non-interactive with defaults
- $0 -b dev -p 8080 -y # Deploy dev branch on port 8080
- $0 -t "my-secure-token" -y # Use custom admin token
- $0 --domain hub.example.com -y # Deploy with Caddy HTTPS
- $0 --enable-caddy -y # Deploy with Caddy HTTP-only
- For more information, visit: https://github.com/ding113/claude-code-hub
- EOF
- }
- parse_args() {
- while [[ $# -gt 0 ]]; do
- case $1 in
- -b|--branch)
- if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
- log_error "Option $1 requires an argument"
- exit 1
- fi
- BRANCH_ARG="$2"
- shift 2
- ;;
- -p|--port)
- if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
- log_error "Option $1 requires an argument"
- exit 1
- fi
- PORT_ARG="$2"
- shift 2
- ;;
- -t|--admin-token)
- if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
- log_error "Option $1 requires an argument"
- exit 1
- fi
- TOKEN_ARG="$2"
- shift 2
- ;;
- -d|--deploy-dir)
- if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
- log_error "Option $1 requires an argument"
- exit 1
- fi
- DIR_ARG="$2"
- shift 2
- ;;
- --domain)
- if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
- log_error "Option $1 requires an argument"
- exit 1
- fi
- DOMAIN_ARG="$2"
- ENABLE_CADDY=true
- shift 2
- ;;
- --enable-caddy)
- ENABLE_CADDY=true
- shift
- ;;
- -y|--yes)
- NON_INTERACTIVE=true
- shift
- ;;
- -h|--help)
- show_help
- exit 0
- ;;
- *)
- log_error "Unknown option: $1"
- echo ""
- show_help
- exit 1
- ;;
- esac
- done
- }
- validate_inputs() {
- # Validate port
- if [[ -n "$PORT_ARG" ]]; then
- if ! [[ "$PORT_ARG" =~ ^[0-9]+$ ]] || [[ "$PORT_ARG" -lt 1 ]] || [[ "$PORT_ARG" -gt 65535 ]]; then
- log_error "Invalid port number: $PORT_ARG (must be 1-65535)"
- exit 1
- fi
- APP_PORT="$PORT_ARG"
- fi
- # Validate admin token length
- if [[ -n "$TOKEN_ARG" ]]; then
- if [[ ${#TOKEN_ARG} -lt 16 ]]; then
- log_error "Admin token too short: minimum 16 characters required"
- exit 1
- fi
- ADMIN_TOKEN="$TOKEN_ARG"
- fi
- # Validate branch
- if [[ -n "$BRANCH_ARG" ]]; then
- case "$BRANCH_ARG" in
- main)
- IMAGE_TAG="latest"
- BRANCH_NAME="main"
- ;;
- dev)
- IMAGE_TAG="dev"
- BRANCH_NAME="dev"
- ;;
- *)
- log_error "Invalid branch: $BRANCH_ARG (must be 'main' or 'dev')"
- exit 1
- ;;
- esac
- fi
- # Apply custom deploy directory
- if [[ -n "$DIR_ARG" ]]; then
- DEPLOY_DIR="$DIR_ARG"
- fi
- # Validate domain format if provided
- if [[ -n "$DOMAIN_ARG" ]]; then
- if ! [[ "$DOMAIN_ARG" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
- log_error "Invalid domain format: $DOMAIN_ARG"
- exit 1
- fi
- fi
- }
- print_header() {
- echo -e "${BLUE}"
- echo "+=================================================================+"
- echo "| |"
- echo "| Claude Code Hub - One-Click Deployment |"
- echo "| Version ${VERSION} |"
- echo "| |"
- echo "+=================================================================+"
- echo -e "${NC}"
- }
- detect_os() {
- if [[ "$OSTYPE" == "linux-gnu"* ]]; then
- OS_TYPE="linux"
- DEPLOY_DIR="/www/compose/claude-code-hub"
- elif [[ "$OSTYPE" == "darwin"* ]]; then
- OS_TYPE="macos"
- DEPLOY_DIR="$HOME/Applications/claude-code-hub"
- else
- log_error "Unsupported operating system: $OSTYPE"
- exit 1
- fi
- log_info "Detected OS: $OS_TYPE"
- }
- select_branch() {
- # Skip if branch already set via CLI or non-interactive mode
- if [[ -n "$BRANCH_ARG" ]]; then
- log_info "Using branch from CLI argument: $BRANCH_NAME"
- return
- fi
- if [[ "$NON_INTERACTIVE" == true ]]; then
- log_info "Non-interactive mode: using default branch (main)"
- return
- fi
- echo ""
- echo -e "${BLUE}Please select the branch to deploy:${NC}"
- echo -e " ${GREEN}1)${NC} main (Stable release - recommended for production)"
- echo -e " ${YELLOW}2)${NC} dev (Latest features - for testing)"
- echo ""
-
- local choice
- while true; do
- read -p "Enter your choice [1]: " choice
- choice=${choice:-1}
-
- case $choice in
- 1)
- IMAGE_TAG="latest"
- BRANCH_NAME="main"
- log_success "Selected branch: main (image tag: latest)"
- break
- ;;
- 2)
- IMAGE_TAG="dev"
- BRANCH_NAME="dev"
- log_success "Selected branch: dev (image tag: dev)"
- break
- ;;
- *)
- log_error "Invalid choice. Please enter 1 or 2."
- ;;
- esac
- done
- }
- check_docker() {
- log_info "Checking Docker installation..."
-
- if ! command -v docker &> /dev/null; then
- log_warning "Docker is not installed"
- return 1
- fi
-
- if ! docker compose version &> /dev/null && ! docker-compose --version &> /dev/null; then
- log_warning "Docker Compose is not installed"
- return 1
- fi
-
- log_success "Docker and Docker Compose are installed"
- docker --version
- docker compose version 2>/dev/null || docker-compose --version
- return 0
- }
- install_docker() {
- log_info "Installing Docker..."
-
- if [[ "$OS_TYPE" == "linux" ]]; then
- if [[ $EUID -ne 0 ]]; then
- log_error "Docker installation requires root privileges on Linux"
- log_info "Please run: sudo $0"
- exit 1
- fi
- fi
-
- log_info "Downloading Docker installation script from get.docker.com..."
- local temp_script
- temp_script=$(mktemp)
- if curl -fsSL https://get.docker.com -o "$temp_script"; then
- log_info "Running Docker installation script..."
- sh "$temp_script"
- rm -f "$temp_script"
-
- if [[ "$OS_TYPE" == "linux" ]]; then
- log_info "Starting Docker service..."
- systemctl start docker
- systemctl enable docker
-
- if [[ -n "$SUDO_USER" ]]; then
- log_info "Adding user $SUDO_USER to docker group..."
- usermod -aG docker "$SUDO_USER"
- log_warning "Please log out and log back in for group changes to take effect"
- fi
- fi
-
- log_success "Docker installed successfully"
- else
- log_error "Failed to download Docker installation script"
- exit 1
- fi
- }
- generate_random_suffix() {
- SUFFIX=$(tr -dc 'a-z0-9' < /dev/urandom | head -c 4)
- log_info "Generated random suffix: $SUFFIX"
- }
- generate_admin_token() {
- # Skip if token already set via CLI
- if [[ -n "$ADMIN_TOKEN" ]]; then
- log_info "Using admin token from CLI argument"
- return
- fi
- if command -v openssl &> /dev/null; then
- ADMIN_TOKEN=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
- else
- ADMIN_TOKEN=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32)
- fi
- log_info "Generated secure admin token"
- }
- generate_db_password() {
- if command -v openssl &> /dev/null; then
- DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
- else
- DB_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24)
- fi
- log_info "Generated secure database password"
- }
- create_deployment_dir() {
- log_info "Creating deployment directory: $DEPLOY_DIR"
-
- if [[ "$OS_TYPE" == "linux" ]] && [[ ! -d "/www" ]]; then
- if [[ $EUID -ne 0 ]]; then
- log_error "Creating /www directory requires root privileges"
- log_info "Please run: sudo $0"
- exit 1
- fi
- mkdir -p "$DEPLOY_DIR"
- if [[ -n "$SUDO_USER" ]]; then
- chown -R "$SUDO_USER:$SUDO_USER" /www
- fi
- else
- mkdir -p "$DEPLOY_DIR"
- fi
-
- mkdir -p "$DEPLOY_DIR/data/postgres"
- mkdir -p "$DEPLOY_DIR/data/redis"
-
- log_success "Deployment directory created"
- }
- write_compose_file() {
- log_info "Writing docker-compose.yaml..."
-
- # Determine app ports configuration
- local app_ports_config
- if [[ "$ENABLE_CADDY" == true ]]; then
- # When Caddy is enabled, don't expose app port externally
- app_ports_config=""
- else
- app_ports_config="ports:
- - \"\${APP_PORT:-${APP_PORT}}:\${APP_PORT:-${APP_PORT}}\""
- fi
- cat > "$DEPLOY_DIR/docker-compose.yaml" << EOF
- services:
- postgres:
- image: postgres:18
- container_name: claude-code-hub-db-${SUFFIX}
- restart: unless-stopped
- ports:
- - "127.0.0.1:35432:5432"
- env_file:
- - ./.env
- environment:
- POSTGRES_USER: \${DB_USER:-postgres}
- POSTGRES_PASSWORD: \${DB_PASSWORD:-postgres}
- POSTGRES_DB: \${DB_NAME:-claude_code_hub}
- PGDATA: /data/pgdata
- TZ: Asia/Shanghai
- PGTZ: Asia/Shanghai
- volumes:
- - ./data/postgres:/data
- networks:
- - claude-code-hub-net-${SUFFIX}
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U \${DB_USER:-postgres} -d \${DB_NAME:-claude_code_hub}"]
- interval: 5s
- timeout: 5s
- retries: 10
- start_period: 10s
- redis:
- image: redis:7-alpine
- container_name: claude-code-hub-redis-${SUFFIX}
- restart: unless-stopped
- volumes:
- - ./data/redis:/data
- command: redis-server --appendonly yes
- networks:
- - claude-code-hub-net-${SUFFIX}
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 5s
- timeout: 3s
- retries: 5
- start_period: 5s
- app:
- image: ghcr.io/ding113/claude-code-hub:${IMAGE_TAG}
- container_name: claude-code-hub-app-${SUFFIX}
- depends_on:
- postgres:
- condition: service_healthy
- redis:
- condition: service_healthy
- env_file:
- - ./.env
- environment:
- NODE_ENV: production
- PORT: \${APP_PORT:-${APP_PORT}}
- DSN: postgresql://\${DB_USER:-postgres}:\${DB_PASSWORD:-postgres}@claude-code-hub-db-${SUFFIX}:5432/\${DB_NAME:-claude_code_hub}
- REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379
- AUTO_MIGRATE: \${AUTO_MIGRATE:-true}
- ENABLE_RATE_LIMIT: \${ENABLE_RATE_LIMIT:-true}
- SESSION_TTL: \${SESSION_TTL:-300}
- TZ: Asia/Shanghai
- EOF
- # Add app ports only if Caddy is not enabled
- if [[ "$ENABLE_CADDY" != true ]]; then
- cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
- ports:
- - "\${APP_PORT:-${APP_PORT}}:\${APP_PORT:-${APP_PORT}}"
- EOF
- fi
- cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
- restart: unless-stopped
- networks:
- - claude-code-hub-net-${SUFFIX}
- healthcheck:
- test: ["CMD-SHELL", "curl -f http://localhost:\${APP_PORT:-${APP_PORT}}/api/actions/health || exit 1"]
- interval: 30s
- timeout: 5s
- retries: 3
- start_period: 30s
- EOF
- # Add Caddy service if enabled
- if [[ "$ENABLE_CADDY" == true ]]; then
- cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
- caddy:
- image: caddy:2-alpine
- container_name: claude-code-hub-caddy-${SUFFIX}
- restart: unless-stopped
- ports:
- - "80:80"
- - "443:443"
- volumes:
- - ./Caddyfile:/etc/caddy/Caddyfile:ro
- - caddy_data:/data
- - caddy_config:/config
- depends_on:
- app:
- condition: service_healthy
- networks:
- - claude-code-hub-net-${SUFFIX}
- EOF
- fi
- cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
- networks:
- claude-code-hub-net-${SUFFIX}:
- driver: bridge
- name: claude-code-hub-net-${SUFFIX}
- EOF
- # Add Caddy volumes if enabled
- if [[ "$ENABLE_CADDY" == true ]]; then
- cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
- volumes:
- caddy_data:
- caddy_config:
- EOF
- fi
-
- log_success "docker-compose.yaml created"
- }
- write_caddyfile() {
- if [[ "$ENABLE_CADDY" != true ]]; then
- return
- fi
- log_info "Writing Caddyfile..."
- if [[ -n "$DOMAIN_ARG" ]]; then
- # HTTPS mode with domain (Let's Encrypt automatic)
- cat > "$DEPLOY_DIR/Caddyfile" << EOF
- ${DOMAIN_ARG} {
- reverse_proxy app:${APP_PORT}
- encode gzip
- }
- EOF
- log_success "Caddyfile created (HTTPS mode with domain: $DOMAIN_ARG)"
- else
- # HTTP-only mode
- cat > "$DEPLOY_DIR/Caddyfile" << EOF
- :80 {
- reverse_proxy app:${APP_PORT}
- encode gzip
- }
- EOF
- log_success "Caddyfile created (HTTP-only mode)"
- fi
- }
- write_env_file() {
- log_info "Writing .env file..."
-
- # Determine secure cookies setting based on Caddy and domain
- local secure_cookies="true"
- if [[ "$ENABLE_CADDY" == true ]] && [[ -z "$DOMAIN_ARG" ]]; then
- # HTTP-only Caddy mode - disable secure cookies
- secure_cookies="false"
- fi
- # If domain is set, APP_URL should use https
- local app_url=""
- if [[ -n "$DOMAIN_ARG" ]]; then
- app_url="https://${DOMAIN_ARG}"
- fi
-
- cat > "$DEPLOY_DIR/.env" << EOF
- # Admin Token (KEEP THIS SECRET!)
- ADMIN_TOKEN=${ADMIN_TOKEN}
- # Database Configuration
- DB_USER=postgres
- DB_PASSWORD=${DB_PASSWORD}
- DB_NAME=claude_code_hub
- # Application Configuration
- APP_PORT=${APP_PORT}
- APP_URL=${app_url}
- # Auto Migration (enabled for first-time setup)
- AUTO_MIGRATE=true
- # Redis Configuration
- ENABLE_RATE_LIMIT=true
- # Session Configuration
- SESSION_TTL=300
- STORE_SESSION_MESSAGES=false
- STORE_SESSION_RESPONSE_BODY=true
- # Cookie Security
- ENABLE_SECURE_COOKIES=${secure_cookies}
- # Circuit Breaker Configuration
- ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
- # Environment
- NODE_ENV=production
- TZ=Asia/Shanghai
- LOG_LEVEL=info
- EOF
- # W-015: 设置 .env 文件权限,防止敏感信息泄露
- chmod 600 "$DEPLOY_DIR/.env"
- log_success ".env file created"
- }
- start_services() {
- log_info "Starting Docker services..."
-
- cd "$DEPLOY_DIR"
-
- if docker compose version &> /dev/null; then
- docker compose pull
- docker compose up -d
- else
- docker-compose pull
- docker-compose up -d
- fi
-
- log_success "Docker services started"
- }
- wait_for_health() {
- log_info "Waiting for services to become healthy (max 60 seconds)..."
-
- cd "$DEPLOY_DIR"
-
- local max_attempts=12
- local attempt=0
-
- while [ $attempt -lt $max_attempts ]; do
- attempt=$((attempt + 1))
-
- local postgres_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-${SUFFIX}" 2>/dev/null || echo "unknown")
- local redis_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-${SUFFIX}" 2>/dev/null || echo "unknown")
- local app_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-${SUFFIX}" 2>/dev/null || echo "unknown")
-
- log_info "Health status - Postgres: $postgres_health, Redis: $redis_health, App: $app_health"
-
- if [[ "$postgres_health" == "healthy" ]] && [[ "$redis_health" == "healthy" ]] && [[ "$app_health" == "healthy" ]]; then
- log_success "All services are healthy!"
- return 0
- fi
-
- if [ $attempt -lt $max_attempts ]; then
- sleep 5
- fi
- done
-
- log_warning "Services did not become healthy within 60 seconds"
- log_info "You can check the logs with: cd $DEPLOY_DIR && docker compose logs -f"
- return 1
- }
- get_network_addresses() {
- local addresses=()
-
- if [[ "$OS_TYPE" == "linux" ]]; then
- if command -v ip &> /dev/null; then
- while IFS= read -r line; do
- addresses+=("$line")
- done < <(ip addr show 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' | grep -v '^172\.17\.' | grep -v '^169\.254\.')
- elif command -v ifconfig &> /dev/null; then
- while IFS= read -r line; do
- addresses+=("$line")
- done < <(ifconfig 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' | grep -v '^172\.17\.' | grep -v '^169\.254\.')
- fi
- elif [[ "$OS_TYPE" == "macos" ]]; then
- while IFS= read -r line; do
- addresses+=("$line")
- done < <(ifconfig 2>/dev/null | grep 'inet ' | awk '{print $2}' | grep -v '^127\.' | grep -v '^169\.254\.')
- fi
-
- addresses+=("localhost")
-
- printf '%s\n' "${addresses[@]}"
- }
- print_success_message() {
- local addresses=($(get_network_addresses))
-
- echo ""
- echo -e "${GREEN}+================================================================+${NC}"
- echo -e "${GREEN}| |${NC}"
- echo -e "${GREEN}| Claude Code Hub Deployed Successfully! |${NC}"
- echo -e "${GREEN}| |${NC}"
- echo -e "${GREEN}+================================================================+${NC}"
- echo ""
- echo -e "${BLUE}Deployment Directory:${NC}"
- echo -e " $DEPLOY_DIR"
- echo ""
- echo -e "${BLUE}Access URLs:${NC}"
- if [[ "$ENABLE_CADDY" == true ]]; then
- if [[ -n "$DOMAIN_ARG" ]]; then
- # HTTPS mode with domain
- echo -e " ${GREEN}https://${DOMAIN_ARG}${NC}"
- else
- # HTTP-only Caddy mode
- for addr in "${addresses[@]}"; do
- echo -e " ${GREEN}http://${addr}${NC}"
- done
- fi
- else
- # Direct app access
- for addr in "${addresses[@]}"; do
- echo -e " ${GREEN}http://${addr}:${APP_PORT}${NC}"
- done
- fi
- echo ""
- echo -e "${BLUE}Admin Token (KEEP THIS SECRET!):${NC}"
- echo -e " ${YELLOW}${ADMIN_TOKEN}${NC}"
- echo ""
- echo -e "${BLUE}Usage Documentation:${NC}"
- if [[ "$ENABLE_CADDY" == true ]] && [[ -n "$DOMAIN_ARG" ]]; then
- echo -e " Chinese: ${GREEN}https://${DOMAIN_ARG}/zh-CN/usage-doc${NC}"
- echo -e " English: ${GREEN}https://${DOMAIN_ARG}/en-US/usage-doc${NC}"
- else
- local first_addr="${addresses[0]}"
- local port_suffix=""
- if [[ "$ENABLE_CADDY" != true ]]; then
- port_suffix=":${APP_PORT}"
- fi
- echo -e " Chinese: ${GREEN}http://${first_addr}${port_suffix}/zh-CN/usage-doc${NC}"
- echo -e " English: ${GREEN}http://${first_addr}${port_suffix}/en-US/usage-doc${NC}"
- fi
- echo ""
- echo -e "${BLUE}Useful Commands:${NC}"
- echo -e " View logs: ${YELLOW}cd $DEPLOY_DIR && docker compose logs -f${NC}"
- echo -e " Stop services: ${YELLOW}cd $DEPLOY_DIR && docker compose down${NC}"
- echo -e " Restart: ${YELLOW}cd $DEPLOY_DIR && docker compose restart${NC}"
- if [[ "$ENABLE_CADDY" == true ]]; then
- echo ""
- echo -e "${BLUE}Caddy Configuration:${NC}"
- if [[ -n "$DOMAIN_ARG" ]]; then
- echo -e " Mode: HTTPS with Let's Encrypt (domain: $DOMAIN_ARG)"
- echo -e " Ports: 80 (HTTP redirect), 443 (HTTPS)"
- else
- echo -e " Mode: HTTP-only reverse proxy"
- echo -e " Port: 80"
- fi
- fi
- echo ""
- echo -e "${RED}IMPORTANT: Please save the admin token in a secure location!${NC}"
- echo ""
- }
- main() {
- # Parse CLI arguments first
- parse_args "$@"
-
- print_header
-
- detect_os
-
- # Apply CLI overrides after OS detection (for deploy dir)
- validate_inputs
-
- if ! check_docker; then
- log_warning "Docker is not installed. Attempting to install..."
- install_docker
-
- if ! check_docker; then
- log_error "Docker installation failed. Please install Docker manually."
- exit 1
- fi
- fi
-
- select_branch
-
- generate_random_suffix
- generate_admin_token
- generate_db_password
-
- create_deployment_dir
- write_compose_file
- write_caddyfile
- write_env_file
-
- start_services
-
- if wait_for_health; then
- print_success_message
- else
- log_warning "Deployment completed but some services may not be fully healthy yet"
- log_info "Please check the logs: cd $DEPLOY_DIR && docker compose logs -f"
- print_success_message
- fi
- }
- main "$@"
|