清理: 根目录散乱文件归位
- 删根 9 个构建/换装日志(*.log 本已 gitignore,属磁盘残留)+ 根 todo.md(陈旧副本,真板为 docs/todo.md)+ .dockerignore(无 Dockerfile) - 根 swap-install.ps1(v3 加固版)迁 scripts/,替换其中陈旧 43 行版(句柄探测/SHA256 校验/重试/自清理) - dev-tools.bat / dev-server.bat 迁 scripts/ - Batch.md / PROGRESS.md 迁 docs/07-项目管理/,同步修正 5 处指向根的文档链接 - .gitignore 补 /swap-install.lock(v3 运行期锁)
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
@echo off
|
||||
cd /d E:\wk-lab\devflow
|
||||
echo [dev] 启动 DevFlow 开发服务器...
|
||||
echo [dev] Vite + Tauri 后端将同时启动
|
||||
echo [dev] 请勿关闭此窗口
|
||||
echo.
|
||||
C:\Users\23780\.bun\bin\bun.exe run tauri dev
|
||||
echo.
|
||||
echo [dev] 服务器已停止
|
||||
pause
|
||||
@@ -0,0 +1,30 @@
|
||||
@echo off
|
||||
echo DevFlow 开发工具
|
||||
echo.
|
||||
echo 1. 启动开发服务器
|
||||
echo 2. 停止开发服务器
|
||||
echo 3. 重启开发服务器
|
||||
echo 4. 查看运行的 Vite 进程
|
||||
echo 5. 退出
|
||||
echo.
|
||||
set /p choice=请选择操作 (1-5):
|
||||
|
||||
if "%choice%"=="1" (
|
||||
echo 启动开发服务器...
|
||||
npm run dev
|
||||
) else if "%choice%"=="2" (
|
||||
echo 停止开发服务器...
|
||||
npm run dev:stop
|
||||
) else if "%choice%"=="3" (
|
||||
echo 重启开发服务器...
|
||||
npm run dev:restart
|
||||
) else if "%choice%"=="4" (
|
||||
echo 查看运行的 Vite 进程...
|
||||
tasklist | findstr "vite.exe"
|
||||
) else if "%choice%"=="5" (
|
||||
exit /b 0
|
||||
) else (
|
||||
echo 无效选择,请重新运行
|
||||
)
|
||||
|
||||
pause
|
||||
+182
-36
@@ -1,43 +1,189 @@
|
||||
# DevFlow 换装脚本:kill 旧进程 -> 静默安装 -> 启动验证
|
||||
# 独立后台执行,避免中断当前会话导致换装半途而废
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$log = 'E:/wk-lab/devflow/swap.log'
|
||||
function Log($m) { "$(Get-Date -Format 'HH:mm:ss') $m" | Tee-Object -FilePath $log -Append }
|
||||
# DevFlow 自宿主换装脚本 v3
|
||||
# 流程: 等旧进程退出并探测句柄释放, 静默安装, 按构建产物哈希校验且失败重试一次, 交互用户可见重启, 结束自清理
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# 安装包路径:按构建产物 glob 探测(不硬编码版本号,版本升级时脚本零改动)。
|
||||
# C3 决议:脚本消费构建产物,版本号唯一源头是 Cargo.toml → tauri.conf.json 注入文件名。
|
||||
$nsisDir = 'E:\wk-lab\devflow\target\release\bundle\nsis'
|
||||
$installer = $null
|
||||
if (Test-Path $nsisDir) {
|
||||
$installer = Get-ChildItem -Path $nsisDir -Filter 'devflow_*_x64-setup.exe' -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
|
||||
}
|
||||
if (-not $installer) {
|
||||
# 兜底:目录缺失或无匹配产物时,保留旧硬编码路径以维持既有失败提示语义
|
||||
$installer = 'E:\wk-lab\devflow\target\release\bundle\nsis\devflow_0.1.1_x64-setup.exe'
|
||||
}
|
||||
$target = 'C:\Users\23780\AppData\Local\devflow\devflow.exe'
|
||||
$builtExe = 'E:\wk-lab\devflow\target\release\devflow.exe'
|
||||
$log = 'E:\wk-lab\devflow\swap-install.log'
|
||||
$lockFile = 'E:\wk-lab\devflow\swap-install.lock'
|
||||
|
||||
Log "=== 换装开始 ==="
|
||||
# 构建产物哈希是唯一强基准; 产物缺失时退化为与安装前快照比对的弱校验
|
||||
$hashWant = $null
|
||||
if (Test-Path $builtExe) { $hashWant = (Get-FileHash $builtExe -Algorithm SHA256).Hash }
|
||||
|
||||
# 1. kill 旧进程
|
||||
$procs = Get-Process devflow -ErrorAction SilentlyContinue
|
||||
if ($procs) {
|
||||
Log "发现运行中 devflow 进程: $($procs.Id -join ','),正在结束..."
|
||||
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 3
|
||||
$still = Get-Process devflow -ErrorAction SilentlyContinue
|
||||
if ($still) { Log "警告: 仍有残留进程 $($still.Id -join ',')"; $still | Stop-Process -Force -ErrorAction SilentlyContinue; Start-Sleep -Seconds 2 }
|
||||
Log "旧进程已结束"
|
||||
} else {
|
||||
Log "无运行中 devflow 进程"
|
||||
# 0. SYSTEM 上下文转投用户会话
|
||||
$who = whoami
|
||||
if ($who -like '*SYSTEM*') {
|
||||
$loggedUser = (Get-CimInstance Win32_ComputerSystem).UserName
|
||||
if ($loggedUser) {
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$PSCommandPath`""
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $loggedUser -LogonType Interactive
|
||||
Register-ScheduledTask -TaskName 'DevFlowSwapUser' -Action $action -Principal $principal -Force | Out-Null
|
||||
Start-ScheduledTask -TaskName 'DevFlowSwapUser'
|
||||
# 锁由重入脚本创建删除, 是其存活的可靠信号; 按 task State 等有 Ready 竞态且超时会腰斩换装
|
||||
$deadline = (Get-Date).AddSeconds(30)
|
||||
while (-not (Test-Path $lockFile) -and (Get-Date) -lt $deadline) { Start-Sleep -Seconds 1 }
|
||||
$deadline = (Get-Date).AddSeconds(300)
|
||||
while ((Test-Path $lockFile) -and (Get-Date) -lt $deadline) { Start-Sleep -Seconds 2 }
|
||||
Unregister-ScheduledTask -TaskName 'DevFlowSwapUser' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
"[FAIL] SYSTEM context and no logged-on user found" | Out-File $log -Encoding utf8
|
||||
}
|
||||
Unregister-ScheduledTask -TaskName 'DevFlowSwapInstall' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 2. 静默安装新包
|
||||
$setup = 'E:/wk-lab/devflow/target/release/bundle/nsis/devflow_0.1.1_x64-setup.exe'
|
||||
if (-not (Test-Path $setup)) { Log "错误: 安装包不存在 $setup"; exit 1 }
|
||||
Log "静默安装: $setup"
|
||||
$p = Start-Process -FilePath $setup -ArgumentList '/S' -PassThru -Wait
|
||||
Log "安装器退出码: $($p.ExitCode)"
|
||||
Start-Sleep -Seconds 3
|
||||
if (Test-Path $lockFile) {
|
||||
$lockAge = (Get-Date) - (Get-Item $lockFile).LastWriteTime
|
||||
if ($lockAge.TotalMinutes -lt 5) { exit 0 }
|
||||
}
|
||||
New-Item $lockFile -Force | Out-Null
|
||||
|
||||
# 3. 启动验证
|
||||
$installed = 'C:\Users\23780\AppData\Local\devflow\devflow.exe'
|
||||
if (Test-Path $installed) {
|
||||
$ver = (Get-Item $installed).VersionInfo.FileVersion
|
||||
Log "已安装二进制: $installed (FileVersion=$ver)"
|
||||
Start-Process -FilePath $installed
|
||||
# 独占打开目标 exe 成功即旧句柄已释放; Windows 文件锁延迟释放会让 NSIS 静默伪成功
|
||||
function Wait-HandleReleased {
|
||||
if (-not (Test-Path $target)) { return $true }
|
||||
$deadline = (Get-Date).AddSeconds(30)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
try {
|
||||
$fs = [IO.File]::Open($target, 'Open', 'ReadWrite', 'None')
|
||||
$fs.Close()
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] handle released" | Add-Content $log
|
||||
return $true
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
}
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] handle wait timeout, continue" | Add-Content $log
|
||||
return $false
|
||||
}
|
||||
|
||||
# 静默安装并校验新 exe, 返回安装后哈希($null=失败)
|
||||
# 基准说明: NSIS 打包器会重写主 exe 资源, 包内哈希原理上不等于磁盘构建产物,
|
||||
# 故哈希比对只作参考, 本质判据=安装器确定性输出(两次装出同一哈希且实变)
|
||||
function Invoke-SwapInstall {
|
||||
param([string]$Attempt)
|
||||
$p = Start-Process $installer -ArgumentList '/S' -Wait -PassThru
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] installer exit=$($p.ExitCode) (attempt $Attempt)" | Add-Content $log
|
||||
if ($p.ExitCode -ne 0) { return $null }
|
||||
if (-not (Test-Path $target)) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] FAIL: target missing after install" | Add-Content $log
|
||||
return $null
|
||||
}
|
||||
$afterHash = (Get-FileHash $target -Algorithm SHA256).Hash
|
||||
$afterSize = (Get-Item $target).Length
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] installed size=$afterSize hash=$($afterHash.Substring(0, 12))" | Add-Content $log
|
||||
return $afterHash
|
||||
}
|
||||
|
||||
try {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] swap start (user=$(whoami))" | Out-File $log -Encoding utf8
|
||||
if ($hashWant) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify baseline: built exe hash=$($hashWant.Substring(0, 12))" | Add-Content $log
|
||||
} else {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify baseline: weak check (built exe absent)" | Add-Content $log
|
||||
}
|
||||
|
||||
# 1. 等旧进程退出(最多 30s), 超时强杀
|
||||
$deadline = (Get-Date).AddSeconds(30)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if (-not (Get-Process devflow -ErrorAction SilentlyContinue)) { break }
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
Get-Process devflow -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
Start-Sleep -Seconds 2
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] old process stopped" | Add-Content $log
|
||||
|
||||
# 2. 探测句柄释放
|
||||
Wait-HandleReleased | Out-Null
|
||||
|
||||
# 3. 安装前快照, 供弱校验与前后比对
|
||||
$before = @{ hash = 'absent'; size = 0 }
|
||||
if (Test-Path $target) {
|
||||
$before.hash = (Get-FileHash $target -Algorithm SHA256).Hash
|
||||
$before.size = (Get-Item $target).Length
|
||||
}
|
||||
$beforeHashShort = $before.hash
|
||||
if ($beforeHashShort.Length -gt 12) { $beforeHashShort = $beforeHashShort.Substring(0, 12) }
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] before size=$($before.size) hash=$beforeHashShort" | Add-Content $log
|
||||
|
||||
# 4. 静默安装 + 校验, 判据三级: 等磁盘快照强过; 两次装出同一哈希且实变为安装器确定性输出; 其余 FAIL
|
||||
$firstHash = Invoke-SwapInstall 'first'
|
||||
$pass = $false
|
||||
if ($firstHash) {
|
||||
if ($hashWant -and $firstHash -eq $hashWant) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify pass (match disk build)" | Add-Content $log
|
||||
$pass = $true
|
||||
} elseif (-not $hashWant -and $firstHash -ne $before.hash) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify pass (weak check, changed from before)" | Add-Content $log
|
||||
$pass = $true
|
||||
} else {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify mismatch disk baseline, retry once" | Add-Content $log
|
||||
Wait-HandleReleased | Out-Null
|
||||
$retryHash = Invoke-SwapInstall 'retry'
|
||||
if ($retryHash -and $firstHash -eq $retryHash) {
|
||||
if ($retryHash -ne $before.hash) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify pass (installer deterministic output $($retryHash.Substring(0, 12)))" | Add-Content $log
|
||||
} else {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] verify pass (already up to date, same version reinstalled)" | Add-Content $log
|
||||
}
|
||||
$pass = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $pass) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] FAIL: verify failed twice" | Add-Content $log
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 5. 重启并验证; GUI 必须起在交互用户会话才可见
|
||||
Start-Process $target
|
||||
Start-Sleep -Seconds 5
|
||||
$new = Get-Process devflow -ErrorAction SilentlyContinue
|
||||
if ($new) { Log "启动成功, PID=$($new.Id -join ',')" } else { Log "警告: 启动后未检测到进程" }
|
||||
} else {
|
||||
Log "警告: 未找到安装后二进制 $installed"
|
||||
}
|
||||
$proc = Get-Process devflow -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($proc) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] OK: restarted pid=$($proc.Id)" | Add-Content $log
|
||||
exit 0
|
||||
}
|
||||
|
||||
Log "=== 换装结束 ==="
|
||||
# SYSTEM 会话直启的 GUI 不可见, 转投交互登录用户名下的一次性计划任务
|
||||
$explorer = Get-CimInstance Win32_Process -Filter "Name='explorer.exe'" | Select-Object -First 1
|
||||
if ($explorer) {
|
||||
$o = Invoke-CimMethod -InputObject $explorer -MethodName GetOwner
|
||||
$user = "$($o.Domain)\$($o.User)"
|
||||
try {
|
||||
$action = New-ScheduledTaskAction -Execute $target
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive
|
||||
Register-ScheduledTask -TaskName 'DevFlowStartOnce' -Action $action -Principal $principal -Force -ErrorAction Stop | Out-Null
|
||||
Start-ScheduledTask -TaskName 'DevFlowStartOnce'
|
||||
Start-Sleep -Seconds 5
|
||||
} catch {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] start task error: $($_.Exception.Message)" | Add-Content $log
|
||||
} finally {
|
||||
Unregister-ScheduledTask -TaskName 'DevFlowStartOnce' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
$proc = Get-Process devflow -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($proc) {
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] OK: restarted as $user pid=$($proc.Id)" | Add-Content $log
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# 仍无进程, 落标记等人工拉起
|
||||
Set-Content -Path 'E:\wk-lab\devflow\swap-need-start.flag' -Value (Get-Date -Format o)
|
||||
"[$(Get-Date -Format 'HH:mm:ss')] FAIL: need manual start" | Add-Content $log
|
||||
exit 1
|
||||
}
|
||||
finally {
|
||||
Remove-Item $lockFile -ErrorAction SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName 'DevFlowSwapInstall' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user