| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- # Claude Code Hub - One-Click Deployment Script for Windows
- # PowerShell 5.1+ required
- #Requires -Version 5.1
- # Script version
- $VERSION = "1.0.0"
- # Global variables
- $SUFFIX = ""
- $ADMIN_TOKEN = ""
- $DB_PASSWORD = ""
- $DEPLOY_DIR = "C:\ProgramData\claude-code-hub"
- $IMAGE_TAG = "latest"
- $BRANCH_NAME = "main"
- function Write-ColorOutput {
- param(
- [string]$Message,
- [string]$Type = "Info"
- )
-
- switch ($Type) {
- "Header" { Write-Host $Message -ForegroundColor Cyan }
- "Info" { Write-Host "[INFO] $Message" -ForegroundColor Blue }
- "Success" { Write-Host "[SUCCESS] $Message" -ForegroundColor Green }
- "Warning" { Write-Host "[WARNING] $Message" -ForegroundColor Yellow }
- "Error" { Write-Host "[ERROR] $Message" -ForegroundColor Red }
- default { Write-Host $Message }
- }
- }
- function Show-Header {
- Write-ColorOutput "╔════════════════════════════════════════════════════════════════╗" -Type Header
- Write-ColorOutput "║ ║" -Type Header
- Write-ColorOutput "║ Claude Code Hub - One-Click Deployment ║" -Type Header
- Write-ColorOutput "║ Version $VERSION ║" -Type Header
- Write-ColorOutput "║ ║" -Type Header
- Write-ColorOutput "╚════════════════════════════════════════════════════════════════╝" -Type Header
- Write-Host ""
- }
- function Test-DockerInstalled {
- Write-ColorOutput "Checking Docker installation..." -Type Info
-
- try {
- $dockerVersion = docker --version 2>$null
- if ($LASTEXITCODE -ne 0) {
- Write-ColorOutput "Docker is not installed" -Type Warning
- return $false
- }
-
- $composeVersion = docker compose version 2>$null
- if ($LASTEXITCODE -ne 0) {
- Write-ColorOutput "Docker Compose is not installed" -Type Warning
- return $false
- }
-
- Write-ColorOutput "Docker and Docker Compose are installed" -Type Success
- Write-Host $dockerVersion
- Write-Host $composeVersion
- return $true
- }
- catch {
- Write-ColorOutput "Docker is not installed" -Type Warning
- return $false
- }
- }
- function Show-DockerInstallInstructions {
- Write-ColorOutput "Docker is not installed on this system" -Type Error
- Write-Host ""
- Write-ColorOutput "Please install Docker Desktop for Windows:" -Type Info
- Write-Host " 1. Download from: https://www.docker.com/products/docker-desktop/" -ForegroundColor Cyan
- Write-Host " 2. Run the installer and follow the instructions"
- Write-Host " 3. Restart your computer after installation"
- Write-Host " 4. Run this script again"
- Write-Host ""
- Write-ColorOutput "Press any key to open Docker Desktop download page..." -Type Info
- $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
- Start-Process "https://www.docker.com/products/docker-desktop/"
- exit 1
- }
- function Select-Branch {
- Write-Host ""
- Write-ColorOutput "Please select the branch to deploy:" -Type Info
- Write-Host " 1) main (Stable release - recommended for production)" -ForegroundColor Green
- Write-Host " 2) dev (Latest features - for testing)" -ForegroundColor Yellow
- Write-Host ""
-
- while ($true) {
- $choice = Read-Host "Enter your choice [1]"
- if ([string]::IsNullOrWhiteSpace($choice)) {
- $choice = "1"
- }
-
- switch ($choice) {
- "1" {
- $script:IMAGE_TAG = "latest"
- $script:BRANCH_NAME = "main"
- Write-ColorOutput "Selected branch: main (image tag: latest)" -Type Success
- break
- }
- "2" {
- $script:IMAGE_TAG = "dev"
- $script:BRANCH_NAME = "dev"
- Write-ColorOutput "Selected branch: dev (image tag: dev)" -Type Success
- break
- }
- default {
- Write-ColorOutput "Invalid choice. Please enter 1 or 2." -Type Error
- continue
- }
- }
- break
- }
- }
- function New-RandomSuffix {
- $chars = "abcdefghijklmnopqrstuvwxyz0123456789"
- $script:SUFFIX = -join ((1..4) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] })
- Write-ColorOutput "Generated random suffix: $SUFFIX" -Type Info
- }
- function New-AdminToken {
- $bytes = New-Object byte[] 24
- $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
- $rng.GetBytes($bytes)
- $script:ADMIN_TOKEN = [Convert]::ToBase64String($bytes) -replace '[/+=]', '' | Select-Object -First 32
- $script:ADMIN_TOKEN = $script:ADMIN_TOKEN.Substring(0, 32)
- Write-ColorOutput "Generated secure admin token" -Type Info
- }
- function New-DbPassword {
- $bytes = New-Object byte[] 18
- $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
- $rng.GetBytes($bytes)
- $script:DB_PASSWORD = [Convert]::ToBase64String($bytes) -replace '[/+=]', '' | Select-Object -First 24
- $script:DB_PASSWORD = $script:DB_PASSWORD.Substring(0, 24)
- Write-ColorOutput "Generated secure database password" -Type Info
- }
- function New-DeploymentDirectory {
- Write-ColorOutput "Creating deployment directory: $DEPLOY_DIR" -Type Info
-
- try {
- if (-not (Test-Path $DEPLOY_DIR)) {
- New-Item -ItemType Directory -Path $DEPLOY_DIR -Force | Out-Null
- }
-
- New-Item -ItemType Directory -Path "$DEPLOY_DIR\data\postgres" -Force | Out-Null
- New-Item -ItemType Directory -Path "$DEPLOY_DIR\data\redis" -Force | Out-Null
-
- Write-ColorOutput "Deployment directory created" -Type Success
- }
- catch {
- Write-ColorOutput "Failed to create deployment directory: $_" -Type Error
- exit 1
- }
- }
- function Write-ComposeFile {
- Write-ColorOutput "Writing docker-compose.yaml..." -Type Info
-
- $composeContent = @"
- 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
- "@
-
- try {
- Set-Content -Path "$DEPLOY_DIR\docker-compose.yaml" -Value $composeContent -Encoding UTF8
- Write-ColorOutput "docker-compose.yaml created" -Type Success
- }
- catch {
- Write-ColorOutput "Failed to write docker-compose.yaml: $_" -Type Error
- exit 1
- }
- }
- function Write-EnvFile {
- Write-ColorOutput "Writing .env file..." -Type Info
-
- $envContent = @"
- # 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
- "@
-
- try {
- Set-Content -Path "$DEPLOY_DIR\.env" -Value $envContent -Encoding UTF8
- Write-ColorOutput ".env file created" -Type Success
- }
- catch {
- Write-ColorOutput "Failed to write .env file: $_" -Type Error
- exit 1
- }
- }
- function Start-Services {
- Write-ColorOutput "Starting Docker services..." -Type Info
-
- try {
- Push-Location $DEPLOY_DIR
-
- docker compose pull
- if ($LASTEXITCODE -ne 0) {
- throw "Failed to pull Docker images"
- }
-
- docker compose up -d
- if ($LASTEXITCODE -ne 0) {
- throw "Failed to start services"
- }
-
- Pop-Location
- Write-ColorOutput "Docker services started" -Type Success
- }
- catch {
- Pop-Location
- Write-ColorOutput "Failed to start services: $_" -Type Error
- exit 1
- }
- }
- function Wait-ForHealth {
- Write-ColorOutput "Waiting for services to become healthy (max 60 seconds)..." -Type Info
-
- $maxAttempts = 12
- $attempt = 0
-
- Push-Location $DEPLOY_DIR
-
- while ($attempt -lt $maxAttempts) {
- $attempt++
-
- try {
- $postgresHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-$SUFFIX" 2>$null)
- $redisHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-$SUFFIX" 2>$null)
- $appHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-$SUFFIX" 2>$null)
-
- if (-not $postgresHealth) { $postgresHealth = "unknown" }
- if (-not $redisHealth) { $redisHealth = "unknown" }
- if (-not $appHealth) { $appHealth = "unknown" }
-
- Write-ColorOutput "Health status - Postgres: $postgresHealth, Redis: $redisHealth, App: $appHealth" -Type Info
-
- if ($postgresHealth -eq "healthy" -and $redisHealth -eq "healthy" -and $appHealth -eq "healthy") {
- Pop-Location
- Write-ColorOutput "All services are healthy!" -Type Success
- return $true
- }
- }
- catch {
- # Continue waiting
- }
-
- if ($attempt -lt $maxAttempts) {
- Start-Sleep -Seconds 5
- }
- }
-
- Pop-Location
- Write-ColorOutput "Services did not become healthy within 60 seconds" -Type Warning
- Write-ColorOutput "You can check the logs with: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
- return $false
- }
- function Get-NetworkAddresses {
- $addresses = @()
-
- try {
- $adapters = Get-NetIPAddress -AddressFamily IPv4 |
- Where-Object {
- $_.InterfaceAlias -notlike '*Loopback*' -and
- $_.InterfaceAlias -notlike '*Docker*' -and
- $_.IPAddress -notlike '169.254.*'
- }
-
- foreach ($adapter in $adapters) {
- $addresses += $adapter.IPAddress
- }
- }
- catch {
- # Silently continue
- }
-
- $addresses += "localhost"
- return $addresses
- }
- function Show-SuccessMessage {
- $addresses = Get-NetworkAddresses
-
- Write-Host ""
- Write-Host "╔════════════════════════════════════════════════════════════════╗" -ForegroundColor Green
- Write-Host "║ ║" -ForegroundColor Green
- Write-Host "║ 🎉 Claude Code Hub Deployed Successfully! 🎉 ║" -ForegroundColor Green
- Write-Host "║ ║" -ForegroundColor Green
- Write-Host "╚════════════════════════════════════════════════════════════════╝" -ForegroundColor Green
- Write-Host ""
-
- Write-Host "📍 Deployment Directory:" -ForegroundColor Blue
- Write-Host " $DEPLOY_DIR"
- Write-Host ""
-
- Write-Host "🌐 Access URLs:" -ForegroundColor Blue
- foreach ($addr in $addresses) {
- Write-Host " http://${addr}:23000" -ForegroundColor Green
- }
- Write-Host ""
-
- Write-Host "🔑 Admin Token (KEEP THIS SECRET!):" -ForegroundColor Blue
- Write-Host " $ADMIN_TOKEN" -ForegroundColor Yellow
- Write-Host ""
-
- Write-Host "📚 Usage Documentation:" -ForegroundColor Blue
- $firstAddr = $addresses[0]
- Write-Host " Chinese: http://${firstAddr}:23000/zh-CN/usage-doc" -ForegroundColor Green
- Write-Host " English: http://${firstAddr}:23000/en-US/usage-doc" -ForegroundColor Green
- Write-Host ""
-
- Write-Host "🔧 Useful Commands:" -ForegroundColor Blue
- Write-Host " View logs: cd $DEPLOY_DIR; docker compose logs -f" -ForegroundColor Yellow
- Write-Host " Stop services: cd $DEPLOY_DIR; docker compose down" -ForegroundColor Yellow
- Write-Host " Restart: cd $DEPLOY_DIR; docker compose restart" -ForegroundColor Yellow
- Write-Host ""
-
- Write-Host "⚠️ IMPORTANT: Please save the admin token in a secure location!" -ForegroundColor Red
- Write-Host ""
- }
- function Main {
- Show-Header
-
- if (-not (Test-DockerInstalled)) {
- Show-DockerInstallInstructions
- exit 1
- }
-
- Select-Branch
-
- New-RandomSuffix
- New-AdminToken
- New-DbPassword
-
- New-DeploymentDirectory
- Write-ComposeFile
- Write-EnvFile
-
- Start-Services
-
- $isHealthy = Wait-ForHealth
-
- if ($isHealthy) {
- Show-SuccessMessage
- }
- else {
- Write-ColorOutput "Deployment completed but some services may not be fully healthy yet" -Type Warning
- Write-ColorOutput "Please check the logs: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
- Show-SuccessMessage
- }
- }
- # Run main function
- Main
|