2
0

deploy.ps1 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  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. # Cookie Security
  432. ENABLE_SECURE_COOKIES=$secureCookies
  433. # Circuit Breaker Configuration
  434. ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
  435. # Environment
  436. NODE_ENV=production
  437. TZ=Asia/Shanghai
  438. LOG_LEVEL=info
  439. "@
  440. try {
  441. Set-Content -Path "$DEPLOY_DIR\.env" -Value $envContent -Encoding UTF8
  442. # W-015: Restrict .env file permissions (equivalent to chmod 600)
  443. # Remove inheritance and set owner-only access
  444. $envFile = "$DEPLOY_DIR\.env"
  445. $acl = Get-Acl $envFile
  446. $acl.SetAccessRuleProtection($true, $false) # Disable inheritance, don't copy existing rules
  447. $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
  448. $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
  449. $currentUser,
  450. "FullControl",
  451. "Allow"
  452. )
  453. $acl.SetAccessRule($accessRule)
  454. Set-Acl -Path $envFile -AclObject $acl
  455. Write-ColorOutput ".env file created" -Type Success
  456. }
  457. catch {
  458. Write-ColorOutput "Failed to write .env file: $_" -Type Error
  459. exit 1
  460. }
  461. }
  462. function Start-Services {
  463. Write-ColorOutput "Starting Docker services..." -Type Info
  464. try {
  465. Push-Location $DEPLOY_DIR
  466. docker compose pull
  467. if ($LASTEXITCODE -ne 0) {
  468. throw "Failed to pull Docker images"
  469. }
  470. docker compose up -d
  471. if ($LASTEXITCODE -ne 0) {
  472. throw "Failed to start services"
  473. }
  474. Pop-Location
  475. Write-ColorOutput "Docker services started" -Type Success
  476. }
  477. catch {
  478. Pop-Location
  479. Write-ColorOutput "Failed to start services: $_" -Type Error
  480. exit 1
  481. }
  482. }
  483. function Wait-ForHealth {
  484. Write-ColorOutput "Waiting for services to become healthy (max 60 seconds)..." -Type Info
  485. $maxAttempts = 12
  486. $attempt = 0
  487. Push-Location $DEPLOY_DIR
  488. while ($attempt -lt $maxAttempts) {
  489. $attempt++
  490. try {
  491. $postgresHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-db-$SUFFIX" 2>$null)
  492. $redisHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-redis-$SUFFIX" 2>$null)
  493. $appHealth = (docker inspect --format='{{.State.Health.Status}}' "claude-code-hub-app-$SUFFIX" 2>$null)
  494. if (-not $postgresHealth) { $postgresHealth = "unknown" }
  495. if (-not $redisHealth) { $redisHealth = "unknown" }
  496. if (-not $appHealth) { $appHealth = "unknown" }
  497. Write-ColorOutput "Health status - Postgres: $postgresHealth, Redis: $redisHealth, App: $appHealth" -Type Info
  498. if ($postgresHealth -eq "healthy" -and $redisHealth -eq "healthy" -and $appHealth -eq "healthy") {
  499. Pop-Location
  500. Write-ColorOutput "All services are healthy!" -Type Success
  501. return $true
  502. }
  503. }
  504. catch {
  505. # Continue waiting
  506. }
  507. if ($attempt -lt $maxAttempts) {
  508. Start-Sleep -Seconds 5
  509. }
  510. }
  511. Pop-Location
  512. Write-ColorOutput "Services did not become healthy within 60 seconds" -Type Warning
  513. Write-ColorOutput "You can check the logs with: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
  514. return $false
  515. }
  516. function Get-NetworkAddresses {
  517. $addresses = @()
  518. try {
  519. $adapters = Get-NetIPAddress -AddressFamily IPv4 |
  520. Where-Object {
  521. $_.InterfaceAlias -notlike '*Loopback*' -and
  522. $_.InterfaceAlias -notlike '*Docker*' -and
  523. $_.IPAddress -notlike '169.254.*'
  524. }
  525. foreach ($adapter in $adapters) {
  526. $addresses += $adapter.IPAddress
  527. }
  528. }
  529. catch {
  530. # Silently continue
  531. }
  532. $addresses += "localhost"
  533. return $addresses
  534. }
  535. function Show-SuccessMessage {
  536. $addresses = Get-NetworkAddresses
  537. Write-Host ""
  538. Write-Host "+================================================================+" -ForegroundColor Green
  539. Write-Host "| |" -ForegroundColor Green
  540. Write-Host "| Claude Code Hub Deployed Successfully! |" -ForegroundColor Green
  541. Write-Host "| |" -ForegroundColor Green
  542. Write-Host "+================================================================+" -ForegroundColor Green
  543. Write-Host ""
  544. Write-Host "Deployment Directory:" -ForegroundColor Blue
  545. Write-Host " $DEPLOY_DIR"
  546. Write-Host ""
  547. Write-Host "Access URLs:" -ForegroundColor Blue
  548. if ($script:ENABLE_CADDY) {
  549. if ($script:DOMAIN_ARG) {
  550. # HTTPS mode with domain
  551. Write-Host " https://$($script:DOMAIN_ARG)" -ForegroundColor Green
  552. }
  553. else {
  554. # HTTP-only Caddy mode
  555. foreach ($addr in $addresses) {
  556. Write-Host " http://${addr}" -ForegroundColor Green
  557. }
  558. }
  559. }
  560. else {
  561. # Direct app access
  562. foreach ($addr in $addresses) {
  563. Write-Host " http://${addr}:$($script:APP_PORT)" -ForegroundColor Green
  564. }
  565. }
  566. Write-Host ""
  567. Write-Host "Admin Token (KEEP THIS SECRET!):" -ForegroundColor Blue
  568. Write-Host " $ADMIN_TOKEN" -ForegroundColor Yellow
  569. Write-Host ""
  570. Write-Host "Usage Documentation:" -ForegroundColor Blue
  571. if ($script:ENABLE_CADDY -and $script:DOMAIN_ARG) {
  572. Write-Host " Chinese: https://$($script:DOMAIN_ARG)/zh-CN/usage-doc" -ForegroundColor Green
  573. Write-Host " English: https://$($script:DOMAIN_ARG)/en-US/usage-doc" -ForegroundColor Green
  574. }
  575. else {
  576. $firstAddr = $addresses[0]
  577. $portSuffix = ""
  578. if (-not $script:ENABLE_CADDY) {
  579. $portSuffix = ":$($script:APP_PORT)"
  580. }
  581. Write-Host " Chinese: http://${firstAddr}${portSuffix}/zh-CN/usage-doc" -ForegroundColor Green
  582. Write-Host " English: http://${firstAddr}${portSuffix}/en-US/usage-doc" -ForegroundColor Green
  583. }
  584. Write-Host ""
  585. Write-Host "Useful Commands:" -ForegroundColor Blue
  586. Write-Host " View logs: cd $DEPLOY_DIR; docker compose logs -f" -ForegroundColor Yellow
  587. Write-Host " Stop services: cd $DEPLOY_DIR; docker compose down" -ForegroundColor Yellow
  588. Write-Host " Restart: cd $DEPLOY_DIR; docker compose restart" -ForegroundColor Yellow
  589. if ($script:ENABLE_CADDY) {
  590. Write-Host ""
  591. Write-Host "Caddy Configuration:" -ForegroundColor Blue
  592. if ($script:DOMAIN_ARG) {
  593. Write-Host " Mode: HTTPS with Let's Encrypt (domain: $($script:DOMAIN_ARG))"
  594. Write-Host " Ports: 80 (HTTP redirect), 443 (HTTPS)"
  595. }
  596. else {
  597. Write-Host " Mode: HTTP-only reverse proxy"
  598. Write-Host " Port: 80"
  599. }
  600. }
  601. Write-Host ""
  602. Write-Host "IMPORTANT: Please save the admin token in a secure location!" -ForegroundColor Red
  603. Write-Host ""
  604. }
  605. function Main {
  606. # Handle help flag first
  607. if ($Help) {
  608. Show-Help
  609. exit 0
  610. }
  611. # Initialize parameters from CLI args
  612. Initialize-Parameters
  613. Show-Header
  614. if (-not (Test-DockerInstalled)) {
  615. Show-DockerInstallInstructions
  616. exit 1
  617. }
  618. Select-Branch
  619. New-RandomSuffix
  620. New-AdminToken
  621. New-DbPassword
  622. New-DeploymentDirectory
  623. Write-ComposeFile
  624. Write-Caddyfile
  625. Write-EnvFile
  626. Start-Services
  627. $isHealthy = Wait-ForHealth
  628. if ($isHealthy) {
  629. Show-SuccessMessage
  630. }
  631. else {
  632. Write-ColorOutput "Deployment completed but some services may not be fully healthy yet" -Type Warning
  633. Write-ColorOutput "Please check the logs: cd $DEPLOY_DIR; docker compose logs -f" -Type Info
  634. Show-SuccessMessage
  635. }
  636. }
  637. # Run main function
  638. Main