deploy.ps1 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. # Claude Code Hub - One-Click Deployment Script for Windows
  2. # PowerShell 5.1+ required
  3. #Requires -Version 5.1
  4. [CmdletBinding()]
  5. param(
  6. [Alias("b")]
  7. [ValidateSet("main", "dev", "")]
  8. [string]$Branch = "",
  9. [Alias("p")]
  10. [ValidateRange(1, 65535)]
  11. [int]$Port = 0,
  12. [Alias("t")]
  13. [string]$AdminToken = "",
  14. [Alias("d")]
  15. [string]$DeployDir = "",
  16. [string]$Domain = "",
  17. [switch]$EnableCaddy,
  18. [Alias("y")]
  19. [switch]$Yes,
  20. [Alias("h")]
  21. [switch]$Help
  22. )
  23. # Script version
  24. $VERSION = "1.1.0"
  25. # Global variables
  26. $script:SUFFIX = ""
  27. $script:ADMIN_TOKEN = ""
  28. $script:DB_PASSWORD = ""
  29. $script:DEPLOY_DIR = "C:\ProgramData\claude-code-hub"
  30. $script:IMAGE_TAG = "latest"
  31. $script:BRANCH_NAME = "main"
  32. $script:APP_PORT = "23000"
  33. $script:ENABLE_CADDY = $false
  34. $script:DOMAIN_ARG = ""
  35. function Show-Help {
  36. $helpText = @"
  37. Claude Code Hub - One-Click Deployment Script v$VERSION
  38. Usage: .\deploy.ps1 [OPTIONS]
  39. Options:
  40. -Branch, -b <name> Branch to deploy: main (default) or dev
  41. -Port, -p <port> App external port (default: 23000)
  42. -AdminToken, -t <token> Custom admin token (default: auto-generated)
  43. -DeployDir, -d <path> Custom deployment directory
  44. -Domain <domain> Domain for Caddy HTTPS (enables Caddy automatically)
  45. -EnableCaddy Enable Caddy reverse proxy without HTTPS (HTTP only)
  46. -Yes, -y Non-interactive mode (skip prompts, use defaults)
  47. -Help, -h Show this help message
  48. Examples:
  49. .\deploy.ps1 # Interactive deployment
  50. .\deploy.ps1 -Yes # Non-interactive with defaults
  51. .\deploy.ps1 -Branch dev -Port 8080 -Yes # Deploy dev branch on port 8080
  52. .\deploy.ps1 -AdminToken "my-secure-token" -Yes # Use custom admin token
  53. .\deploy.ps1 -Domain hub.example.com -Yes # Deploy with Caddy HTTPS
  54. .\deploy.ps1 -EnableCaddy -Yes # Deploy with Caddy HTTP-only
  55. For more information, visit: https://github.com/ding113/claude-code-hub
  56. "@
  57. Write-Host $helpText
  58. }
  59. function Initialize-Parameters {
  60. # Apply CLI parameters
  61. if ($Branch) {
  62. if ($Branch -eq "main") {
  63. $script:IMAGE_TAG = "latest"
  64. $script:BRANCH_NAME = "main"
  65. } elseif ($Branch -eq "dev") {
  66. $script:IMAGE_TAG = "dev"
  67. $script:BRANCH_NAME = "dev"
  68. }
  69. }
  70. if ($Port -gt 0) {
  71. $script:APP_PORT = $Port.ToString()
  72. }
  73. if ($AdminToken) {
  74. if ($AdminToken.Length -lt 16) {
  75. Write-ColorOutput "Admin token too short: minimum 16 characters required" -Type Error
  76. exit 1
  77. }
  78. $script:ADMIN_TOKEN = $AdminToken
  79. }
  80. if ($DeployDir) {
  81. $script:DEPLOY_DIR = $DeployDir
  82. }
  83. if ($Domain) {
  84. # Validate domain format
  85. if ($Domain -notmatch '^[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])?)*$') {
  86. Write-ColorOutput "Invalid domain format: $Domain" -Type Error
  87. exit 1
  88. }
  89. $script:DOMAIN_ARG = $Domain
  90. $script:ENABLE_CADDY = $true
  91. }
  92. if ($EnableCaddy) {
  93. $script:ENABLE_CADDY = $true
  94. }
  95. }
  96. function Write-ColorOutput {
  97. param(
  98. [string]$Message,
  99. [string]$Type = "Info"
  100. )
  101. switch ($Type) {
  102. "Header" { Write-Host $Message -ForegroundColor Cyan }
  103. "Info" { Write-Host "[INFO] $Message" -ForegroundColor Blue }
  104. "Success" { Write-Host "[SUCCESS] $Message" -ForegroundColor Green }
  105. "Warning" { Write-Host "[WARNING] $Message" -ForegroundColor Yellow }
  106. "Error" { Write-Host "[ERROR] $Message" -ForegroundColor Red }
  107. default { Write-Host $Message }
  108. }
  109. }
  110. function Show-Header {
  111. Write-ColorOutput "+=================================================================+" -Type Header
  112. Write-ColorOutput "| |" -Type Header
  113. Write-ColorOutput "| Claude Code Hub - One-Click Deployment |" -Type Header
  114. Write-ColorOutput "| Version $VERSION |" -Type Header
  115. Write-ColorOutput "| |" -Type Header
  116. Write-ColorOutput "+=================================================================+" -Type Header
  117. Write-Host ""
  118. }
  119. function Test-DockerInstalled {
  120. Write-ColorOutput "Checking Docker installation..." -Type Info
  121. try {
  122. $dockerVersion = docker --version 2>$null
  123. if ($LASTEXITCODE -ne 0) {
  124. Write-ColorOutput "Docker is not installed" -Type Warning
  125. return $false
  126. }
  127. $composeVersion = docker compose version 2>$null
  128. if ($LASTEXITCODE -ne 0) {
  129. Write-ColorOutput "Docker Compose is not installed" -Type Warning
  130. return $false
  131. }
  132. Write-ColorOutput "Docker and Docker Compose are installed" -Type Success
  133. Write-Host $dockerVersion
  134. Write-Host $composeVersion
  135. return $true
  136. }
  137. catch {
  138. Write-ColorOutput "Docker is not installed" -Type Warning
  139. return $false
  140. }
  141. }
  142. function Show-DockerInstallInstructions {
  143. Write-ColorOutput "Docker is not installed on this system" -Type Error
  144. Write-Host ""
  145. Write-ColorOutput "Please install Docker Desktop for Windows:" -Type Info
  146. Write-Host " 1. Download from: https://www.docker.com/products/docker-desktop/" -ForegroundColor Cyan
  147. Write-Host " 2. Run the installer and follow the instructions"
  148. Write-Host " 3. Restart your computer after installation"
  149. Write-Host " 4. Run this script again"
  150. Write-Host ""
  151. Write-ColorOutput "Press any key to open Docker Desktop download page..." -Type Info
  152. $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
  153. Start-Process "https://www.docker.com/products/docker-desktop/"
  154. exit 1
  155. }
  156. function Select-Branch {
  157. # Skip if branch already set via CLI or non-interactive mode
  158. if ($Branch) {
  159. Write-ColorOutput "Using branch from CLI argument: $script:BRANCH_NAME" -Type Info
  160. return
  161. }
  162. if ($Yes) {
  163. Write-ColorOutput "Non-interactive mode: using default branch (main)" -Type Info
  164. return
  165. }
  166. Write-Host ""
  167. Write-ColorOutput "Please select the branch to deploy:" -Type Info
  168. Write-Host " 1) main (Stable release - recommended for production)" -ForegroundColor Green
  169. Write-Host " 2) dev (Latest features - for testing)" -ForegroundColor Yellow
  170. Write-Host ""
  171. while ($true) {
  172. $choice = Read-Host "Enter your choice [1]"
  173. if ([string]::IsNullOrWhiteSpace($choice)) {
  174. $choice = "1"
  175. }
  176. switch ($choice) {
  177. "1" {
  178. $script:IMAGE_TAG = "latest"
  179. $script:BRANCH_NAME = "main"
  180. Write-ColorOutput "Selected branch: main (image tag: latest)" -Type Success
  181. break
  182. }
  183. "2" {
  184. $script:IMAGE_TAG = "dev"
  185. $script:BRANCH_NAME = "dev"
  186. Write-ColorOutput "Selected branch: dev (image tag: dev)" -Type Success
  187. break
  188. }
  189. default {
  190. Write-ColorOutput "Invalid choice. Please enter 1 or 2." -Type Error
  191. continue
  192. }
  193. }
  194. break
  195. }
  196. }
  197. function New-RandomSuffix {
  198. $chars = "abcdefghijklmnopqrstuvwxyz0123456789"
  199. $script:SUFFIX = -join ((1..4) | ForEach-Object { $chars[(Get-Random -Maximum $chars.Length)] })
  200. Write-ColorOutput "Generated random suffix: $SUFFIX" -Type Info
  201. }
  202. function New-AdminToken {
  203. # Skip if token already set via CLI
  204. if ($script:ADMIN_TOKEN) {
  205. Write-ColorOutput "Using admin token from CLI argument" -Type Info
  206. return
  207. }
  208. # Generate more bytes to ensure we have enough after removing special chars
  209. $bytes = New-Object byte[] 48
  210. $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
  211. $rng.GetBytes($bytes)
  212. $rng.Dispose()
  213. $token = [Convert]::ToBase64String($bytes) -replace '[/+=]', ''
  214. $script:ADMIN_TOKEN = $token.Substring(0, [Math]::Min(32, $token.Length))
  215. Write-ColorOutput "Generated secure admin token" -Type Info
  216. }
  217. function New-DbPassword {
  218. # Generate more bytes to ensure we have enough after removing special chars
  219. $bytes = New-Object byte[] 36
  220. $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
  221. $rng.GetBytes($bytes)
  222. $rng.Dispose()
  223. $password = [Convert]::ToBase64String($bytes) -replace '[/+=]', ''
  224. $script:DB_PASSWORD = $password.Substring(0, [Math]::Min(24, $password.Length))
  225. Write-ColorOutput "Generated secure database password" -Type Info
  226. }
  227. function New-DeploymentDirectory {
  228. Write-ColorOutput "Creating deployment directory: $DEPLOY_DIR" -Type Info
  229. try {
  230. if (-not (Test-Path $DEPLOY_DIR)) {
  231. New-Item -ItemType Directory -Path $DEPLOY_DIR -Force | Out-Null
  232. }
  233. New-Item -ItemType Directory -Path "$DEPLOY_DIR\data\postgres" -Force | Out-Null
  234. New-Item -ItemType Directory -Path "$DEPLOY_DIR\data\redis" -Force | Out-Null
  235. Write-ColorOutput "Deployment directory created" -Type Success
  236. }
  237. catch {
  238. Write-ColorOutput "Failed to create deployment directory: $_" -Type Error
  239. exit 1
  240. }
  241. }
  242. function Write-ComposeFile {
  243. Write-ColorOutput "Writing docker-compose.yaml..." -Type Info
  244. # Build ports section for app (only if Caddy is not enabled)
  245. $appPortsSection = ""
  246. if (-not $script:ENABLE_CADDY) {
  247. $appPortsSection = @"
  248. ports:
  249. - "`${APP_PORT:-$($script:APP_PORT)}:`${APP_PORT:-$($script:APP_PORT)}"
  250. "@
  251. }
  252. $composeContent = @"
  253. services:
  254. postgres:
  255. image: postgres:18
  256. container_name: claude-code-hub-db-$SUFFIX
  257. restart: unless-stopped
  258. ports:
  259. - "127.0.0.1:35432:5432"
  260. env_file:
  261. - ./.env
  262. environment:
  263. POSTGRES_USER: `${DB_USER:-postgres}
  264. POSTGRES_PASSWORD: `${DB_PASSWORD:-postgres}
  265. POSTGRES_DB: `${DB_NAME:-claude_code_hub}
  266. PGDATA: /data/pgdata
  267. TZ: Asia/Shanghai
  268. PGTZ: Asia/Shanghai
  269. volumes:
  270. - ./data/postgres:/data
  271. networks:
  272. - claude-code-hub-net-$SUFFIX
  273. healthcheck:
  274. test: ["CMD-SHELL", "pg_isready -U `${DB_USER:-postgres} -d `${DB_NAME:-claude_code_hub}"]
  275. interval: 5s
  276. timeout: 5s
  277. retries: 10
  278. start_period: 10s
  279. redis:
  280. image: redis:7-alpine
  281. container_name: claude-code-hub-redis-$SUFFIX
  282. restart: unless-stopped
  283. volumes:
  284. - ./data/redis:/data
  285. command: redis-server --appendonly yes
  286. networks:
  287. - claude-code-hub-net-$SUFFIX
  288. healthcheck:
  289. test: ["CMD", "redis-cli", "ping"]
  290. interval: 5s
  291. timeout: 3s
  292. retries: 5
  293. start_period: 5s
  294. app:
  295. image: ghcr.io/ding113/claude-code-hub:$IMAGE_TAG
  296. container_name: claude-code-hub-app-$SUFFIX
  297. depends_on:
  298. postgres:
  299. condition: service_healthy
  300. redis:
  301. condition: service_healthy
  302. env_file:
  303. - ./.env
  304. environment:
  305. NODE_ENV: production
  306. PORT: `${APP_PORT:-$($script:APP_PORT)}
  307. DSN: postgresql://`${DB_USER:-postgres}:`${DB_PASSWORD:-postgres}@claude-code-hub-db-${SUFFIX}:5432/`${DB_NAME:-claude_code_hub}
  308. REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379
  309. AUTO_MIGRATE: `${AUTO_MIGRATE:-true}
  310. ENABLE_RATE_LIMIT: `${ENABLE_RATE_LIMIT:-true}
  311. SESSION_TTL: `${SESSION_TTL:-300}
  312. TZ: Asia/Shanghai
  313. $appPortsSection
  314. restart: unless-stopped
  315. networks:
  316. - claude-code-hub-net-$SUFFIX
  317. healthcheck:
  318. test: ["CMD-SHELL", "curl -f http://localhost:`${APP_PORT:-$($script:APP_PORT)}/api/actions/health || exit 1"]
  319. interval: 30s
  320. timeout: 5s
  321. retries: 3
  322. start_period: 30s
  323. "@
  324. # Add Caddy service if enabled
  325. if ($script:ENABLE_CADDY) {
  326. $composeContent += @"
  327. caddy:
  328. image: caddy:2-alpine
  329. container_name: claude-code-hub-caddy-$SUFFIX
  330. restart: unless-stopped
  331. ports:
  332. - "80:80"
  333. - "443:443"
  334. volumes:
  335. - ./Caddyfile:/etc/caddy/Caddyfile:ro
  336. - caddy_data:/data
  337. - caddy_config:/config
  338. depends_on:
  339. app:
  340. condition: service_healthy
  341. networks:
  342. - claude-code-hub-net-$SUFFIX
  343. "@
  344. }
  345. $composeContent += @"
  346. networks:
  347. claude-code-hub-net-${SUFFIX}:
  348. driver: bridge
  349. name: claude-code-hub-net-$SUFFIX
  350. "@
  351. # Add Caddy volumes if enabled
  352. if ($script:ENABLE_CADDY) {
  353. $composeContent += @"
  354. volumes:
  355. caddy_data:
  356. caddy_config:
  357. "@
  358. }
  359. try {
  360. Set-Content -Path "$DEPLOY_DIR\docker-compose.yaml" -Value $composeContent -Encoding UTF8
  361. Write-ColorOutput "docker-compose.yaml created" -Type Success
  362. }
  363. catch {
  364. Write-ColorOutput "Failed to write docker-compose.yaml: $_" -Type Error
  365. exit 1
  366. }
  367. }
  368. function Write-Caddyfile {
  369. if (-not $script:ENABLE_CADDY) {
  370. return
  371. }
  372. Write-ColorOutput "Writing Caddyfile..." -Type Info
  373. if ($script:DOMAIN_ARG) {
  374. # HTTPS mode with domain (Let's Encrypt automatic)
  375. $caddyContent = @"
  376. $($script:DOMAIN_ARG) {
  377. reverse_proxy app:$($script:APP_PORT)
  378. encode gzip
  379. }
  380. "@
  381. Write-ColorOutput "Caddyfile created (HTTPS mode with domain: $($script:DOMAIN_ARG))" -Type Success
  382. }
  383. else {
  384. # HTTP-only mode
  385. $caddyContent = @"
  386. :80 {
  387. reverse_proxy app:$($script:APP_PORT)
  388. encode gzip
  389. }
  390. "@
  391. Write-ColorOutput "Caddyfile created (HTTP-only mode)" -Type Success
  392. }
  393. try {
  394. Set-Content -Path "$DEPLOY_DIR\Caddyfile" -Value $caddyContent -Encoding UTF8
  395. }
  396. catch {
  397. Write-ColorOutput "Failed to write Caddyfile: $_" -Type Error
  398. exit 1
  399. }
  400. }
  401. function Write-EnvFile {
  402. Write-ColorOutput "Writing .env file..." -Type Info
  403. # Determine secure cookies setting based on Caddy and domain
  404. $secureCookies = "true"
  405. if ($script:ENABLE_CADDY -and -not $script:DOMAIN_ARG) {
  406. # HTTP-only Caddy mode - disable secure cookies
  407. $secureCookies = "false"
  408. }
  409. # If domain is set, APP_URL should use https
  410. $appUrl = ""
  411. if ($script:DOMAIN_ARG) {
  412. $appUrl = "https://$($script:DOMAIN_ARG)"
  413. }
  414. $envContent = @"
  415. # Admin Token (KEEP THIS SECRET!)
  416. ADMIN_TOKEN=$ADMIN_TOKEN
  417. # Database Configuration
  418. DB_USER=postgres
  419. DB_PASSWORD=$DB_PASSWORD
  420. DB_NAME=claude_code_hub
  421. # Application Configuration
  422. APP_PORT=$($script:APP_PORT)
  423. APP_URL=$appUrl
  424. # Auto Migration (enabled for first-time setup)
  425. AUTO_MIGRATE=true
  426. # Redis Configuration
  427. ENABLE_RATE_LIMIT=true
  428. # Session Configuration
  429. SESSION_TTL=300
  430. STORE_SESSION_MESSAGES=false
  431. STORE_SESSION_RESPONSE_BODY=true
  432. # Cookie Security
  433. ENABLE_SECURE_COOKIES=$secureCookies
  434. # Circuit Breaker Configuration
  435. ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
  436. # Environment
  437. NODE_ENV=production
  438. TZ=Asia/Shanghai
  439. LOG_LEVEL=info
  440. "@
  441. try {
  442. Set-Content -Path "$DEPLOY_DIR\.env" -Value $envContent -Encoding UTF8
  443. # W-015: Restrict .env file permissions (equivalent to chmod 600)
  444. # Remove inheritance and set owner-only access
  445. $envFile = "$DEPLOY_DIR\.env"
  446. $acl = Get-Acl $envFile
  447. $acl.SetAccessRuleProtection($true, $false) # Disable inheritance, don't copy existing rules
  448. $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
  449. $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
  450. $currentUser,
  451. "FullControl",
  452. "Allow"
  453. )
  454. $acl.SetAccessRule($accessRule)
  455. Set-Acl -Path $envFile -AclObject $acl
  456. Write-ColorOutput ".env file created" -Type Success
  457. }
  458. catch {
  459. Write-ColorOutput "Failed to write .env file: $_" -Type Error
  460. exit 1
  461. }
  462. }
  463. function Start-Services {
  464. Write-ColorOutput "Starting Docker services..." -Type Info
  465. try {
  466. Push-Location $DEPLOY_DIR
  467. docker compose pull
  468. if ($LASTEXITCODE -ne 0) {
  469. throw "Failed to pull Docker images"
  470. }
  471. docker compose up -d
  472. if ($LASTEXITCODE -ne 0) {
  473. throw "Failed to start services"
  474. }
  475. Pop-Location
  476. Write-ColorOutput "Docker services started" -Type Success
  477. }
  478. catch {
  479. Pop-Location
  480. Write-ColorOutput "Failed to start services: $_" -Type Error
  481. exit 1
  482. }
  483. }
  484. function Wait-ForHealth {
  485. Write-ColorOutput "Waiting for services to become healthy (max 60 seconds)..." -Type Info
  486. $maxAttempts = 12
  487. $attempt = 0
  488. Push-Location $DEPLOY_DIR
  489. while ($attempt -lt $maxAttempts) {
  490. $attempt++
  491. try {
  492. $postgresHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-$SUFFIX" 2>$null)
  493. $redisHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-$SUFFIX" 2>$null)
  494. $appHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-$SUFFIX" 2>$null)
  495. if (-not $postgresHealth) { $postgresHealth = "unknown" }
  496. if (-not $redisHealth) { $redisHealth = "unknown" }
  497. if (-not $appHealth) { $appHealth = "unknown" }
  498. Write-ColorOutput "Health status - Postgres: $postgresHealth, Redis: $redisHealth, App: $appHealth" -Type Info
  499. if ($postgresHealth -eq "healthy" -and $redisHealth -eq "healthy" -and $appHealth -eq "healthy") {
  500. Pop-Location
  501. Write-ColorOutput "All services are healthy!" -Type Success
  502. return $true
  503. }
  504. }
  505. catch {
  506. # Continue waiting
  507. }
  508. if ($attempt -lt $maxAttempts) {
  509. Start-Sleep -Seconds 5
  510. }
  511. }
  512. Pop-Location
  513. Write-ColorOutput "Services did not become healthy within 60 seconds" -Type Warning
  514. Write-ColorOutput "You can check the logs with: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
  515. return $false
  516. }
  517. function Get-NetworkAddresses {
  518. $addresses = @()
  519. try {
  520. $adapters = Get-NetIPAddress -AddressFamily IPv4 |
  521. Where-Object {
  522. $_.InterfaceAlias -notlike '*Loopback*' -and
  523. $_.InterfaceAlias -notlike '*Docker*' -and
  524. $_.IPAddress -notlike '169.254.*'
  525. }
  526. foreach ($adapter in $adapters) {
  527. $addresses += $adapter.IPAddress
  528. }
  529. }
  530. catch {
  531. # Silently continue
  532. }
  533. $addresses += "localhost"
  534. return $addresses
  535. }
  536. function Show-SuccessMessage {
  537. $addresses = Get-NetworkAddresses
  538. Write-Host ""
  539. Write-Host "+================================================================+" -ForegroundColor Green
  540. Write-Host "| |" -ForegroundColor Green
  541. Write-Host "| Claude Code Hub Deployed Successfully! |" -ForegroundColor Green
  542. Write-Host "| |" -ForegroundColor Green
  543. Write-Host "+================================================================+" -ForegroundColor Green
  544. Write-Host ""
  545. Write-Host "Deployment Directory:" -ForegroundColor Blue
  546. Write-Host " $DEPLOY_DIR"
  547. Write-Host ""
  548. Write-Host "Access URLs:" -ForegroundColor Blue
  549. if ($script:ENABLE_CADDY) {
  550. if ($script:DOMAIN_ARG) {
  551. # HTTPS mode with domain
  552. Write-Host " https://$($script:DOMAIN_ARG)" -ForegroundColor Green
  553. }
  554. else {
  555. # HTTP-only Caddy mode
  556. foreach ($addr in $addresses) {
  557. Write-Host " http://${addr}" -ForegroundColor Green
  558. }
  559. }
  560. }
  561. else {
  562. # Direct app access
  563. foreach ($addr in $addresses) {
  564. Write-Host " http://${addr}:$($script:APP_PORT)" -ForegroundColor Green
  565. }
  566. }
  567. Write-Host ""
  568. Write-Host "Admin Token (KEEP THIS SECRET!):" -ForegroundColor Blue
  569. Write-Host " $ADMIN_TOKEN" -ForegroundColor Yellow
  570. Write-Host ""
  571. Write-Host "Usage Documentation:" -ForegroundColor Blue
  572. if ($script:ENABLE_CADDY -and $script:DOMAIN_ARG) {
  573. Write-Host " Chinese: https://$($script:DOMAIN_ARG)/zh-CN/usage-doc" -ForegroundColor Green
  574. Write-Host " English: https://$($script:DOMAIN_ARG)/en-US/usage-doc" -ForegroundColor Green
  575. }
  576. else {
  577. $firstAddr = $addresses[0]
  578. $portSuffix = ""
  579. if (-not $script:ENABLE_CADDY) {
  580. $portSuffix = ":$($script:APP_PORT)"
  581. }
  582. Write-Host " Chinese: http://${firstAddr}${portSuffix}/zh-CN/usage-doc" -ForegroundColor Green
  583. Write-Host " English: http://${firstAddr}${portSuffix}/en-US/usage-doc" -ForegroundColor Green
  584. }
  585. Write-Host ""
  586. Write-Host "Useful Commands:" -ForegroundColor Blue
  587. Write-Host " View logs: cd $DEPLOY_DIR; docker compose logs -f" -ForegroundColor Yellow
  588. Write-Host " Stop services: cd $DEPLOY_DIR; docker compose down" -ForegroundColor Yellow
  589. Write-Host " Restart: cd $DEPLOY_DIR; docker compose restart" -ForegroundColor Yellow
  590. if ($script:ENABLE_CADDY) {
  591. Write-Host ""
  592. Write-Host "Caddy Configuration:" -ForegroundColor Blue
  593. if ($script:DOMAIN_ARG) {
  594. Write-Host " Mode: HTTPS with Let's Encrypt (domain: $($script:DOMAIN_ARG))"
  595. Write-Host " Ports: 80 (HTTP redirect), 443 (HTTPS)"
  596. }
  597. else {
  598. Write-Host " Mode: HTTP-only reverse proxy"
  599. Write-Host " Port: 80"
  600. }
  601. }
  602. Write-Host ""
  603. Write-Host "IMPORTANT: Please save the admin token in a secure location!" -ForegroundColor Red
  604. Write-Host ""
  605. }
  606. function Main {
  607. # Handle help flag first
  608. if ($Help) {
  609. Show-Help
  610. exit 0
  611. }
  612. # Initialize parameters from CLI args
  613. Initialize-Parameters
  614. Show-Header
  615. if (-not (Test-DockerInstalled)) {
  616. Show-DockerInstallInstructions
  617. exit 1
  618. }
  619. Select-Branch
  620. New-RandomSuffix
  621. New-AdminToken
  622. New-DbPassword
  623. New-DeploymentDirectory
  624. Write-ComposeFile
  625. Write-Caddyfile
  626. Write-EnvFile
  627. Start-Services
  628. $isHealthy = Wait-ForHealth
  629. if ($isHealthy) {
  630. Show-SuccessMessage
  631. }
  632. else {
  633. Write-ColorOutput "Deployment completed but some services may not be fully healthy yet" -Type Warning
  634. Write-ColorOutput "Please check the logs: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
  635. Show-SuccessMessage
  636. }
  637. }
  638. # Run main function
  639. Main