deploy.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. #!/usr/bin/env bash
  2. set -e
  3. # Colors for output
  4. RED='\033[0;31m'
  5. GREEN='\033[0;32m'
  6. YELLOW='\033[1;33m'
  7. BLUE='\033[0;34m'
  8. NC='\033[0m' # No Color
  9. # Script version
  10. VERSION="1.0.0"
  11. # Global variables
  12. SUFFIX=""
  13. ADMIN_TOKEN=""
  14. DB_PASSWORD=""
  15. DEPLOY_DIR=""
  16. OS_TYPE=""
  17. IMAGE_TAG="latest"
  18. BRANCH_NAME="main"
  19. print_header() {
  20. echo -e "${BLUE}"
  21. echo "╔════════════════════════════════════════════════════════════════╗"
  22. echo "║ ║"
  23. echo "║ Claude Code Hub - One-Click Deployment ║"
  24. echo "║ Version ${VERSION} ║"
  25. echo "║ ║"
  26. echo "╚════════════════════════════════════════════════════════════════╝"
  27. echo -e "${NC}"
  28. }
  29. log_info() {
  30. echo -e "${BLUE}[INFO]${NC} $1"
  31. }
  32. log_success() {
  33. echo -e "${GREEN}[SUCCESS]${NC} $1"
  34. }
  35. log_warning() {
  36. echo -e "${YELLOW}[WARNING]${NC} $1"
  37. }
  38. log_error() {
  39. echo -e "${RED}[ERROR]${NC} $1"
  40. }
  41. detect_os() {
  42. if [[ "$OSTYPE" == "linux-gnu"* ]]; then
  43. OS_TYPE="linux"
  44. DEPLOY_DIR="/www/compose/claude-code-hub"
  45. elif [[ "$OSTYPE" == "darwin"* ]]; then
  46. OS_TYPE="macos"
  47. DEPLOY_DIR="$HOME/Applications/claude-code-hub"
  48. else
  49. log_error "Unsupported operating system: $OSTYPE"
  50. exit 1
  51. fi
  52. log_info "Detected OS: $OS_TYPE"
  53. }
  54. select_branch() {
  55. echo ""
  56. echo -e "${BLUE}Please select the branch to deploy:${NC}"
  57. echo -e " ${GREEN}1)${NC} main (Stable release - recommended for production)"
  58. echo -e " ${YELLOW}2)${NC} dev (Latest features - for testing)"
  59. echo ""
  60. local choice
  61. while true; do
  62. read -p "Enter your choice [1]: " choice
  63. choice=${choice:-1}
  64. case $choice in
  65. 1)
  66. IMAGE_TAG="latest"
  67. BRANCH_NAME="main"
  68. log_success "Selected branch: main (image tag: latest)"
  69. break
  70. ;;
  71. 2)
  72. IMAGE_TAG="dev"
  73. BRANCH_NAME="dev"
  74. log_success "Selected branch: dev (image tag: dev)"
  75. break
  76. ;;
  77. *)
  78. log_error "Invalid choice. Please enter 1 or 2."
  79. ;;
  80. esac
  81. done
  82. }
  83. check_docker() {
  84. log_info "Checking Docker installation..."
  85. if ! command -v docker &> /dev/null; then
  86. log_warning "Docker is not installed"
  87. return 1
  88. fi
  89. if ! docker compose version &> /dev/null && ! docker-compose --version &> /dev/null; then
  90. log_warning "Docker Compose is not installed"
  91. return 1
  92. fi
  93. log_success "Docker and Docker Compose are installed"
  94. docker --version
  95. docker compose version 2>/dev/null || docker-compose --version
  96. return 0
  97. }
  98. install_docker() {
  99. log_info "Installing Docker..."
  100. if [[ "$OS_TYPE" == "linux" ]]; then
  101. if [[ $EUID -ne 0 ]]; then
  102. log_error "Docker installation requires root privileges on Linux"
  103. log_info "Please run: sudo $0"
  104. exit 1
  105. fi
  106. fi
  107. log_info "Downloading Docker installation script from get.docker.com..."
  108. if curl -fsSL https://get.docker.com -o /tmp/get-docker.sh; then
  109. log_info "Running Docker installation script..."
  110. sh /tmp/get-docker.sh
  111. rm /tmp/get-docker.sh
  112. if [[ "$OS_TYPE" == "linux" ]]; then
  113. log_info "Starting Docker service..."
  114. systemctl start docker
  115. systemctl enable docker
  116. if [[ -n "$SUDO_USER" ]]; then
  117. log_info "Adding user $SUDO_USER to docker group..."
  118. usermod -aG docker "$SUDO_USER"
  119. log_warning "Please log out and log back in for group changes to take effect"
  120. fi
  121. fi
  122. log_success "Docker installed successfully"
  123. else
  124. log_error "Failed to download Docker installation script"
  125. exit 1
  126. fi
  127. }
  128. generate_random_suffix() {
  129. SUFFIX=$(tr -dc 'a-z0-9' < /dev/urandom | head -c 4)
  130. log_info "Generated random suffix: $SUFFIX"
  131. }
  132. generate_admin_token() {
  133. if command -v openssl &> /dev/null; then
  134. ADMIN_TOKEN=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
  135. else
  136. ADMIN_TOKEN=$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 32)
  137. fi
  138. log_info "Generated secure admin token"
  139. }
  140. generate_db_password() {
  141. if command -v openssl &> /dev/null; then
  142. DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
  143. else
  144. DB_PASSWORD=$(cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 24)
  145. fi
  146. log_info "Generated secure database password"
  147. }
  148. create_deployment_dir() {
  149. log_info "Creating deployment directory: $DEPLOY_DIR"
  150. if [[ "$OS_TYPE" == "linux" ]] && [[ ! -d "/www" ]]; then
  151. if [[ $EUID -ne 0 ]]; then
  152. log_error "Creating /www directory requires root privileges"
  153. log_info "Please run: sudo $0"
  154. exit 1
  155. fi
  156. mkdir -p "$DEPLOY_DIR"
  157. if [[ -n "$SUDO_USER" ]]; then
  158. chown -R "$SUDO_USER:$SUDO_USER" /www
  159. fi
  160. else
  161. mkdir -p "$DEPLOY_DIR"
  162. fi
  163. mkdir -p "$DEPLOY_DIR/data/postgres"
  164. mkdir -p "$DEPLOY_DIR/data/redis"
  165. log_success "Deployment directory created"
  166. }
  167. write_compose_file() {
  168. log_info "Writing docker-compose.yaml..."
  169. cat > "$DEPLOY_DIR/docker-compose.yaml" << EOF
  170. services:
  171. postgres:
  172. image: postgres:18
  173. container_name: claude-code-hub-db-${SUFFIX}
  174. restart: unless-stopped
  175. ports:
  176. - "35432:5432"
  177. env_file:
  178. - ./.env
  179. environment:
  180. POSTGRES_USER: \${DB_USER:-postgres}
  181. POSTGRES_PASSWORD: \${DB_PASSWORD:-postgres}
  182. POSTGRES_DB: \${DB_NAME:-claude_code_hub}
  183. PGDATA: /data/pgdata
  184. TZ: Asia/Shanghai
  185. PGTZ: Asia/Shanghai
  186. volumes:
  187. - ./data/postgres:/data
  188. networks:
  189. - claude-code-hub-net-${SUFFIX}
  190. healthcheck:
  191. test: ["CMD-SHELL", "pg_isready -U \${DB_USER:-postgres} -d \${DB_NAME:-claude_code_hub}"]
  192. interval: 5s
  193. timeout: 5s
  194. retries: 10
  195. start_period: 10s
  196. redis:
  197. image: redis:7-alpine
  198. container_name: claude-code-hub-redis-${SUFFIX}
  199. restart: unless-stopped
  200. volumes:
  201. - ./data/redis:/data
  202. command: redis-server --appendonly yes
  203. networks:
  204. - claude-code-hub-net-${SUFFIX}
  205. healthcheck:
  206. test: ["CMD", "redis-cli", "ping"]
  207. interval: 5s
  208. timeout: 3s
  209. retries: 5
  210. start_period: 5s
  211. app:
  212. image: ghcr.io/ding113/claude-code-hub:${IMAGE_TAG}
  213. container_name: claude-code-hub-app-${SUFFIX}
  214. depends_on:
  215. postgres:
  216. condition: service_healthy
  217. redis:
  218. condition: service_healthy
  219. env_file:
  220. - ./.env
  221. environment:
  222. NODE_ENV: production
  223. PORT: \${APP_PORT:-23000}
  224. DSN: postgresql://\${DB_USER:-postgres}:\${DB_PASSWORD:-postgres}@claude-code-hub-db-${SUFFIX}:5432/\${DB_NAME:-claude_code_hub}
  225. REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379
  226. AUTO_MIGRATE: \${AUTO_MIGRATE:-true}
  227. ENABLE_RATE_LIMIT: \${ENABLE_RATE_LIMIT:-true}
  228. SESSION_TTL: \${SESSION_TTL:-300}
  229. TZ: Asia/Shanghai
  230. ports:
  231. - "\${APP_PORT:-23000}:\${APP_PORT:-23000}"
  232. restart: unless-stopped
  233. networks:
  234. - claude-code-hub-net-${SUFFIX}
  235. healthcheck:
  236. test: ["CMD-SHELL", "curl -f http://localhost:\${APP_PORT:-23000}/api/actions/health || exit 1"]
  237. interval: 30s
  238. timeout: 5s
  239. retries: 3
  240. start_period: 30s
  241. networks:
  242. claude-code-hub-net-${SUFFIX}:
  243. driver: bridge
  244. name: claude-code-hub-net-${SUFFIX}
  245. EOF
  246. log_success "docker-compose.yaml created"
  247. }
  248. write_env_file() {
  249. log_info "Writing .env file..."
  250. cat > "$DEPLOY_DIR/.env" << EOF
  251. # Admin Token (KEEP THIS SECRET!)
  252. ADMIN_TOKEN=${ADMIN_TOKEN}
  253. # Database Configuration
  254. DB_USER=postgres
  255. DB_PASSWORD=${DB_PASSWORD}
  256. DB_NAME=claude_code_hub
  257. # Application Configuration
  258. APP_PORT=23000
  259. APP_URL=
  260. # Auto Migration (enabled for first-time setup)
  261. AUTO_MIGRATE=true
  262. # Redis Configuration
  263. ENABLE_RATE_LIMIT=true
  264. # Session Configuration
  265. SESSION_TTL=300
  266. STORE_SESSION_MESSAGES=false
  267. # Cookie Security
  268. ENABLE_SECURE_COOKIES=true
  269. # Circuit Breaker Configuration
  270. ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
  271. # Environment
  272. NODE_ENV=production
  273. TZ=Asia/Shanghai
  274. LOG_LEVEL=info
  275. EOF
  276. log_success ".env file created"
  277. }
  278. start_services() {
  279. log_info "Starting Docker services..."
  280. cd "$DEPLOY_DIR"
  281. if docker compose version &> /dev/null; then
  282. docker compose pull
  283. docker compose up -d
  284. else
  285. docker-compose pull
  286. docker-compose up -d
  287. fi
  288. log_success "Docker services started"
  289. }
  290. wait_for_health() {
  291. log_info "Waiting for services to become healthy (max 60 seconds)..."
  292. cd "$DEPLOY_DIR"
  293. local max_attempts=12
  294. local attempt=0
  295. while [ $attempt -lt $max_attempts ]; do
  296. attempt=$((attempt + 1))
  297. local postgres_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-${SUFFIX}" 2>/dev/null || echo "unknown")
  298. local redis_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-${SUFFIX}" 2>/dev/null || echo "unknown")
  299. local app_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-${SUFFIX}" 2>/dev/null || echo "unknown")
  300. log_info "Health status - Postgres: $postgres_health, Redis: $redis_health, App: $app_health"
  301. if [[ "$postgres_health" == "healthy" ]] && [[ "$redis_health" == "healthy" ]] && [[ "$app_health" == "healthy" ]]; then
  302. log_success "All services are healthy!"
  303. return 0
  304. fi
  305. if [ $attempt -lt $max_attempts ]; then
  306. sleep 5
  307. fi
  308. done
  309. log_warning "Services did not become healthy within 60 seconds"
  310. log_info "You can check the logs with: cd $DEPLOY_DIR && docker compose logs -f"
  311. return 1
  312. }
  313. get_network_addresses() {
  314. log_info "Detecting network addresses..."
  315. local addresses=()
  316. if [[ "$OS_TYPE" == "linux" ]]; then
  317. if command -v ip &> /dev/null; then
  318. while IFS= read -r line; do
  319. addresses+=("$line")
  320. done < <(ip addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' | grep -v '^172\.17\.' | grep -v '^169\.254\.')
  321. elif command -v ifconfig &> /dev/null; then
  322. while IFS= read -r line; do
  323. addresses+=("$line")
  324. done < <(ifconfig | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' | grep -v '^172\.17\.' | grep -v '^169\.254\.')
  325. fi
  326. elif [[ "$OS_TYPE" == "macos" ]]; then
  327. while IFS= read -r line; do
  328. addresses+=("$line")
  329. done < <(ifconfig | grep 'inet ' | awk '{print $2}' | grep -v '^127\.' | grep -v '^169\.254\.')
  330. fi
  331. addresses+=("localhost")
  332. printf '%s\n' "${addresses[@]}"
  333. }
  334. print_success_message() {
  335. local addresses=($(get_network_addresses))
  336. echo ""
  337. echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
  338. echo -e "${GREEN}║ ║${NC}"
  339. echo -e "${GREEN}║ 🎉 Claude Code Hub Deployed Successfully! 🎉 ║${NC}"
  340. echo -e "${GREEN}║ ║${NC}"
  341. echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}"
  342. echo ""
  343. echo -e "${BLUE}📍 Deployment Directory:${NC}"
  344. echo -e " $DEPLOY_DIR"
  345. echo ""
  346. echo -e "${BLUE}🌐 Access URLs:${NC}"
  347. for addr in "${addresses[@]}"; do
  348. echo -e " ${GREEN}http://${addr}:23000${NC}"
  349. done
  350. echo ""
  351. echo -e "${BLUE}🔑 Admin Token (KEEP THIS SECRET!):${NC}"
  352. echo -e " ${YELLOW}${ADMIN_TOKEN}${NC}"
  353. echo ""
  354. echo -e "${BLUE}📚 Usage Documentation:${NC}"
  355. for addr in "${addresses[@]}"; do
  356. echo -e " Chinese: ${GREEN}http://${addr}:23000/zh-CN/usage-doc${NC}"
  357. echo -e " English: ${GREEN}http://${addr}:23000/en-US/usage-doc${NC}"
  358. break
  359. done
  360. echo ""
  361. echo -e "${BLUE}🔧 Useful Commands:${NC}"
  362. echo -e " View logs: ${YELLOW}cd $DEPLOY_DIR && docker compose logs -f${NC}"
  363. echo -e " Stop services: ${YELLOW}cd $DEPLOY_DIR && docker compose down${NC}"
  364. echo -e " Restart: ${YELLOW}cd $DEPLOY_DIR && docker compose restart${NC}"
  365. echo ""
  366. echo -e "${RED}⚠️ IMPORTANT: Please save the admin token in a secure location!${NC}"
  367. echo ""
  368. }
  369. main() {
  370. print_header
  371. detect_os
  372. if ! check_docker; then
  373. log_warning "Docker is not installed. Attempting to install..."
  374. install_docker
  375. if ! check_docker; then
  376. log_error "Docker installation failed. Please install Docker manually."
  377. exit 1
  378. fi
  379. fi
  380. select_branch
  381. generate_random_suffix
  382. generate_admin_token
  383. generate_db_password
  384. create_deployment_dir
  385. write_compose_file
  386. write_env_file
  387. start_services
  388. if wait_for_health; then
  389. print_success_message
  390. else
  391. log_warning "Deployment completed but some services may not be fully healthy yet"
  392. log_info "Please check the logs: cd $DEPLOY_DIR && docker compose logs -f"
  393. print_success_message
  394. fi
  395. }
  396. main "$@"