deploy.sh 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  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.1.0"
  11. # Logging functions (defined early for use in parse_args)
  12. log_info() {
  13. echo -e "${BLUE}[INFO]${NC} $1"
  14. }
  15. log_success() {
  16. echo -e "${GREEN}[SUCCESS]${NC} $1"
  17. }
  18. log_warning() {
  19. echo -e "${YELLOW}[WARNING]${NC} $1"
  20. }
  21. log_error() {
  22. echo -e "${RED}[ERROR]${NC} $1"
  23. }
  24. # Global variables
  25. SUFFIX=""
  26. ADMIN_TOKEN=""
  27. DB_PASSWORD=""
  28. DEPLOY_DIR=""
  29. OS_TYPE=""
  30. IMAGE_TAG="latest"
  31. BRANCH_NAME="main"
  32. APP_PORT="23000"
  33. # CLI argument variables
  34. BRANCH_ARG=""
  35. PORT_ARG=""
  36. TOKEN_ARG=""
  37. DIR_ARG=""
  38. DOMAIN_ARG=""
  39. ENABLE_CADDY=false
  40. NON_INTERACTIVE=false
  41. show_help() {
  42. cat << EOF
  43. Claude Code Hub - One-Click Deployment Script v${VERSION}
  44. Usage: $0 [OPTIONS]
  45. Options:
  46. -b, --branch <name> Branch to deploy: main (default) or dev
  47. -p, --port <port> App external port (default: 23000)
  48. -t, --admin-token <token> Custom admin token (default: auto-generated)
  49. -d, --deploy-dir <path> Custom deployment directory
  50. --domain <domain> Domain for Caddy HTTPS (enables Caddy automatically)
  51. --enable-caddy Enable Caddy reverse proxy without HTTPS (HTTP only)
  52. -y, --yes Non-interactive mode (skip prompts, use defaults)
  53. -h, --help Show this help message
  54. Examples:
  55. $0 # Interactive deployment
  56. $0 -y # Non-interactive with defaults
  57. $0 -b dev -p 8080 -y # Deploy dev branch on port 8080
  58. $0 -t "my-secure-token" -y # Use custom admin token
  59. $0 --domain hub.example.com -y # Deploy with Caddy HTTPS
  60. $0 --enable-caddy -y # Deploy with Caddy HTTP-only
  61. For more information, visit: https://github.com/ding113/claude-code-hub
  62. EOF
  63. }
  64. parse_args() {
  65. while [[ $# -gt 0 ]]; do
  66. case $1 in
  67. -b|--branch)
  68. if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
  69. log_error "Option $1 requires an argument"
  70. exit 1
  71. fi
  72. BRANCH_ARG="$2"
  73. shift 2
  74. ;;
  75. -p|--port)
  76. if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
  77. log_error "Option $1 requires an argument"
  78. exit 1
  79. fi
  80. PORT_ARG="$2"
  81. shift 2
  82. ;;
  83. -t|--admin-token)
  84. if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
  85. log_error "Option $1 requires an argument"
  86. exit 1
  87. fi
  88. TOKEN_ARG="$2"
  89. shift 2
  90. ;;
  91. -d|--deploy-dir)
  92. if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
  93. log_error "Option $1 requires an argument"
  94. exit 1
  95. fi
  96. DIR_ARG="$2"
  97. shift 2
  98. ;;
  99. --domain)
  100. if [[ -z "${2:-}" ]] || [[ "$2" == -* ]]; then
  101. log_error "Option $1 requires an argument"
  102. exit 1
  103. fi
  104. DOMAIN_ARG="$2"
  105. ENABLE_CADDY=true
  106. shift 2
  107. ;;
  108. --enable-caddy)
  109. ENABLE_CADDY=true
  110. shift
  111. ;;
  112. -y|--yes)
  113. NON_INTERACTIVE=true
  114. shift
  115. ;;
  116. -h|--help)
  117. show_help
  118. exit 0
  119. ;;
  120. *)
  121. log_error "Unknown option: $1"
  122. echo ""
  123. show_help
  124. exit 1
  125. ;;
  126. esac
  127. done
  128. }
  129. validate_inputs() {
  130. # Validate port
  131. if [[ -n "$PORT_ARG" ]]; then
  132. if ! [[ "$PORT_ARG" =~ ^[0-9]+$ ]] || [[ "$PORT_ARG" -lt 1 ]] || [[ "$PORT_ARG" -gt 65535 ]]; then
  133. log_error "Invalid port number: $PORT_ARG (must be 1-65535)"
  134. exit 1
  135. fi
  136. APP_PORT="$PORT_ARG"
  137. fi
  138. # Validate admin token length
  139. if [[ -n "$TOKEN_ARG" ]]; then
  140. if [[ ${#TOKEN_ARG} -lt 16 ]]; then
  141. log_error "Admin token too short: minimum 16 characters required"
  142. exit 1
  143. fi
  144. ADMIN_TOKEN="$TOKEN_ARG"
  145. fi
  146. # Validate branch
  147. if [[ -n "$BRANCH_ARG" ]]; then
  148. case "$BRANCH_ARG" in
  149. main)
  150. IMAGE_TAG="latest"
  151. BRANCH_NAME="main"
  152. ;;
  153. dev)
  154. IMAGE_TAG="dev"
  155. BRANCH_NAME="dev"
  156. ;;
  157. *)
  158. log_error "Invalid branch: $BRANCH_ARG (must be 'main' or 'dev')"
  159. exit 1
  160. ;;
  161. esac
  162. fi
  163. # Apply custom deploy directory
  164. if [[ -n "$DIR_ARG" ]]; then
  165. DEPLOY_DIR="$DIR_ARG"
  166. fi
  167. # Validate domain format if provided
  168. if [[ -n "$DOMAIN_ARG" ]]; then
  169. 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
  170. log_error "Invalid domain format: $DOMAIN_ARG"
  171. exit 1
  172. fi
  173. fi
  174. }
  175. print_header() {
  176. echo -e "${BLUE}"
  177. echo "+=================================================================+"
  178. echo "| |"
  179. echo "| Claude Code Hub - One-Click Deployment |"
  180. echo "| Version ${VERSION} |"
  181. echo "| |"
  182. echo "+=================================================================+"
  183. echo -e "${NC}"
  184. }
  185. detect_os() {
  186. if [[ "$OSTYPE" == "linux-gnu"* ]]; then
  187. OS_TYPE="linux"
  188. DEPLOY_DIR="/www/compose/claude-code-hub"
  189. elif [[ "$OSTYPE" == "darwin"* ]]; then
  190. OS_TYPE="macos"
  191. DEPLOY_DIR="$HOME/Applications/claude-code-hub"
  192. else
  193. log_error "Unsupported operating system: $OSTYPE"
  194. exit 1
  195. fi
  196. log_info "Detected OS: $OS_TYPE"
  197. }
  198. select_branch() {
  199. # Skip if branch already set via CLI or non-interactive mode
  200. if [[ -n "$BRANCH_ARG" ]]; then
  201. log_info "Using branch from CLI argument: $BRANCH_NAME"
  202. return
  203. fi
  204. if [[ "$NON_INTERACTIVE" == true ]]; then
  205. log_info "Non-interactive mode: using default branch (main)"
  206. return
  207. fi
  208. echo ""
  209. echo -e "${BLUE}Please select the branch to deploy:${NC}"
  210. echo -e " ${GREEN}1)${NC} main (Stable release - recommended for production)"
  211. echo -e " ${YELLOW}2)${NC} dev (Latest features - for testing)"
  212. echo ""
  213. local choice
  214. while true; do
  215. read -p "Enter your choice [1]: " choice
  216. choice=${choice:-1}
  217. case $choice in
  218. 1)
  219. IMAGE_TAG="latest"
  220. BRANCH_NAME="main"
  221. log_success "Selected branch: main (image tag: latest)"
  222. break
  223. ;;
  224. 2)
  225. IMAGE_TAG="dev"
  226. BRANCH_NAME="dev"
  227. log_success "Selected branch: dev (image tag: dev)"
  228. break
  229. ;;
  230. *)
  231. log_error "Invalid choice. Please enter 1 or 2."
  232. ;;
  233. esac
  234. done
  235. }
  236. check_docker() {
  237. log_info "Checking Docker installation..."
  238. if ! command -v docker &> /dev/null; then
  239. log_warning "Docker is not installed"
  240. return 1
  241. fi
  242. if ! docker compose version &> /dev/null && ! docker-compose --version &> /dev/null; then
  243. log_warning "Docker Compose is not installed"
  244. return 1
  245. fi
  246. log_success "Docker and Docker Compose are installed"
  247. docker --version
  248. docker compose version 2>/dev/null || docker-compose --version
  249. return 0
  250. }
  251. install_docker() {
  252. log_info "Installing Docker..."
  253. if [[ "$OS_TYPE" == "linux" ]]; then
  254. if [[ $EUID -ne 0 ]]; then
  255. log_error "Docker installation requires root privileges on Linux"
  256. log_info "Please run: sudo $0"
  257. exit 1
  258. fi
  259. fi
  260. log_info "Downloading Docker installation script from get.docker.com..."
  261. local temp_script
  262. temp_script=$(mktemp)
  263. if curl -fsSL https://get.docker.com -o "$temp_script"; then
  264. log_info "Running Docker installation script..."
  265. sh "$temp_script"
  266. rm -f "$temp_script"
  267. if [[ "$OS_TYPE" == "linux" ]]; then
  268. log_info "Starting Docker service..."
  269. systemctl start docker
  270. systemctl enable docker
  271. if [[ -n "$SUDO_USER" ]]; then
  272. log_info "Adding user $SUDO_USER to docker group..."
  273. usermod -aG docker "$SUDO_USER"
  274. log_warning "Please log out and log back in for group changes to take effect"
  275. fi
  276. fi
  277. log_success "Docker installed successfully"
  278. else
  279. log_error "Failed to download Docker installation script"
  280. exit 1
  281. fi
  282. }
  283. generate_random_suffix() {
  284. SUFFIX=$(tr -dc 'a-z0-9' < /dev/urandom | head -c 4)
  285. log_info "Generated random suffix: $SUFFIX"
  286. }
  287. generate_admin_token() {
  288. # Skip if token already set via CLI
  289. if [[ -n "$ADMIN_TOKEN" ]]; then
  290. log_info "Using admin token from CLI argument"
  291. return
  292. fi
  293. if command -v openssl &> /dev/null; then
  294. ADMIN_TOKEN=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
  295. else
  296. ADMIN_TOKEN=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32)
  297. fi
  298. log_info "Generated secure admin token"
  299. }
  300. generate_db_password() {
  301. if command -v openssl &> /dev/null; then
  302. DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
  303. else
  304. DB_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24)
  305. fi
  306. log_info "Generated secure database password"
  307. }
  308. create_deployment_dir() {
  309. log_info "Creating deployment directory: $DEPLOY_DIR"
  310. if [[ "$OS_TYPE" == "linux" ]] && [[ ! -d "/www" ]]; then
  311. if [[ $EUID -ne 0 ]]; then
  312. log_error "Creating /www directory requires root privileges"
  313. log_info "Please run: sudo $0"
  314. exit 1
  315. fi
  316. mkdir -p "$DEPLOY_DIR"
  317. if [[ -n "$SUDO_USER" ]]; then
  318. chown -R "$SUDO_USER:$SUDO_USER" /www
  319. fi
  320. else
  321. mkdir -p "$DEPLOY_DIR"
  322. fi
  323. mkdir -p "$DEPLOY_DIR/data/postgres"
  324. mkdir -p "$DEPLOY_DIR/data/redis"
  325. log_success "Deployment directory created"
  326. }
  327. write_compose_file() {
  328. log_info "Writing docker-compose.yaml..."
  329. # Determine app ports configuration
  330. local app_ports_config
  331. if [[ "$ENABLE_CADDY" == true ]]; then
  332. # When Caddy is enabled, don't expose app port externally
  333. app_ports_config=""
  334. else
  335. app_ports_config="ports:
  336. - \"\${APP_PORT:-${APP_PORT}}:\${APP_PORT:-${APP_PORT}}\""
  337. fi
  338. cat > "$DEPLOY_DIR/docker-compose.yaml" << EOF
  339. services:
  340. postgres:
  341. image: postgres:18
  342. container_name: claude-code-hub-db-${SUFFIX}
  343. restart: unless-stopped
  344. ports:
  345. - "127.0.0.1:35432:5432"
  346. env_file:
  347. - ./.env
  348. environment:
  349. POSTGRES_USER: \${DB_USER:-postgres}
  350. POSTGRES_PASSWORD: \${DB_PASSWORD:-postgres}
  351. POSTGRES_DB: \${DB_NAME:-claude_code_hub}
  352. PGDATA: /data/pgdata
  353. TZ: Asia/Shanghai
  354. PGTZ: Asia/Shanghai
  355. volumes:
  356. - ./data/postgres:/data
  357. networks:
  358. - claude-code-hub-net-${SUFFIX}
  359. healthcheck:
  360. test: ["CMD-SHELL", "pg_isready -U \${DB_USER:-postgres} -d \${DB_NAME:-claude_code_hub}"]
  361. interval: 5s
  362. timeout: 5s
  363. retries: 10
  364. start_period: 10s
  365. redis:
  366. image: redis:7-alpine
  367. container_name: claude-code-hub-redis-${SUFFIX}
  368. restart: unless-stopped
  369. volumes:
  370. - ./data/redis:/data
  371. command: redis-server --appendonly yes
  372. networks:
  373. - claude-code-hub-net-${SUFFIX}
  374. healthcheck:
  375. test: ["CMD", "redis-cli", "ping"]
  376. interval: 5s
  377. timeout: 3s
  378. retries: 5
  379. start_period: 5s
  380. app:
  381. image: ghcr.io/ding113/claude-code-hub:${IMAGE_TAG}
  382. container_name: claude-code-hub-app-${SUFFIX}
  383. depends_on:
  384. postgres:
  385. condition: service_healthy
  386. redis:
  387. condition: service_healthy
  388. env_file:
  389. - ./.env
  390. environment:
  391. NODE_ENV: production
  392. PORT: \${APP_PORT:-${APP_PORT}}
  393. DSN: postgresql://\${DB_USER:-postgres}:\${DB_PASSWORD:-postgres}@claude-code-hub-db-${SUFFIX}:5432/\${DB_NAME:-claude_code_hub}
  394. REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379
  395. AUTO_MIGRATE: \${AUTO_MIGRATE:-true}
  396. ENABLE_RATE_LIMIT: \${ENABLE_RATE_LIMIT:-true}
  397. SESSION_TTL: \${SESSION_TTL:-300}
  398. TZ: Asia/Shanghai
  399. EOF
  400. # Add app ports only if Caddy is not enabled
  401. if [[ "$ENABLE_CADDY" != true ]]; then
  402. cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
  403. ports:
  404. - "\${APP_PORT:-${APP_PORT}}:\${APP_PORT:-${APP_PORT}}"
  405. EOF
  406. fi
  407. cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
  408. restart: unless-stopped
  409. networks:
  410. - claude-code-hub-net-${SUFFIX}
  411. healthcheck:
  412. test: ["CMD-SHELL", "curl -f http://localhost:\${APP_PORT:-${APP_PORT}}/api/actions/health || exit 1"]
  413. interval: 30s
  414. timeout: 5s
  415. retries: 3
  416. start_period: 30s
  417. EOF
  418. # Add Caddy service if enabled
  419. if [[ "$ENABLE_CADDY" == true ]]; then
  420. cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
  421. caddy:
  422. image: caddy:2-alpine
  423. container_name: claude-code-hub-caddy-${SUFFIX}
  424. restart: unless-stopped
  425. ports:
  426. - "80:80"
  427. - "443:443"
  428. volumes:
  429. - ./Caddyfile:/etc/caddy/Caddyfile:ro
  430. - caddy_data:/data
  431. - caddy_config:/config
  432. depends_on:
  433. app:
  434. condition: service_healthy
  435. networks:
  436. - claude-code-hub-net-${SUFFIX}
  437. EOF
  438. fi
  439. cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
  440. networks:
  441. claude-code-hub-net-${SUFFIX}:
  442. driver: bridge
  443. name: claude-code-hub-net-${SUFFIX}
  444. EOF
  445. # Add Caddy volumes if enabled
  446. if [[ "$ENABLE_CADDY" == true ]]; then
  447. cat >> "$DEPLOY_DIR/docker-compose.yaml" << EOF
  448. volumes:
  449. caddy_data:
  450. caddy_config:
  451. EOF
  452. fi
  453. log_success "docker-compose.yaml created"
  454. }
  455. write_caddyfile() {
  456. if [[ "$ENABLE_CADDY" != true ]]; then
  457. return
  458. fi
  459. log_info "Writing Caddyfile..."
  460. if [[ -n "$DOMAIN_ARG" ]]; then
  461. # HTTPS mode with domain (Let's Encrypt automatic)
  462. cat > "$DEPLOY_DIR/Caddyfile" << EOF
  463. ${DOMAIN_ARG} {
  464. reverse_proxy app:${APP_PORT}
  465. encode gzip
  466. }
  467. EOF
  468. log_success "Caddyfile created (HTTPS mode with domain: $DOMAIN_ARG)"
  469. else
  470. # HTTP-only mode
  471. cat > "$DEPLOY_DIR/Caddyfile" << EOF
  472. :80 {
  473. reverse_proxy app:${APP_PORT}
  474. encode gzip
  475. }
  476. EOF
  477. log_success "Caddyfile created (HTTP-only mode)"
  478. fi
  479. }
  480. write_env_file() {
  481. log_info "Writing .env file..."
  482. # Determine secure cookies setting based on Caddy and domain
  483. local secure_cookies="true"
  484. if [[ "$ENABLE_CADDY" == true ]] && [[ -z "$DOMAIN_ARG" ]]; then
  485. # HTTP-only Caddy mode - disable secure cookies
  486. secure_cookies="false"
  487. fi
  488. # If domain is set, APP_URL should use https
  489. local app_url=""
  490. if [[ -n "$DOMAIN_ARG" ]]; then
  491. app_url="https://${DOMAIN_ARG}"
  492. fi
  493. cat > "$DEPLOY_DIR/.env" << EOF
  494. # Admin Token (KEEP THIS SECRET!)
  495. ADMIN_TOKEN=${ADMIN_TOKEN}
  496. # Database Configuration
  497. DB_USER=postgres
  498. DB_PASSWORD=${DB_PASSWORD}
  499. DB_NAME=claude_code_hub
  500. # Application Configuration
  501. APP_PORT=${APP_PORT}
  502. APP_URL=${app_url}
  503. # Auto Migration (enabled for first-time setup)
  504. AUTO_MIGRATE=true
  505. # Redis Configuration
  506. ENABLE_RATE_LIMIT=true
  507. # Session Configuration
  508. SESSION_TTL=300
  509. STORE_SESSION_MESSAGES=false
  510. STORE_SESSION_RESPONSE_BODY=true
  511. # Cookie Security
  512. ENABLE_SECURE_COOKIES=${secure_cookies}
  513. # Circuit Breaker Configuration
  514. ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
  515. # Environment
  516. NODE_ENV=production
  517. TZ=Asia/Shanghai
  518. LOG_LEVEL=info
  519. EOF
  520. # W-015: 设置 .env 文件权限,防止敏感信息泄露
  521. chmod 600 "$DEPLOY_DIR/.env"
  522. log_success ".env file created"
  523. }
  524. start_services() {
  525. log_info "Starting Docker services..."
  526. cd "$DEPLOY_DIR"
  527. if docker compose version &> /dev/null; then
  528. docker compose pull
  529. docker compose up -d
  530. else
  531. docker-compose pull
  532. docker-compose up -d
  533. fi
  534. log_success "Docker services started"
  535. }
  536. wait_for_health() {
  537. log_info "Waiting for services to become healthy (max 60 seconds)..."
  538. cd "$DEPLOY_DIR"
  539. local max_attempts=12
  540. local attempt=0
  541. while [ $attempt -lt $max_attempts ]; do
  542. attempt=$((attempt + 1))
  543. local postgres_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-${SUFFIX}" 2>/dev/null || echo "unknown")
  544. local redis_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-${SUFFIX}" 2>/dev/null || echo "unknown")
  545. local app_health=$(docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-${SUFFIX}" 2>/dev/null || echo "unknown")
  546. log_info "Health status - Postgres: $postgres_health, Redis: $redis_health, App: $app_health"
  547. if [[ "$postgres_health" == "healthy" ]] && [[ "$redis_health" == "healthy" ]] && [[ "$app_health" == "healthy" ]]; then
  548. log_success "All services are healthy!"
  549. return 0
  550. fi
  551. if [ $attempt -lt $max_attempts ]; then
  552. sleep 5
  553. fi
  554. done
  555. log_warning "Services did not become healthy within 60 seconds"
  556. log_info "You can check the logs with: cd $DEPLOY_DIR && docker compose logs -f"
  557. return 1
  558. }
  559. get_network_addresses() {
  560. local addresses=()
  561. if [[ "$OS_TYPE" == "linux" ]]; then
  562. if command -v ip &> /dev/null; then
  563. while IFS= read -r line; do
  564. addresses+=("$line")
  565. 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\.')
  566. elif command -v ifconfig &> /dev/null; then
  567. while IFS= read -r line; do
  568. addresses+=("$line")
  569. done < <(ifconfig 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '^127\.' | grep -v '^172\.17\.' | grep -v '^169\.254\.')
  570. fi
  571. elif [[ "$OS_TYPE" == "macos" ]]; then
  572. while IFS= read -r line; do
  573. addresses+=("$line")
  574. done < <(ifconfig 2>/dev/null | grep 'inet ' | awk '{print $2}' | grep -v '^127\.' | grep -v '^169\.254\.')
  575. fi
  576. addresses+=("localhost")
  577. printf '%s\n' "${addresses[@]}"
  578. }
  579. print_success_message() {
  580. local addresses=($(get_network_addresses))
  581. echo ""
  582. echo -e "${GREEN}+================================================================+${NC}"
  583. echo -e "${GREEN}| |${NC}"
  584. echo -e "${GREEN}| Claude Code Hub Deployed Successfully! |${NC}"
  585. echo -e "${GREEN}| |${NC}"
  586. echo -e "${GREEN}+================================================================+${NC}"
  587. echo ""
  588. echo -e "${BLUE}Deployment Directory:${NC}"
  589. echo -e " $DEPLOY_DIR"
  590. echo ""
  591. echo -e "${BLUE}Access URLs:${NC}"
  592. if [[ "$ENABLE_CADDY" == true ]]; then
  593. if [[ -n "$DOMAIN_ARG" ]]; then
  594. # HTTPS mode with domain
  595. echo -e " ${GREEN}https://${DOMAIN_ARG}${NC}"
  596. else
  597. # HTTP-only Caddy mode
  598. for addr in "${addresses[@]}"; do
  599. echo -e " ${GREEN}http://${addr}${NC}"
  600. done
  601. fi
  602. else
  603. # Direct app access
  604. for addr in "${addresses[@]}"; do
  605. echo -e " ${GREEN}http://${addr}:${APP_PORT}${NC}"
  606. done
  607. fi
  608. echo ""
  609. echo -e "${BLUE}Admin Token (KEEP THIS SECRET!):${NC}"
  610. echo -e " ${YELLOW}${ADMIN_TOKEN}${NC}"
  611. echo ""
  612. echo -e "${BLUE}Usage Documentation:${NC}"
  613. if [[ "$ENABLE_CADDY" == true ]] && [[ -n "$DOMAIN_ARG" ]]; then
  614. echo -e " Chinese: ${GREEN}https://${DOMAIN_ARG}/zh-CN/usage-doc${NC}"
  615. echo -e " English: ${GREEN}https://${DOMAIN_ARG}/en-US/usage-doc${NC}"
  616. else
  617. local first_addr="${addresses[0]}"
  618. local port_suffix=""
  619. if [[ "$ENABLE_CADDY" != true ]]; then
  620. port_suffix=":${APP_PORT}"
  621. fi
  622. echo -e " Chinese: ${GREEN}http://${first_addr}${port_suffix}/zh-CN/usage-doc${NC}"
  623. echo -e " English: ${GREEN}http://${first_addr}${port_suffix}/en-US/usage-doc${NC}"
  624. fi
  625. echo ""
  626. echo -e "${BLUE}Useful Commands:${NC}"
  627. echo -e " View logs: ${YELLOW}cd $DEPLOY_DIR && docker compose logs -f${NC}"
  628. echo -e " Stop services: ${YELLOW}cd $DEPLOY_DIR && docker compose down${NC}"
  629. echo -e " Restart: ${YELLOW}cd $DEPLOY_DIR && docker compose restart${NC}"
  630. if [[ "$ENABLE_CADDY" == true ]]; then
  631. echo ""
  632. echo -e "${BLUE}Caddy Configuration:${NC}"
  633. if [[ -n "$DOMAIN_ARG" ]]; then
  634. echo -e " Mode: HTTPS with Let's Encrypt (domain: $DOMAIN_ARG)"
  635. echo -e " Ports: 80 (HTTP redirect), 443 (HTTPS)"
  636. else
  637. echo -e " Mode: HTTP-only reverse proxy"
  638. echo -e " Port: 80"
  639. fi
  640. fi
  641. echo ""
  642. echo -e "${RED}IMPORTANT: Please save the admin token in a secure location!${NC}"
  643. echo ""
  644. }
  645. main() {
  646. # Parse CLI arguments first
  647. parse_args "$@"
  648. print_header
  649. detect_os
  650. # Apply CLI overrides after OS detection (for deploy dir)
  651. validate_inputs
  652. if ! check_docker; then
  653. log_warning "Docker is not installed. Attempting to install..."
  654. install_docker
  655. if ! check_docker; then
  656. log_error "Docker installation failed. Please install Docker manually."
  657. exit 1
  658. fi
  659. fi
  660. select_branch
  661. generate_random_suffix
  662. generate_admin_token
  663. generate_db_password
  664. create_deployment_dir
  665. write_compose_file
  666. write_caddyfile
  667. write_env_file
  668. start_services
  669. if wait_for_health; then
  670. print_success_message
  671. else
  672. log_warning "Deployment completed but some services may not be fully healthy yet"
  673. log_info "Please check the logs: cd $DEPLOY_DIR && docker compose logs -f"
  674. print_success_message
  675. fi
  676. }
  677. main "$@"