deploy.ps1 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. # Claude Code Hub - One-Click Deployment Script for Windows
  2. # PowerShell 5.1+ required
  3. #Requires -Version 5.1
  4. # Script version
  5. $VERSION = "1.0.0"
  6. # Global variables
  7. $SUFFIX = ""
  8. $ADMIN_TOKEN = ""
  9. $DB_PASSWORD = ""
  10. $DEPLOY_DIR = "C:\ProgramData\claude-code-hub"
  11. $IMAGE_TAG = "latest"
  12. $BRANCH_NAME = "main"
  13. function Write-ColorOutput {
  14. param(
  15. [string]$Message,
  16. [string]$Type = "Info"
  17. )
  18. switch ($Type) {
  19. "Header" { Write-Host $Message -ForegroundColor Cyan }
  20. "Info" { Write-Host "[INFO] $Message" -ForegroundColor Blue }
  21. "Success" { Write-Host "[SUCCESS] $Message" -ForegroundColor Green }
  22. "Warning" { Write-Host "[WARNING] $Message" -ForegroundColor Yellow }
  23. "Error" { Write-Host "[ERROR] $Message" -ForegroundColor Red }
  24. default { Write-Host $Message }
  25. }
  26. }
  27. function Show-Header {
  28. Write-ColorOutput "╔════════════════════════════════════════════════════════════════╗" -Type Header
  29. Write-ColorOutput "║ ║" -Type Header
  30. Write-ColorOutput "║ Claude Code Hub - One-Click Deployment ║" -Type Header
  31. Write-ColorOutput "║ Version $VERSION ║" -Type Header
  32. Write-ColorOutput "║ ║" -Type Header
  33. Write-ColorOutput "╚════════════════════════════════════════════════════════════════╝" -Type Header
  34. Write-Host ""
  35. }
  36. function Test-DockerInstalled {
  37. Write-ColorOutput "Checking Docker installation..." -Type Info
  38. try {
  39. $dockerVersion = docker --version 2>$null
  40. if ($LASTEXITCODE -ne 0) {
  41. Write-ColorOutput "Docker is not installed" -Type Warning
  42. return $false
  43. }
  44. $composeVersion = docker compose version 2>$null
  45. if ($LASTEXITCODE -ne 0) {
  46. Write-ColorOutput "Docker Compose is not installed" -Type Warning
  47. return $false
  48. }
  49. Write-ColorOutput "Docker and Docker Compose are installed" -Type Success
  50. Write-Host $dockerVersion
  51. Write-Host $composeVersion
  52. return $true
  53. }
  54. catch {
  55. Write-ColorOutput "Docker is not installed" -Type Warning
  56. return $false
  57. }
  58. }
  59. function Show-DockerInstallInstructions {
  60. Write-ColorOutput "Docker is not installed on this system" -Type Error
  61. Write-Host ""
  62. Write-ColorOutput "Please install Docker Desktop for Windows:" -Type Info
  63. Write-Host " 1. Download from: https://www.docker.com/products/docker-desktop/" -ForegroundColor Cyan
  64. Write-Host " 2. Run the installer and follow the instructions"
  65. Write-Host " 3. Restart your computer after installation"
  66. Write-Host " 4. Run this script again"
  67. Write-Host ""
  68. Write-ColorOutput "Press any key to open Docker Desktop download page..." -Type Info
  69. $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
  70. Start-Process "https://www.docker.com/products/docker-desktop/"
  71. exit 1
  72. }
  73. function Select-Branch {
  74. Write-Host ""
  75. Write-ColorOutput "Please select the branch to deploy:" -Type Info
  76. Write-Host " 1) main (Stable release - recommended for production)" -ForegroundColor Green
  77. Write-Host " 2) dev (Latest features - for testing)" -ForegroundColor Yellow
  78. Write-Host ""
  79. while ($true) {
  80. $choice = Read-Host "Enter your choice [1]"
  81. if ([string]::IsNullOrWhiteSpace($choice)) {
  82. $choice = "1"
  83. }
  84. switch ($choice) {
  85. "1" {
  86. $script:IMAGE_TAG = "latest"
  87. $script:BRANCH_NAME = "main"
  88. Write-ColorOutput "Selected branch: main (image tag: latest)" -Type Success
  89. break
  90. }
  91. "2" {
  92. $script:IMAGE_TAG = "dev"
  93. $script:BRANCH_NAME = "dev"
  94. Write-ColorOutput "Selected branch: dev (image tag: dev)" -Type Success
  95. break
  96. }
  97. default {
  98. Write-ColorOutput "Invalid choice. Please enter 1 or 2." -Type Error
  99. continue
  100. }
  101. }
  102. break
  103. }
  104. }
  105. function New-RandomSuffix {
  106. $chars = "abcdefghijklmnopqrstuvwxyz0123456789"
  107. $script:SUFFIX = -join ((1..4) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] })
  108. Write-ColorOutput "Generated random suffix: $SUFFIX" -Type Info
  109. }
  110. function New-AdminToken {
  111. $bytes = New-Object byte[] 24
  112. $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
  113. $rng.GetBytes($bytes)
  114. $script:ADMIN_TOKEN = [Convert]::ToBase64String($bytes) -replace '[/+=]', '' | Select-Object -First 32
  115. $script:ADMIN_TOKEN = $script:ADMIN_TOKEN.Substring(0, 32)
  116. Write-ColorOutput "Generated secure admin token" -Type Info
  117. }
  118. function New-DbPassword {
  119. $bytes = New-Object byte[] 18
  120. $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
  121. $rng.GetBytes($bytes)
  122. $script:DB_PASSWORD = [Convert]::ToBase64String($bytes) -replace '[/+=]', '' | Select-Object -First 24
  123. $script:DB_PASSWORD = $script:DB_PASSWORD.Substring(0, 24)
  124. Write-ColorOutput "Generated secure database password" -Type Info
  125. }
  126. function New-DeploymentDirectory {
  127. Write-ColorOutput "Creating deployment directory: $DEPLOY_DIR" -Type Info
  128. try {
  129. if (-not (Test-Path $DEPLOY_DIR)) {
  130. New-Item -ItemType Directory -Path $DEPLOY_DIR -Force | Out-Null
  131. }
  132. New-Item -ItemType Directory -Path "$DEPLOY_DIR\data\postgres" -Force | Out-Null
  133. New-Item -ItemType Directory -Path "$DEPLOY_DIR\data\redis" -Force | Out-Null
  134. Write-ColorOutput "Deployment directory created" -Type Success
  135. }
  136. catch {
  137. Write-ColorOutput "Failed to create deployment directory: $_" -Type Error
  138. exit 1
  139. }
  140. }
  141. function Write-ComposeFile {
  142. Write-ColorOutput "Writing docker-compose.yaml..." -Type Info
  143. $composeContent = @"
  144. services:
  145. postgres:
  146. image: postgres:18
  147. container_name: claude-code-hub-db-$SUFFIX
  148. restart: unless-stopped
  149. ports:
  150. - "35432:5432"
  151. env_file:
  152. - ./.env
  153. environment:
  154. POSTGRES_USER: `${DB_USER:-postgres}
  155. POSTGRES_PASSWORD: `${DB_PASSWORD:-postgres}
  156. POSTGRES_DB: `${DB_NAME:-claude_code_hub}
  157. PGDATA: /data/pgdata
  158. TZ: Asia/Shanghai
  159. PGTZ: Asia/Shanghai
  160. volumes:
  161. - ./data/postgres:/data
  162. networks:
  163. - claude-code-hub-net-$SUFFIX
  164. healthcheck:
  165. test: ["CMD-SHELL", "pg_isready -U `${DB_USER:-postgres} -d `${DB_NAME:-claude_code_hub}"]
  166. interval: 5s
  167. timeout: 5s
  168. retries: 10
  169. start_period: 10s
  170. redis:
  171. image: redis:7-alpine
  172. container_name: claude-code-hub-redis-$SUFFIX
  173. restart: unless-stopped
  174. volumes:
  175. - ./data/redis:/data
  176. command: redis-server --appendonly yes
  177. networks:
  178. - claude-code-hub-net-$SUFFIX
  179. healthcheck:
  180. test: ["CMD", "redis-cli", "ping"]
  181. interval: 5s
  182. timeout: 3s
  183. retries: 5
  184. start_period: 5s
  185. app:
  186. image: ghcr.io/ding113/claude-code-hub:$IMAGE_TAG
  187. container_name: claude-code-hub-app-$SUFFIX
  188. depends_on:
  189. postgres:
  190. condition: service_healthy
  191. redis:
  192. condition: service_healthy
  193. env_file:
  194. - ./.env
  195. environment:
  196. NODE_ENV: production
  197. PORT: `${APP_PORT:-23000}
  198. DSN: postgresql://`${DB_USER:-postgres}:`${DB_PASSWORD:-postgres}@claude-code-hub-db-${SUFFIX}:5432/`${DB_NAME:-claude_code_hub}
  199. REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379
  200. AUTO_MIGRATE: `${AUTO_MIGRATE:-true}
  201. ENABLE_RATE_LIMIT: `${ENABLE_RATE_LIMIT:-true}
  202. SESSION_TTL: `${SESSION_TTL:-300}
  203. TZ: Asia/Shanghai
  204. ports:
  205. - "`${APP_PORT:-23000}:`${APP_PORT:-23000}"
  206. restart: unless-stopped
  207. networks:
  208. - claude-code-hub-net-$SUFFIX
  209. healthcheck:
  210. test: ["CMD-SHELL", "curl -f http://localhost:`${APP_PORT:-23000}/api/actions/health || exit 1"]
  211. interval: 30s
  212. timeout: 5s
  213. retries: 3
  214. start_period: 30s
  215. networks:
  216. claude-code-hub-net-${SUFFIX}:
  217. driver: bridge
  218. name: claude-code-hub-net-$SUFFIX
  219. "@
  220. try {
  221. Set-Content -Path "$DEPLOY_DIR\docker-compose.yaml" -Value $composeContent -Encoding UTF8
  222. Write-ColorOutput "docker-compose.yaml created" -Type Success
  223. }
  224. catch {
  225. Write-ColorOutput "Failed to write docker-compose.yaml: $_" -Type Error
  226. exit 1
  227. }
  228. }
  229. function Write-EnvFile {
  230. Write-ColorOutput "Writing .env file..." -Type Info
  231. $envContent = @"
  232. # Admin Token (KEEP THIS SECRET!)
  233. ADMIN_TOKEN=$ADMIN_TOKEN
  234. # Database Configuration
  235. DB_USER=postgres
  236. DB_PASSWORD=$DB_PASSWORD
  237. DB_NAME=claude_code_hub
  238. # Application Configuration
  239. APP_PORT=23000
  240. APP_URL=
  241. # Auto Migration (enabled for first-time setup)
  242. AUTO_MIGRATE=true
  243. # Redis Configuration
  244. ENABLE_RATE_LIMIT=true
  245. # Session Configuration
  246. SESSION_TTL=300
  247. STORE_SESSION_MESSAGES=false
  248. # Cookie Security
  249. ENABLE_SECURE_COOKIES=true
  250. # Circuit Breaker Configuration
  251. ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
  252. # Environment
  253. NODE_ENV=production
  254. TZ=Asia/Shanghai
  255. LOG_LEVEL=info
  256. "@
  257. try {
  258. Set-Content -Path "$DEPLOY_DIR\.env" -Value $envContent -Encoding UTF8
  259. Write-ColorOutput ".env file created" -Type Success
  260. }
  261. catch {
  262. Write-ColorOutput "Failed to write .env file: $_" -Type Error
  263. exit 1
  264. }
  265. }
  266. function Start-Services {
  267. Write-ColorOutput "Starting Docker services..." -Type Info
  268. try {
  269. Push-Location $DEPLOY_DIR
  270. docker compose pull
  271. if ($LASTEXITCODE -ne 0) {
  272. throw "Failed to pull Docker images"
  273. }
  274. docker compose up -d
  275. if ($LASTEXITCODE -ne 0) {
  276. throw "Failed to start services"
  277. }
  278. Pop-Location
  279. Write-ColorOutput "Docker services started" -Type Success
  280. }
  281. catch {
  282. Pop-Location
  283. Write-ColorOutput "Failed to start services: $_" -Type Error
  284. exit 1
  285. }
  286. }
  287. function Wait-ForHealth {
  288. Write-ColorOutput "Waiting for services to become healthy (max 60 seconds)..." -Type Info
  289. $maxAttempts = 12
  290. $attempt = 0
  291. Push-Location $DEPLOY_DIR
  292. while ($attempt -lt $maxAttempts) {
  293. $attempt++
  294. try {
  295. $postgresHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-$SUFFIX" 2>$null)
  296. $redisHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-$SUFFIX" 2>$null)
  297. $appHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-$SUFFIX" 2>$null)
  298. if (-not $postgresHealth) { $postgresHealth = "unknown" }
  299. if (-not $redisHealth) { $redisHealth = "unknown" }
  300. if (-not $appHealth) { $appHealth = "unknown" }
  301. Write-ColorOutput "Health status - Postgres: $postgresHealth, Redis: $redisHealth, App: $appHealth" -Type Info
  302. if ($postgresHealth -eq "healthy" -and $redisHealth -eq "healthy" -and $appHealth -eq "healthy") {
  303. Pop-Location
  304. Write-ColorOutput "All services are healthy!" -Type Success
  305. return $true
  306. }
  307. }
  308. catch {
  309. # Continue waiting
  310. }
  311. if ($attempt -lt $maxAttempts) {
  312. Start-Sleep -Seconds 5
  313. }
  314. }
  315. Pop-Location
  316. Write-ColorOutput "Services did not become healthy within 60 seconds" -Type Warning
  317. Write-ColorOutput "You can check the logs with: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
  318. return $false
  319. }
  320. function Get-NetworkAddresses {
  321. $addresses = @()
  322. try {
  323. $adapters = Get-NetIPAddress -AddressFamily IPv4 |
  324. Where-Object {
  325. $_.InterfaceAlias -notlike '*Loopback*' -and
  326. $_.InterfaceAlias -notlike '*Docker*' -and
  327. $_.IPAddress -notlike '169.254.*'
  328. }
  329. foreach ($adapter in $adapters) {
  330. $addresses += $adapter.IPAddress
  331. }
  332. }
  333. catch {
  334. # Silently continue
  335. }
  336. $addresses += "localhost"
  337. return $addresses
  338. }
  339. function Show-SuccessMessage {
  340. $addresses = Get-NetworkAddresses
  341. Write-Host ""
  342. Write-Host "╔════════════════════════════════════════════════════════════════╗" -ForegroundColor Green
  343. Write-Host "║ ║" -ForegroundColor Green
  344. Write-Host "║ 🎉 Claude Code Hub Deployed Successfully! 🎉 ║" -ForegroundColor Green
  345. Write-Host "║ ║" -ForegroundColor Green
  346. Write-Host "╚════════════════════════════════════════════════════════════════╝" -ForegroundColor Green
  347. Write-Host ""
  348. Write-Host "📍 Deployment Directory:" -ForegroundColor Blue
  349. Write-Host " $DEPLOY_DIR"
  350. Write-Host ""
  351. Write-Host "🌐 Access URLs:" -ForegroundColor Blue
  352. foreach ($addr in $addresses) {
  353. Write-Host " http://${addr}:23000" -ForegroundColor Green
  354. }
  355. Write-Host ""
  356. Write-Host "🔑 Admin Token (KEEP THIS SECRET!):" -ForegroundColor Blue
  357. Write-Host " $ADMIN_TOKEN" -ForegroundColor Yellow
  358. Write-Host ""
  359. Write-Host "📚 Usage Documentation:" -ForegroundColor Blue
  360. $firstAddr = $addresses[0]
  361. Write-Host " Chinese: http://${firstAddr}:23000/zh-CN/usage-doc" -ForegroundColor Green
  362. Write-Host " English: http://${firstAddr}:23000/en-US/usage-doc" -ForegroundColor Green
  363. Write-Host ""
  364. Write-Host "🔧 Useful Commands:" -ForegroundColor Blue
  365. Write-Host " View logs: cd $DEPLOY_DIR; docker compose logs -f" -ForegroundColor Yellow
  366. Write-Host " Stop services: cd $DEPLOY_DIR; docker compose down" -ForegroundColor Yellow
  367. Write-Host " Restart: cd $DEPLOY_DIR; docker compose restart" -ForegroundColor Yellow
  368. Write-Host ""
  369. Write-Host "⚠️ IMPORTANT: Please save the admin token in a secure location!" -ForegroundColor Red
  370. Write-Host ""
  371. }
  372. function Main {
  373. Show-Header
  374. if (-not (Test-DockerInstalled)) {
  375. Show-DockerInstallInstructions
  376. exit 1
  377. }
  378. Select-Branch
  379. New-RandomSuffix
  380. New-AdminToken
  381. New-DbPassword
  382. New-DeploymentDirectory
  383. Write-ComposeFile
  384. Write-EnvFile
  385. Start-Services
  386. $isHealthy = Wait-ForHealth
  387. if ($isHealthy) {
  388. Show-SuccessMessage
  389. }
  390. else {
  391. Write-ColorOutput "Deployment completed but some services may not be fully healthy yet" -Type Warning
  392. Write-ColorOutput "Please check the logs: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
  393. Show-SuccessMessage
  394. }
  395. }
  396. # Run main function
  397. Main