W1: 卸载脚本改为只删 Bookworm 注入的 7 目录+3 文件,
保留用户自有 CLAUDE.md/memory/projects (防误杀)
W2: bw-doctor [9] 改检 DPAPI→profile 注入链路,
不再检查 User 持久环境变量 (逻辑反转修正)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
458 lines
21 KiB
PowerShell
458 lines
21 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Bookworm Portable 体检工具 (v3.1.3)
|
|
|
|
.DESCRIPTION
|
|
13 维度自检, 覆盖 auto-setup.ps1 7 阶段所有安装产物.
|
|
输出彩色 PASS/WARN/FAIL 报告, 不修改任何文件.
|
|
|
|
维度:
|
|
[1] PowerShell 7 (pwsh)
|
|
[2] Node.js
|
|
[3] Git
|
|
[4] Claude Code (claude.ps1 可达)
|
|
[5] 桌面 .lnk (启动/更新 + Args 契约)
|
|
[6] DPAPI 凭证 (HKCU CachedEnv)
|
|
[7] Profile BW_CRED 块 (PS7 + PS5.1)
|
|
[8] Profile BW_CLIP 块 (PS7)
|
|
[9] 环境变量 (ANTHROPIC_*)
|
|
[10] ~/.claude 完整性 (CLAUDE.md / Skills / Hooks / Settings)
|
|
[11] API 中转站连通 (bww.letcareme.com)
|
|
[12] Worker 连通 (bookworm-router)
|
|
[13] Gitea 连通 (code.letcareme.com)
|
|
|
|
.NOTES
|
|
用法: pwsh -NoProfile -ExecutionPolicy Bypass -File bw-doctor.ps1
|
|
日志: $env:TEMP\bw-doctor.log
|
|
退出码: 0 = 全 PASS / 1 = 有 FAIL
|
|
#>
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
|
|
$doctorLog = Join-Path $env:TEMP "bw-doctor.log"
|
|
$pass = 0; $warn = 0; $fail = 0
|
|
$results = @()
|
|
|
|
function Log-Doctor {
|
|
param([string]$Msg)
|
|
try { "[$([DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))] $Msg" | Out-File -FilePath $doctorLog -Append -Encoding UTF8 -EA SilentlyContinue } catch {}
|
|
}
|
|
|
|
function Report {
|
|
param([string]$Dim, [string]$Status, [string]$Detail)
|
|
$color = switch ($Status) { 'PASS' { 'Green' } 'WARN' { 'Yellow' } 'FAIL' { 'Red' } default { 'Gray' } }
|
|
$icon = switch ($Status) { 'PASS' { [char]0x2714 } 'WARN' { '!' } 'FAIL' { [char]0x2718 } default { '?' } }
|
|
$line = " $icon [$Status] $Dim"
|
|
if ($Detail) { $line += " — $Detail" }
|
|
Write-Host $line -ForegroundColor $color
|
|
Log-Doctor "$Status $Dim $Detail"
|
|
switch ($Status) {
|
|
'PASS' { $script:pass++ }
|
|
'WARN' { $script:warn++ }
|
|
'FAIL' { $script:fail++ }
|
|
}
|
|
$script:results += @{ Dim = $Dim; Status = $Status; Detail = $Detail }
|
|
}
|
|
|
|
function Test-Cmd($name) { return [bool](Get-Command $name -EA SilentlyContinue) }
|
|
|
|
# ── Banner ──
|
|
Write-Host ""
|
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
|
Write-Host " | Bookworm Doctor v3.1.3 |" -ForegroundColor Cyan
|
|
Write-Host " | 13 维度健康体检 |" -ForegroundColor Cyan
|
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Log-Doctor "=== Bookworm Doctor v3.1.3 START ==="
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [1/13] PowerShell 7
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [1/13] PowerShell 7" -ForegroundColor Cyan
|
|
$pwshCmd = Get-Command pwsh -EA SilentlyContinue
|
|
if ($pwshCmd) {
|
|
try {
|
|
$pwshVer = (& pwsh -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>$null).Trim()
|
|
Report "[1] PowerShell 7" "PASS" "v$pwshVer ($($pwshCmd.Source))"
|
|
} catch {
|
|
Report "[1] PowerShell 7" "WARN" "pwsh 可达但版本查询失败"
|
|
}
|
|
} else {
|
|
Report "[1] PowerShell 7" "FAIL" "pwsh 不在 PATH — 桌面 .lnk 强依赖 pwsh.exe"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [2/13] Node.js
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [2/13] Node.js" -ForegroundColor Cyan
|
|
if (Test-Cmd "node") {
|
|
try {
|
|
$nodeVer = (& node --version 2>$null).Trim()
|
|
Report "[2] Node.js" "PASS" "$nodeVer"
|
|
} catch {
|
|
Report "[2] Node.js" "WARN" "node 可达但版本查询失败"
|
|
}
|
|
} else {
|
|
Report "[2] Node.js" "FAIL" "node 不在 PATH — Claude Code 强依赖"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [3/13] Git
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [3/13] Git" -ForegroundColor Cyan
|
|
if (Test-Cmd "git") {
|
|
try {
|
|
$gitVer = (& git --version 2>$null).Trim()
|
|
Report "[3] Git" "PASS" "$gitVer"
|
|
} catch {
|
|
Report "[3] Git" "WARN" "git 可达但版本查询失败"
|
|
}
|
|
} else {
|
|
Report "[3] Git" "FAIL" "git 不在 PATH — 配置同步强依赖"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [4/13] Claude Code
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [4/13] Claude Code" -ForegroundColor Cyan
|
|
$claudePs1Found = $null
|
|
|
|
$claudeCmd = Get-Command claude -EA SilentlyContinue
|
|
if ($claudeCmd -and $claudeCmd.Source -and $claudeCmd.Source.EndsWith('.ps1') -and (Test-Path $claudeCmd.Source)) {
|
|
$claudePs1Found = $claudeCmd.Source
|
|
}
|
|
if (-not $claudePs1Found) {
|
|
try {
|
|
$npmPrefix = (& npm config get prefix 2>$null | Select-Object -First 1).Trim()
|
|
$candidate = Join-Path $npmPrefix "claude.ps1"
|
|
if (Test-Path $candidate) { $claudePs1Found = $candidate }
|
|
} catch {}
|
|
}
|
|
if (-not $claudePs1Found) {
|
|
foreach ($p in @("$env:APPDATA\npm\claude.ps1", "$env:ProgramFiles\nodejs\claude.ps1", "$env:LOCALAPPDATA\npm\claude.ps1")) {
|
|
if (Test-Path $p) { $claudePs1Found = $p; break }
|
|
}
|
|
}
|
|
|
|
if ($claudePs1Found) {
|
|
try {
|
|
$claudeVer = (& claude --version 2>$null | Select-Object -First 1).Trim()
|
|
Report "[4] Claude Code" "PASS" "$claudeVer ($claudePs1Found)"
|
|
} catch {
|
|
Report "[4] Claude Code" "PASS" "claude.ps1 存在: $claudePs1Found (版本查询跳过)"
|
|
}
|
|
} else {
|
|
Report "[4] Claude Code" "FAIL" "claude.ps1 不可达 — 运行 npm i -g @anthropic-ai/claude-code"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [5/13] 桌面 .lnk
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [5/13] 桌面 .lnk" -ForegroundColor Cyan
|
|
$desktop = [Environment]::GetFolderPath('Desktop')
|
|
$requiredLnks = @('启动Bookworm.lnk', '更新Bookworm.lnk')
|
|
$optionalLnks = @('体检Bookworm.lnk', '卸载Bookworm.lnk')
|
|
$lnkMissing = @()
|
|
$lnkPresent = @()
|
|
|
|
foreach ($n in $requiredLnks) {
|
|
$p = Join-Path $desktop $n
|
|
if (Test-Path $p) { $lnkPresent += $n } else { $lnkMissing += $n }
|
|
}
|
|
|
|
$optPresent = @()
|
|
foreach ($n in $optionalLnks) {
|
|
$p = Join-Path $desktop $n
|
|
if (Test-Path $p) { $optPresent += $n }
|
|
}
|
|
|
|
if ($lnkMissing.Count -eq 0) {
|
|
# 验证启动 .lnk Args 契约 (4 项: pwsh TargetPath / bw-launch.ps1 / --dangerously-skip-permissions / -ExecutionPolicy Bypass)
|
|
$launchLnk = Join-Path $desktop '启动Bookworm.lnk'
|
|
$argsOK = $true
|
|
$argsDetail = ""
|
|
try {
|
|
$shell = New-Object -ComObject WScript.Shell
|
|
$sc = $shell.CreateShortcut($launchLnk)
|
|
$checks = @(
|
|
@{ Name = "TargetPath=pwsh"; OK = ($sc.TargetPath -match 'pwsh\.exe$') }
|
|
@{ Name = "bw-launch.ps1"; OK = ($sc.Arguments -match 'bw-launch\.ps1') }
|
|
@{ Name = "--dangerously-skip-permissions"; OK = ($sc.Arguments -match '--dangerously-skip-permissions') }
|
|
@{ Name = "-ExecutionPolicy Bypass"; OK = ($sc.Arguments -match '-ExecutionPolicy Bypass') }
|
|
)
|
|
$badChecks = $checks | Where-Object { -not $_.OK }
|
|
if ($badChecks) {
|
|
$argsOK = $false
|
|
$argsDetail = "契约失败: " + (($badChecks | ForEach-Object { $_.Name }) -join ', ')
|
|
}
|
|
} catch { $argsOK = $false; $argsDetail = "读取 .lnk 异常" }
|
|
|
|
if ($argsOK) {
|
|
$extra = if ($optPresent.Count -gt 0) { " + $($optPresent -join '/')" } else { "" }
|
|
Report "[5] 桌面 .lnk" "PASS" "$($lnkPresent -join ' + ')$extra — 4 项契约 OK"
|
|
} else {
|
|
Report "[5] 桌面 .lnk" "FAIL" "$argsDetail"
|
|
}
|
|
} else {
|
|
Report "[5] 桌面 .lnk" "FAIL" "缺失: $($lnkMissing -join ', ') — 重跑 Bookworm-Setup.exe"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [6/13] DPAPI 凭证
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [6/13] DPAPI 凭证" -ForegroundColor Cyan
|
|
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
|
|
if (Test-Path $regPath) {
|
|
try {
|
|
Add-Type -AssemblyName System.Security -EA Stop
|
|
$props = Get-ItemProperty $regPath -EA Stop
|
|
$envNames = $props.PSObject.Properties | Where-Object { $_.Name -match '^[A-Z_]+$' }
|
|
$decrypted = 0
|
|
foreach ($ev in $envNames) {
|
|
try {
|
|
$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect(
|
|
[Convert]::FromBase64String($ev.Value), $null,
|
|
[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
|
|
$decrypted++
|
|
} catch {}
|
|
}
|
|
if ($envNames.Count -gt 0 -and $decrypted -eq $envNames.Count) {
|
|
Report "[6] DPAPI 凭证" "PASS" "$decrypted/$($envNames.Count) 密钥可解密"
|
|
} elseif ($decrypted -gt 0) {
|
|
Report "[6] DPAPI 凭证" "WARN" "$decrypted/$($envNames.Count) 可解密 (部分失败)"
|
|
} else {
|
|
Report "[6] DPAPI 凭证" "FAIL" "0/$($envNames.Count) 可解密 — DPAPI 可能跨用户失效"
|
|
}
|
|
} catch {
|
|
Report "[6] DPAPI 凭证" "WARN" "HKCU 存在但读取异常: $($_.Exception.Message)"
|
|
}
|
|
} else {
|
|
Report "[6] DPAPI 凭证" "FAIL" "HKCU:\Software\Bookworm\CachedEnv 不存在 — 未安装或已卸载"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [7/13] Profile BW_CRED 块
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [7/13] Profile BW_CRED" -ForegroundColor Cyan
|
|
$credBlockOK = 0; $credBlockMissing = @()
|
|
foreach ($entry in @(
|
|
@{ Name = "PS7"; Path = (Join-Path $env:USERPROFILE "Documents\PowerShell\profile.ps1") }
|
|
@{ Name = "PS5.1"; Path = (Join-Path $env:USERPROFILE "Documents\WindowsPowerShell\profile.ps1") }
|
|
)) {
|
|
if (Test-Path $entry.Path) {
|
|
$c = Get-Content $entry.Path -Raw -EA SilentlyContinue
|
|
if ($c -match 'BW_CRED_START' -and $c -match 'BW_CRED_END') {
|
|
$credBlockOK++
|
|
} else {
|
|
$credBlockMissing += $entry.Name
|
|
}
|
|
} else {
|
|
$credBlockMissing += "$($entry.Name)(文件不存在)"
|
|
}
|
|
}
|
|
if ($credBlockOK -ge 1 -and $credBlockMissing.Count -eq 0) {
|
|
Report "[7] Profile BW_CRED" "PASS" "PS7 + PS5.1 双 profile sentinel 完整"
|
|
} elseif ($credBlockOK -ge 1) {
|
|
Report "[7] Profile BW_CRED" "WARN" "部分缺失: $($credBlockMissing -join ', ')"
|
|
} else {
|
|
Report "[7] Profile BW_CRED" "FAIL" "BW_CRED 块不存在 — 启动时无法自动加载凭证"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [8/13] Profile BW_CLIP 块
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [8/13] Profile BW_CLIP" -ForegroundColor Cyan
|
|
$clipProfile = Join-Path $env:USERPROFILE "Documents\PowerShell\profile.ps1"
|
|
if (Test-Path $clipProfile) {
|
|
$c = Get-Content $clipProfile -Raw -EA SilentlyContinue
|
|
if ($c -match 'BW_CLIP_START' -and $c -match 'BW_CLIP_END') {
|
|
Report "[8] Profile BW_CLIP" "PASS" "截图粘贴助手 sentinel 完整"
|
|
} else {
|
|
Report "[8] Profile BW_CLIP" "WARN" "BW_CLIP 块缺失 (截图粘贴功能不可用, 非核心)"
|
|
}
|
|
} else {
|
|
Report "[8] Profile BW_CLIP" "WARN" "PS7 profile.ps1 不存在"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [9/13] 凭证注入链路 (DPAPI → profile → 运行时 Key)
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [9/13] 凭证注入链路" -ForegroundColor Cyan
|
|
# Bookworm 的设计: DPAPI 加密存 HKCU → profile.ps1 启动时解密 export → 运行时 env 可用
|
|
# 不应检查 User 持久环境变量 (Key 明文存 User env 反而不安全)
|
|
$chainOK = $true; $chainDetail = @()
|
|
|
|
# 环节 1: DPAPI 存储有 Key
|
|
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
|
|
$dpapiHasKey = $false
|
|
if (Test-Path $regPath) {
|
|
try {
|
|
$props = Get-ItemProperty $regPath -EA SilentlyContinue
|
|
$apiKeyProp = $props.PSObject.Properties | Where-Object { $_.Name -eq 'ANTHROPIC_API_KEY' }
|
|
if ($apiKeyProp) { $dpapiHasKey = $true; $chainDetail += "DPAPI=OK" }
|
|
} catch {}
|
|
}
|
|
if (-not $dpapiHasKey) { $chainOK = $false; $chainDetail += "DPAPI=MISSING" }
|
|
|
|
# 环节 2: profile 有 BW_CRED 块
|
|
$ps7Profile = Join-Path $env:USERPROFILE "Documents\PowerShell\profile.ps1"
|
|
$profileHasCred = $false
|
|
if (Test-Path $ps7Profile) {
|
|
$c = Get-Content $ps7Profile -Raw -EA SilentlyContinue
|
|
if ($c -match 'BW_CRED_START') { $profileHasCred = $true; $chainDetail += "Profile=OK" }
|
|
}
|
|
if (-not $profileHasCred) { $chainOK = $false; $chainDetail += "Profile=MISSING" }
|
|
|
|
# 环节 3: BASE_URL (可选但推荐)
|
|
$baseUrl = [Environment]::GetEnvironmentVariable('ANTHROPIC_BASE_URL', 'User')
|
|
if (-not $baseUrl) { $baseUrl = $env:ANTHROPIC_BASE_URL }
|
|
if ($baseUrl) { $chainDetail += "BaseURL=$baseUrl" } else { $chainDetail += "BaseURL=default" }
|
|
|
|
if ($chainOK) {
|
|
Report "[9] 凭证注入链路" "PASS" ($chainDetail -join ' → ')
|
|
} else {
|
|
Report "[9] 凭证注入链路" "FAIL" ($chainDetail -join ' → ') + " — 重跑 Bookworm-Setup.exe"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [10/13] ~/.claude 完整性
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [10/13] ~/.claude 完整性" -ForegroundColor Cyan
|
|
$claudeDir = Join-Path $env:USERPROFILE ".claude"
|
|
$intChecks = @()
|
|
|
|
# CLAUDE.md
|
|
$claudeMdPath = Join-Path $claudeDir "CLAUDE.md"
|
|
$claudeMdOK = $false
|
|
if (Test-Path $claudeMdPath) {
|
|
$cm = Get-Content $claudeMdPath -Raw -EA SilentlyContinue
|
|
$claudeMdOK = $cm -match "Bookworm"
|
|
}
|
|
$intChecks += @{ Name = "CLAUDE.md"; OK = $claudeMdOK }
|
|
|
|
# Skills
|
|
$skillsDir = Join-Path $claudeDir "skills"
|
|
$skillCount = 0
|
|
if (Test-Path $skillsDir) { $skillCount = @(Get-ChildItem $skillsDir -Directory -EA SilentlyContinue).Count }
|
|
$intChecks += @{ Name = "Skills ($skillCount, 需>=10)"; OK = ($skillCount -ge 10) }
|
|
|
|
# Hooks
|
|
$hooksDir = Join-Path $claudeDir "hooks"
|
|
$hookCount = 0
|
|
if (Test-Path $hooksDir) { $hookCount = @(Get-ChildItem $hooksDir -Filter "*.js" -File -EA SilentlyContinue).Count }
|
|
$intChecks += @{ Name = "Hooks ($hookCount, 需>=3)"; OK = ($hookCount -ge 3) }
|
|
|
|
# Settings hooks
|
|
$settingsFile = Join-Path $claudeDir "settings.json"
|
|
$settingsOK = $false
|
|
if (Test-Path $settingsFile) {
|
|
$sc = Get-Content $settingsFile -Raw -EA SilentlyContinue
|
|
$settingsOK = $sc -match '"hooks"'
|
|
}
|
|
$intChecks += @{ Name = "Settings hooks"; OK = $settingsOK }
|
|
|
|
$intFails = $intChecks | Where-Object { -not $_.OK }
|
|
if ($intFails.Count -eq 0) {
|
|
Report "[10] ~/.claude 完整性" "PASS" "CLAUDE.md + $skillCount Skills + $hookCount Hooks + Settings"
|
|
} else {
|
|
$failNames = ($intFails | ForEach-Object { $_.Name }) -join ', '
|
|
Report "[10] ~/.claude 完整性" "FAIL" "缺失: $failNames"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [11/13] API 中转站连通
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [11/13] API 中转站" -ForegroundColor Cyan
|
|
$apiBaseUrl = [Environment]::GetEnvironmentVariable('ANTHROPIC_BASE_URL', 'User')
|
|
if (-not $apiBaseUrl) { $apiBaseUrl = $env:ANTHROPIC_BASE_URL }
|
|
if (-not $apiBaseUrl) { $apiBaseUrl = "https://bww.letcareme.com" }
|
|
|
|
try {
|
|
# 中转站对境外 IP 返 503, NO_PROXY 直连
|
|
$savedProxy = $env:NO_PROXY
|
|
$env:NO_PROXY = "bww.letcareme.com,letcareme.com,localhost,127.0.0.1"
|
|
|
|
$resp = Invoke-WebRequest -Uri "$apiBaseUrl/v1/models" -Method Head -TimeoutSec 10 -UseBasicParsing -EA Stop
|
|
$env:NO_PROXY = $savedProxy
|
|
Report "[11] API 中转站" "PASS" "$apiBaseUrl (HTTP $($resp.StatusCode))"
|
|
} catch {
|
|
$env:NO_PROXY = $savedProxy
|
|
$errMsg = $_.Exception.Message
|
|
if ($errMsg -match '40[0-9]|301|302|503') {
|
|
Report "[11] API 中转站" "PASS" "$apiBaseUrl 可达 (HTTP 错误码 = 网络通)"
|
|
} else {
|
|
Report "[11] API 中转站" "FAIL" "$apiBaseUrl 不可达: $errMsg"
|
|
}
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [12/13] Worker 连通
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [12/13] Worker" -ForegroundColor Cyan
|
|
try {
|
|
$workerUrl = "https://bookworm-router.bookworm-api.workers.dev/config"
|
|
$resp = Invoke-RestMethod -Uri $workerUrl -TimeoutSec 10 -EA Stop
|
|
Report "[12] Worker" "PASS" "bookworm-router /config 可达"
|
|
} catch {
|
|
$errMsg = $_.Exception.Message
|
|
if ($errMsg -match '403|404') {
|
|
Report "[12] Worker" "PASS" "Worker 可达 (HTTP 错误码 = 网络通)"
|
|
} else {
|
|
Report "[12] Worker" "WARN" "Worker 不可达: $errMsg (非核心, 仅影响默认 model)"
|
|
}
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# [13/13] Gitea 连通
|
|
# ══════════════════════════════════════════════════════
|
|
Write-Host " [13/13] Gitea" -ForegroundColor Cyan
|
|
try {
|
|
$savedProxy = $env:NO_PROXY
|
|
$env:NO_PROXY = "code.letcareme.com,letcareme.com,localhost,127.0.0.1"
|
|
$resp = Invoke-WebRequest -Uri "https://code.letcareme.com" -Method Head -TimeoutSec 10 -UseBasicParsing -EA Stop
|
|
$env:NO_PROXY = $savedProxy
|
|
Report "[13] Gitea" "PASS" "code.letcareme.com 可达"
|
|
} catch {
|
|
$env:NO_PROXY = $savedProxy
|
|
$errMsg = $_.Exception.Message
|
|
if ($errMsg -match '40[0-9]|301|302|503') {
|
|
Report "[13] Gitea" "PASS" "code.letcareme.com 可达 (HTTP 错误码 = 网络通)"
|
|
} else {
|
|
Report "[13] Gitea" "FAIL" "code.letcareme.com 不可达: $errMsg — 配置同步将失败"
|
|
}
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# Summary
|
|
# ══════════════════════════════════════════════════════
|
|
$total = $pass + $warn + $fail
|
|
Write-Host ""
|
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
|
Write-Host " | 体检报告 |" -ForegroundColor Cyan
|
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
|
|
|
$passLine = " | PASS: $pass / $total"
|
|
$warnLine = " | WARN: $warn"
|
|
$failLine = " | FAIL: $fail"
|
|
|
|
Write-Host $passLine -ForegroundColor Green
|
|
if ($warn -gt 0) { Write-Host $warnLine -ForegroundColor Yellow }
|
|
if ($fail -gt 0) { Write-Host $failLine -ForegroundColor Red }
|
|
|
|
Write-Host " |" -ForegroundColor Cyan
|
|
if ($fail -eq 0 -and $warn -eq 0) {
|
|
Write-Host " | [ALL GREEN] Bookworm 完全健康" -ForegroundColor Green
|
|
} elseif ($fail -eq 0) {
|
|
Write-Host " | [HEALTHY] 核心功能正常, 有 $warn 项可优化" -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host " | [NEEDS FIX] $fail 项异常需修复" -ForegroundColor Red
|
|
Write-Host " | 修复: 重跑 Bookworm-Setup.exe 或联系管理员" -ForegroundColor Red
|
|
}
|
|
Write-Host " | 日志: $doctorLog" -ForegroundColor Gray
|
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
Log-Doctor "=== DONE: PASS=$pass WARN=$warn FAIL=$fail ==="
|
|
|
|
if ($fail -gt 0) { exit 1 } else { exit 0 }
|