From 657297a7e196343b9582abc69e66da27e627dba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Sun, 5 Jul 2026 19:45:03 -0700 Subject: [PATCH] =?UTF-8?q?feat(portable):=20=E5=86=92=E7=83=9F=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E6=94=AF=E6=8C=81=E7=9C=9F=E5=AE=9E=20U=20=E7=9B=98?= =?UTF-8?q?=E6=97=A0=E6=8D=9F=E6=A0=A1=E9=AA=8C=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 -UsbPath 真实模式:只读校验 + 补建缺失目录 + 缺失时补种配置, 绝不覆盖任何已有文件、绝不写桩 CLI;对盘上真实组件 (openclaw/hermes/node/uv/git)逐一实测版本 - 模拟模式(subst + 桩)保持原行为,用于 CI/快速布局验证 - 已在真实 exFAT U 盘验证:五组件全绿 + 真实二进制 CLAWPANEL_PORTABLE_ROOT 检测通过 Co-Authored-By: Claude Fable 5 --- scripts/smoke-portable-usb.ps1 | 117 ++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 25 deletions(-) diff --git a/scripts/smoke-portable-usb.ps1 b/scripts/smoke-portable-usb.ps1 index 17b1b02..81617e1 100644 --- a/scripts/smoke-portable-usb.ps1 +++ b/scripts/smoke-portable-usb.ps1 @@ -1,5 +1,10 @@ param( + # 模拟模式:用 subst 虚拟盘符 + 桩 CLI(默认,无副作用,用于 CI/本机快速验证布局约定) [string]$DriveLetter = "", + # 真实模式:指定真实 U 盘路径(如 F:\),只做无损校验—— + # 补建缺失目录、portable.json 缺失时补种,绝不覆盖任何已有文件、绝不写桩 CLI; + # 对盘上真实存在的组件(openclaw/hermes/uv/git/node)逐一实测版本 + [string]$UsbPath = "", [string]$RootName = "ClawPanelPortable", [switch]$Keep ) @@ -15,22 +20,42 @@ function Find-FreeDriveLetter { throw "No free test drive letter found" } -if ([string]::IsNullOrWhiteSpace($DriveLetter)) { - $DriveLetter = Find-FreeDriveLetter -} -$DriveLetter = $DriveLetter.Replace(':', '').ToUpperInvariant() -if (Get-PSDrive -Name $DriveLetter -ErrorAction SilentlyContinue) { - throw "Drive $DriveLetter already exists. Choose another DriveLetter." +# 仅在文件不存在时写入(真实盘保护:绝不覆盖用户数据) +function Set-ContentIfAbsent { + param([string]$Path, [object]$Value, [string]$Encoding = "UTF8") + if (Test-Path -LiteralPath $Path) { return $false } + $Value | Set-Content -LiteralPath $Path -Encoding $Encoding + return $true } -$hostRoot = Join-Path $env:TEMP ("clawpanel-usb-smoke-" + [guid]::NewGuid().ToString("N")) -New-Item -ItemType Directory -Force -Path $hostRoot | Out-Null +$realMode = -not [string]::IsNullOrWhiteSpace($UsbPath) +$hostRoot = $null +$driveName = $null + +if ($realMode) { + if (-not (Test-Path -LiteralPath $UsbPath)) { + throw "UsbPath not found: $UsbPath" + } + $usbRoot = Join-Path $UsbPath $RootName +} else { + if ([string]::IsNullOrWhiteSpace($DriveLetter)) { + $DriveLetter = Find-FreeDriveLetter + } + $DriveLetter = $DriveLetter.Replace(':', '').ToUpperInvariant() + if (Get-PSDrive -Name $DriveLetter -ErrorAction SilentlyContinue) { + throw "Drive $DriveLetter already exists. Choose another DriveLetter." + } + $hostRoot = Join-Path $env:TEMP ("clawpanel-usb-smoke-" + [guid]::NewGuid().ToString("N")) + New-Item -ItemType Directory -Force -Path $hostRoot | Out-Null + $driveName = $DriveLetter + ":" +} try { - $driveName = $DriveLetter + ":" - $driveRoot = $DriveLetter + ":\" - subst $driveName $hostRoot - $usbRoot = Join-Path $driveRoot $RootName + if (-not $realMode) { + $driveRoot = $DriveLetter + ":\" + subst $driveName $hostRoot + $usbRoot = Join-Path $driveRoot $RootName + } $dataDir = Join-Path $usbRoot "data" $panelDir = Join-Path $dataDir "clawpanel" $openclawDir = Join-Path $dataDir "openclaw" @@ -44,22 +69,61 @@ try { New-Item -ItemType Directory -Force -Path $dir | Out-Null } - @{ + $null = Set-ContentIfAbsent -Path (Join-Path $usbRoot "portable.json") -Value (@{ mode = "portable" dataDir = "./data" enginesDir = "./engines" runtimesDir = "./runtimes" - } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $usbRoot "portable.json") -Encoding UTF8 + } | ConvertTo-Json -Depth 4) - @{ + $null = Set-ContentIfAbsent -Path (Join-Path $panelDir "clawpanel.json") -Value (@{ accessPassword = "portable-smoke" engine = "openclaw" - } | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $panelDir "clawpanel.json") -Encoding UTF8 + } | ConvertTo-Json -Depth 8) - '{ "gateway": { "port": 18789 }, "agents": { "main": { "name": "main" } } }' | - Set-Content -LiteralPath (Join-Path $openclawDir "openclaw.json") -Encoding UTF8 - "model: smoke" | Set-Content -LiteralPath (Join-Path $hermesHome "config.yaml") -Encoding UTF8 + $null = Set-ContentIfAbsent -Path (Join-Path $openclawDir "openclaw.json") -Value '{ "gateway": { "port": 18789 }, "agents": { "main": { "name": "main" } } }' + $null = Set-ContentIfAbsent -Path (Join-Path $hermesHome "config.yaml") -Value "model: smoke" + if ($realMode) { + # ===== 真实模式:无损校验,逐组件实测(存在才测,不存在报 missing)===== + function Test-Component { + param([string]$Path, [string[]]$CmdArgs) + if (-not (Test-Path -LiteralPath $Path)) { return "(missing)" } + try { + $out = & $Path @CmdArgs 2>&1 | Select-Object -First 1 + return ($out -join " ") + } catch { + return "(error) $($_.Exception.Message)" + } + } + + $manifest = $null + $manifestOk = $false + try { + $raw = Get-Content -LiteralPath (Join-Path $usbRoot "portable.json") -Raw + $manifest = $raw -replace "^", "" | ConvertFrom-Json + $manifestOk = ($manifest.mode -eq "portable") + } catch {} + + $fsName = "unknown" + try { $fsName = (Get-Volume -FilePath $usbRoot -ErrorAction Stop).FileSystem } catch {} + + [pscustomobject]@{ + ok = $manifestOk + mode = "real-usb" + fileSystem = $fsName + usbRoot = $usbRoot + manifestMode = $manifest.mode + openclaw = Test-Component -Path (Join-Path $openclawEngine "openclaw.cmd") -CmdArgs @("--version") + hermes = Test-Component -Path (Join-Path $hermesBin "hermes.cmd") -CmdArgs @("version") + node = Test-Component -Path (Join-Path $openclawEngine "node.exe") -CmdArgs @("--version") + uv = Test-Component -Path (Join-Path $uvBin "uv.exe") -CmdArgs @("--version") + git = Test-Component -Path (Join-Path $gitCmd "git.exe") -CmdArgs @("--version") + } | ConvertTo-Json -Depth 4 + return + } + + # ===== 模拟模式:写桩 CLI,验证布局与 PATH 约定 ===== @( "@echo off", "echo openclaw portable smoke" @@ -128,7 +192,8 @@ try { [pscustomobject]@{ ok = $true - drive = $driveName + mode = "simulated" + fileSystem = "subst(NTFS)" usbRoot = $usbRoot hostRoot = $hostRoot hermes = ($hermesVersion -join "`n") @@ -137,10 +202,12 @@ try { uv = ($uvVersion -join "`n") } | ConvertTo-Json -Depth 4 } finally { - if (-not $Keep) { - subst $driveName /D 2>$null - Remove-Item -LiteralPath $hostRoot -Recurse -Force -ErrorAction SilentlyContinue - } else { - Write-Host "Keeping test drive $driveName -> $hostRoot" + if (-not $realMode) { + if (-not $Keep) { + subst $driveName /D 2>$null + Remove-Item -LiteralPath $hostRoot -Recurse -Force -ErrorAction SilentlyContinue + } else { + Write-Host "Keeping test drive $driveName -> $hostRoot" + } } }