$ErrorActionPreference = "Stop" $PublicBase = if ($env:CODEX_PILOT_PUBLIC_BASE) { $env:CODEX_PILOT_PUBLIC_BASE.TrimEnd("/") } else { "https://pilot.qiliuai.com" } $WorkRoot = Join-Path $env:TEMP "codex-pilot-self-deploy" $ZipPath = Join-Path $WorkRoot "codex-pilot-external-deploy.zip" $ExtractDir = Join-Path $WorkRoot "package" $ReportDir = Join-Path $env:USERPROFILE ".codex-pilot" $ReportPath = Join-Path $ReportDir "self-deploy-report.txt" $InstallDir = Join-Path $env:USERPROFILE ".codex-pilot\mobile-control" $Port = if ($env:CODEX_DROPBOX_PORT) { $env:CODEX_DROPBOX_PORT } else { "8787" } function Write-Step([string]$Message) { Write-Host "[Codex Pilot] $Message" } function Read-LocalToken { $TokenFile = Join-Path $InstallDir ".token" if (Test-Path $TokenFile) { return (Get-Content $TokenFile -Raw).Trim() } return "" } function Test-ServiceReady { $Token = Read-LocalToken if (!$Token) { return $false } $EncodedToken = [Uri]::EscapeDataString($Token) for ($Attempt = 1; $Attempt -le 20; $Attempt++) { try { $Bootstrap = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/api/bootstrap?token=$EncodedToken" -TimeoutSec 3 if ($Bootstrap.ok) { return $true } } catch {} Start-Sleep -Seconds 1 } return $false } try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} New-Item -ItemType Directory -Force $WorkRoot, $ReportDir | Out-Null if (Test-Path $ExtractDir) { Remove-Item -Recurse -Force $ExtractDir } New-Item -ItemType Directory -Force $ExtractDir | Out-Null Write-Step "读取公开版本信息" $Info = Invoke-RestMethod -Uri "$PublicBase/app-info.json" -TimeoutSec 20 if (!$Info.deploy_package.file -or !$Info.deploy_package.sha256) { throw "公开 app-info.json 缺少部署包信息。" } $DeployUrl = "$PublicBase/$($Info.deploy_package.file)" Write-Step "下载外部部署包 $($Info.version_name)" Invoke-WebRequest -Uri $DeployUrl -OutFile $ZipPath -UseBasicParsing -TimeoutSec 120 $ActualHash = (Get-FileHash -Algorithm SHA256 -Path $ZipPath).Hash.ToLowerInvariant() $ExpectedHash = "$($Info.deploy_package.sha256)".ToLowerInvariant() if ($ActualHash -ne $ExpectedHash) { throw "部署包 SHA256 校验失败:期望 $ExpectedHash,实际 $ActualHash。" } Write-Step "解压部署包" Expand-Archive -Path $ZipPath -DestinationPath $ExtractDir -Force $PackageRoot = Get-ChildItem -Path $ExtractDir -Directory -Recurse | Where-Object { Test-Path (Join-Path $_.FullName "install-windows.ps1") } | Select-Object -First 1 if (!$PackageRoot) { throw "解压后没有找到 Codex Pilot 部署包根目录。" } $HasExistingService = (Test-Path (Join-Path $InstallDir ".token")) -or (Test-Path (Join-Path $InstallDir "win_codex_node_server.py")) -or ($null -ne (Get-ScheduledTask -TaskName "CodexPilotMobileControl" -ErrorAction SilentlyContinue)) $ScriptName = if ($HasExistingService -and (Test-Path (Join-Path $PackageRoot.FullName "repair-windows.ps1"))) { "repair-windows.ps1" } else { "install-windows.ps1" } $ScriptPath = Join-Path $PackageRoot.FullName $ScriptName Write-Step "执行 $ScriptName" $Proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`"" -Wait -PassThru if ($Proc.ExitCode -ne 0) { throw "$ScriptName 执行失败,退出码 $($Proc.ExitCode)。" } $Ready = Test-ServiceReady $ConnectionInfo = Join-Path $InstallDir "connection-info.txt" $Lines = @( "Codex Pilot AI 自部署报告", "版本:$($Info.version_name)", "系统:Windows", "部署脚本:$ScriptName", "部署包 SHA256:$ActualHash", "服务状态:" + $(if ($Ready) { "本机健康检查通过" } else { "未通过,请运行 verify-windows.ps1" }), "连接信息文件:$ConnectionInfo", "扫码页面:http://127.0.0.1:$Port/connect", "", "注意:报告不会写出访问口令。需要连接手机时,请在本机 connection-info.txt 查看 token 或使用扫码页面。" ) $Lines | Set-Content -Encoding UTF8 $ReportPath Write-Host "" Write-Host "Codex Pilot AI 自部署完成" Write-Host "版本:$($Info.version_name)" Write-Host "服务状态:$(if ($Ready) { "健康检查通过" } else { "需要运行 verify-windows.ps1 继续排查" })" Write-Host "部署报告:$ReportPath" Write-Host "扫码页面:http://127.0.0.1:$Port/connect" Write-Host "" if ($Ready) { Start-Process "http://127.0.0.1:$Port/connect" }