mirror of
https://github.com/Feather-2/paper-burner-x.git
synced 2026-09-01 15:00:56 +08:00
bbb88e8f76
- 新增 orchestrator 运行时,支持阶段注册与事件驱动 - 添加 textprep、design、eval 等 stage 模块 - 添加 storage 模块管理 artifact 和 run 数据 - 重构 ppt_generator_workflow 使用新运行时 - 添加单元测试和开发依赖
472 lines
14 KiB
Bash
472 lines
14 KiB
Bash
#!/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.0.0"
|
|
|
|
# Global variables
|
|
SUFFIX=""
|
|
ADMIN_TOKEN=""
|
|
DB_PASSWORD=""
|
|
DEPLOY_DIR=""
|
|
OS_TYPE=""
|
|
IMAGE_TAG="latest"
|
|
BRANCH_NAME="main"
|
|
|
|
print_header() {
|
|
echo -e "${BLUE}"
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ ║"
|
|
echo "║ Claude Code Hub - One-Click Deployment ║"
|
|
echo "║ Version ${VERSION} ║"
|
|
echo "║ ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo -e "${NC}"
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
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() {
|
|
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..."
|
|
if curl -fsSL https://get.docker.com -o /tmp/get-docker.sh; then
|
|
log_info "Running Docker installation script..."
|
|
sh /tmp/get-docker.sh
|
|
rm /tmp/get-docker.sh
|
|
|
|
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() {
|
|
if command -v openssl &> /dev/null; then
|
|
ADMIN_TOKEN=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
|
|
else
|
|
ADMIN_TOKEN=$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | 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=$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | 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..."
|
|
|
|
cat > "$DEPLOY_DIR/docker-compose.yaml" << EOF
|
|
services:
|
|
postgres:
|
|
image: postgres:18
|
|
container_name: claude-code-hub-db-${SUFFIX}
|
|
restart: unless-stopped
|
|
ports:
|
|
- "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:-23000}
|
|
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
|
|
ports:
|
|
- "\${APP_PORT:-23000}:\${APP_PORT:-23000}"
|
|
restart: unless-stopped
|
|
networks:
|
|
- claude-code-hub-net-${SUFFIX}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -f http://localhost:\${APP_PORT:-23000}/api/actions/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
networks:
|
|
claude-code-hub-net-${SUFFIX}:
|
|
driver: bridge
|
|
name: claude-code-hub-net-${SUFFIX}
|
|
EOF
|
|
|
|
log_success "docker-compose.yaml created"
|
|
}
|
|
|
|
write_env_file() {
|
|
log_info "Writing .env file..."
|
|
|
|
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=23000
|
|
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
|
|
|
|
# Cookie Security
|
|
ENABLE_SECURE_COOKIES=true
|
|
|
|
# Circuit Breaker Configuration
|
|
ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
|
|
|
|
# Environment
|
|
NODE_ENV=production
|
|
TZ=Asia/Shanghai
|
|
LOG_LEVEL=info
|
|
EOF
|
|
|
|
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}"
|
|
for addr in "${addresses[@]}"; do
|
|
echo -e " ${GREEN}http://${addr}:23000${NC}"
|
|
done
|
|
echo ""
|
|
echo -e "${BLUE}🔑 Admin Token (KEEP THIS SECRET!):${NC}"
|
|
echo -e " ${YELLOW}${ADMIN_TOKEN}${NC}"
|
|
echo ""
|
|
echo -e "${BLUE}📚 Usage Documentation:${NC}"
|
|
for addr in "${addresses[@]}"; do
|
|
echo -e " Chinese: ${GREEN}http://${addr}:23000/zh-CN/usage-doc${NC}"
|
|
echo -e " English: ${GREEN}http://${addr}:23000/en-US/usage-doc${NC}"
|
|
break
|
|
done
|
|
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}"
|
|
echo ""
|
|
echo -e "${RED}⚠️ IMPORTANT: Please save the admin token in a secure location!${NC}"
|
|
echo ""
|
|
}
|
|
|
|
main() {
|
|
print_header
|
|
|
|
detect_os
|
|
|
|
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_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 "$@"
|