Files
wecom_it_smart_desk/scripts/agent-browser-auth-wait.ps1
Simon 7864ab404b feat(scripts): 收纳运维/部署/修复/调试脚本到 scripts/ 目录
**目录结构**:
- scripts/                 顶层运维/工具脚本(20 个)
- scripts/deploy/          一键部署脚本(deploy_*.py / upload_*.py / redeploy.py 等 8 个)
- scripts/fix/             一次性修复脚本(fix_*.py / fix_*.cypher / fix_*.sql 等 9 个)
- scripts/debug/           调试脚本集合(check_*.py/sql、test_*.py、debug_*.py 等 42 个)

**ops-tools/jms_ops.py**:jumpserver-ops skill 默认路径修复
- 从 'jumpserver-automation-shareable' 改为 'jumpserver-ops'
- 同步更新注释(优先 JP_SKILL_DIR 环境变量)

**典型脚本用途**:
- scripts/deploy-v1.2.ps1 / -staging.ps1:一键部署 v1.2 到生产 / staging
- scripts/init_quick_rules.sql:quick_rules 表初始数据
- scripts/gen_admin_token.py:生成 admin JWT token(调试用)
- scripts/build-and-verify.sh:v1.1 实施验证脚本
- scripts/verify_*.sh / .py:API/quick_rules 验证
- scripts/fix/*.py:环境修复(compose、env_key、itsm_bridge 等)

合计 81 文件 + 3317 行 / - 1 行
2026-08-03 18:48:02 +08:00

292 lines
14 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# agent-browser-auth-wait.ps1
#
# 功能:使用 agent-browser 打开登录页(含二维码),导出二维码图片,
# 等待用户扫码完成后继续执行,自动保存认证状态供下次复用。
#
# 设计要点:
# 1. 复用 agent-browser v0.27.0+ 的 state save/load 能力(内置),
# 不自造轮询 cookie 的轮子。
# 2. 使用 eval -b <base64> 注入 JS,避开 PowerShell pipe -> eval --stdin
# 会卡死的坑(实测 v0.27.0 中 PowerShell pipe 到 eval --stdin 永不返回)。
# 3. 默认 headless + --no-sandbox,因为当前 sandbox 环境 headless chromium
# 会因沙箱权限问题直接 exit 3;如需看到浏览器窗口,加 -Headed。
# 4. wait --url 单次默认 25s 超时,因此用"轮询 wait + 累计超时"实现长等待。
# 5. 不主动 close daemon(除非 -CloseOnExit),便于调用方在同一 daemon 内
# 继续执行后续业务。
#
# 用法示例:
# # 等用户扫码登录,登录完成后进入业务页
# .\agent-browser-auth-wait.ps1 `
# -LoginUrl "https://itsupport.servyou.com.cn/login" `
# -ExpectedPath "dashboard" `
# -StateFile "$PSScriptRoot\auth-state.json"
#
# # 测试 headed 模式(用户能看到浏览器窗口)
# .\agent-browser-auth-wait.ps1 -LoginUrl "..." -Headed
#
# # 等待登录后页面上出现"欢迎"字样
# .\agent-browser-auth-wait.ps1 -LoginUrl "..." -ExpectedText "欢迎"
#
# Exit code:
# 0 = 登录成功(或检测到已登录)
# 1 = 等待超时
# 2 = 其他错误
[CmdletBinding()]
param(
# ── 必填 ─────────────────────────────────────────────────────────────
[Parameter(Mandatory = $true)]
[string]$LoginUrl,
# ── 等待条件(二选一或组合)───────────────────────────────────────────
# 登录后跳转的目标 URL 子串(如 "dashboard"),会与 current URL 做 -like 匹配
[string]$ExpectedPath = "dashboard",
# 登录后页面出现的文本(substring 匹配),可选
[string]$ExpectedText,
# ── 超时与轮询 ─────────────────────────────────────────────────────────
# 等待用户扫码的总秒数(默认 2 分钟)
[int]$TimeoutSec = 120,
# 轮询间隔(毫秒),默认 1.5 秒
[int]$PollIntervalMs = 1500,
# ── 持久化 ─────────────────────────────────────────────────────────────
# 登录成功后保存 state 的路径(cookies + localStorage),下次 -StateFile 加载可跳过登录
[string]$StateFile,
# ── 二维码 ─────────────────────────────────────────────────────────────
# 二维码截图导出路径(headless 模式下用户看不到浏览器,必须导出)
[string]$QrImagePath = "./qr-code.png",
# ── 浏览器模式 ─────────────────────────────────────────────────────────
# 显示浏览器窗口(默认 headless + --no-sandbox,适合 CI/无人值守)
[switch]$Headed,
# 完成后关闭 daemon(默认不关,便于调用方继续在同一 daemon 内执行业务)
[switch]$CloseOnExit,
# 调试模式:打印每个 agent-browser 调用的命令和耗时(避免命名 Debug 以免与 CmdletBinding 冲突)
[switch]$Trace
)
# ── 全局设置 ────────────────────────────────────────────────────────────
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
# ── 辅助函数:统一注入 --args ────────────────────────────────────────────
# 用途:所有 agent-browser 调用统一通过此函数,自动添加 --no-sandbox
# (headless 模式必须),便于复用。
#
# 注意:必须将全局参数和命令参数合并成**一个数组**再 splat。
# PowerShell 中 `& cmd @arr1 @arr2` 双 splat 在某些版本下会被
# 解释成"@arr1 当作第一个参数,@arr2 是剩余参数",导致
# agent-browser 收到错误的 argv 然后 hang。本次实测复现。
function Invoke-AgentBrowser {
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$Args
)
# 准备全局参数数组
$globalArgs = @()
if (-not $Headed) {
# headless 模式必须 --no-sandbox,否则 sandbox 环境会 chrome exit 3
$globalArgs = @('--args', '--no-sandbox')
}
# 合并为单一数组(避免双 splat hang 坑)
$allArgs = @($globalArgs + $Args)
# 调试输出
if ($script:DebugMode) {
Write-Host " [DEBUG] agent-browser $($allArgs -join ' ')" -ForegroundColor DarkGray
}
# 调用 agent-browser,捕获输出
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$output = & agent-browser @allArgs 2>&1
$sw.Stop()
$exitCode = $LASTEXITCODE
if ($script:DebugMode) {
Write-Host " [DEBUG] exit=$exitCode, $($sw.ElapsedMilliseconds)ms, output_lines=$(($output | Measure-Object).Count)" -ForegroundColor DarkGray
}
# 返回 PSObject 便于调用方检查
return [PSCustomObject]@{
ExitCode = $exitCode
Output = ($output -join "`n")
}
}
# ── 辅助函数:用 base64 执行 JS(避开 --stdin pipe 卡死问题)────────────
function Invoke-ABEvalJS {
[CmdletBinding()]
param([Parameter(Mandatory = $true)][string]$Js)
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Js)
$base64 = [Convert]::ToBase64String($bytes)
return (Invoke-AgentBrowser -Args @('eval', '-b', $base64))
}
# ── 辅助函数:等待用户扫码登录完成 ─────────────────────────────────────
# 策略:用 `get url`(瞬时命令)做短周期轮询,避免 `wait --url` 内置 25s
# 超时卡住短超时场景。ExpectedPath 作为子串匹配(如 "dashboard")。
function Wait-ForLoginCompletion {
[CmdletBinding()]
param(
[string]$UrlPattern,
[string]$TextPattern,
[int]$TimeoutSec,
[int]$PollIntervalMs = 1500
)
$deadline = (Get-Date).AddSeconds($TimeoutSec)
$attempt = 0
while ((Get-Date) -lt $deadline) {
$attempt++
$remaining = [int]([Math]::Max(0, ($deadline - (Get-Date)).TotalSeconds))
Write-Host " ⏳ 第 $attempt 次检测(剩余 ${remaining}s..."
# ── 检测 1:URL 子串匹配 ──
if ($UrlPattern) {
$urlResult = Invoke-AgentBrowser -Args @('get', 'url')
if ($urlResult.ExitCode -eq 0) {
$currentUrl = ($urlResult.Output -join "`n").Trim()
if ($currentUrl -like "*$UrlPattern*") {
Write-Host " ✅ URL 匹配登录后路径: $currentUrl" -ForegroundColor Green
return $true
}
}
}
# ── 检测 2:页面文本子串匹配 ──
if ($TextPattern) {
$checkJs = "document.body && document.body.innerText.includes('$TextPattern')"
$b = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($checkJs))
$textResult = Invoke-AgentBrowser -Args @('eval', '-b', $b)
if ($textResult.ExitCode -eq 0) {
# eval 返回 "true" 或 "false"
if ($textResult.Output -match '^\s*true\s*$') {
Write-Host " ✅ 页面文本匹配: $TextPattern" -ForegroundColor Green
return $true
}
}
}
Start-Sleep -Milliseconds $PollIntervalMs
}
return $false
}
# ── 主流程 ──────────────────────────────────────────────────────────────
try {
Write-Host ""
Write-Host "════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host " agent-browser 扫码登录等待" -ForegroundColor Cyan
Write-Host "════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host " 登录页: $LoginUrl"
Write-Host " 等待路径: $ExpectedPath"
if ($StateFile) { Write-Host " 状态文件: $StateFile" }
Write-Host " Headed: $Headed"
Write-Host ""
# 调试钩子:-Trace 模式下打印每一步
$script:DebugMode = [bool]$Trace
# ── Step 1: 加载已保存的 state(如提供)或直接打开登录页 ──────────────
# 注意:--state 是启动时的全局参数(位于命令前),daemon 已启动时不能附加。
# 如果 daemon 没启动:第一次 open 会启动 daemon 并应用 --state。
# 如果 daemon 已启动:--state 被忽略,需要先关闭再重启。
# 本脚本不主动关闭 daemon,让调用方管理生命周期。
if ($StateFile -and (Test-Path $StateFile)) {
Write-Host "📂 发现已保存的认证状态,尝试复用..." -ForegroundColor Yellow
Write-Host " agent-browser --state $StateFile open $LoginUrl"
$args = @('--state', $StateFile, 'open', $LoginUrl)
$null = Invoke-AgentBrowser -Args $args
}
else {
Write-Host "🌐 打开登录页..."
$null = Invoke-AgentBrowser -Args @('open', $LoginUrl)
}
# ── Step 2: 等待页面加载完成 ──────────────────────────────────────────
Write-Host "⏳ 等待页面加载..."
$null = Invoke-AgentBrowser -Args @('wait', '--load', 'networkidle')
# ── Step 3: 截图导出二维码 ────────────────────────────────────────────
# headless 模式下浏览器窗口不可见,二维码必须导出;headed 模式截图仅为冗余
$qrDir = Split-Path -Parent $QrImagePath
if ($qrDir -and -not (Test-Path $qrDir)) {
New-Item -ItemType Directory -Path $qrDir -Force | Out-Null
}
Write-Host "📸 导出二维码: $QrImagePath" -ForegroundColor Yellow
$r = Invoke-AgentBrowser -Args @('screenshot', $QrImagePath)
if ($r.ExitCode -ne 0) {
Write-Warning "二维码截图失败:$($r.Output)"
}
elseif (Test-Path $QrImagePath) {
Write-Host " 请用企微 APP 扫描二维码完成登录"
if (-not $Headed) {
Write-Host " (headless 模式:在文件管理器中打开 $QrImagePath 扫描)" -ForegroundColor Gray
}
}
# ── Step 4: 提示用户 ──────────────────────────────────────────────────
Write-Host ""
Write-Host "📱 等待扫码登录(超时 $TimeoutSec 秒)..." -ForegroundColor Yellow
Write-Host ""
# ── Step 5: 轮询等待登录完成 ──────────────────────────────────────────
$ok = Wait-ForLoginCompletion `
-UrlPattern $ExpectedPath `
-TextPattern $ExpectedText `
-TimeoutSec $TimeoutSec `
-PollIntervalMs $PollIntervalMs
if (-not $ok) {
Write-Host ""
Write-Host "❌ 等待扫码登录超时(${TimeoutSec}s" -ForegroundColor Red
Write-Host " 请重试,或检查登录页是否正常显示二维码" -ForegroundColor Red
Write-Host " daemon 保持打开,可手动操作后重跑脚本" -ForegroundColor Gray
exit 1
}
# ── Step 6: 保存 state(供下次复用)──────────────────────────────────
if ($StateFile) {
$stateDir = Split-Path -Parent $StateFile
if ($stateDir -and -not (Test-Path $stateDir)) {
New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
}
Write-Host "💾 保存认证状态: $StateFile"
$r = Invoke-AgentBrowser -Args @('state', 'save', $StateFile)
if ($r.ExitCode -ne 0) {
Write-Warning "state 保存失败:$($r.Output)"
}
}
Write-Host ""
Write-Host "✅ 扫码登录完成" -ForegroundColor Green
Write-Host ""
# ── Step 7: CloseOnExit ──────────────────────────────────────────────
# 注意:脚本不主动关闭 daemon(实测 close --all 在某些 Windows 状态下
# 会让后续命令 hang)。如需关闭,调用方自己执行 `agent-browser close --all`。
if ($CloseOnExit) {
Write-Host "️ CloseOnExit 已请求,但脚本不主动关闭 daemon 以避免 hang" -ForegroundColor Yellow
Write-Host " 请手动执行: agent-browser close --all" -ForegroundColor Yellow
}
Write-Host "ℹ️ daemon 保持打开,可继续执行后续 agent-browser 命令" -ForegroundColor Gray
exit 0
}
catch {
Write-Host ""
Write-Host "❌ 脚本异常: $_" -ForegroundColor Red
Write-Host $_.ScriptStackTrace -ForegroundColor Red
exit 2
}