增加发布来源追溯和实机部署锁冲突验收

This commit is contained in:
xuans 2026-09-15 20:20:34 +08:00
parent a4dffb04d4
commit 4338f63e3d
3 changed files with 75 additions and 9 deletions

View File

@ -122,6 +122,25 @@ func TestRemoteDeploymentIsolatedAcceptance(t *testing.T) {
} }
} }
verify() verify()
// 真实连接竞争同一文件锁时必须拒绝部署,也不得释放另一事务的锁。
lockClient, err := factory.Connect(t.Context(), server)
if err != nil {
t.Fatal(err)
}
lockPath := server.PrivateKeyPath + ".scert-lock"
if err := lockClient.Lock(t.Context(), lockPath); err != nil {
_ = lockClient.Close()
t.Fatal(err)
}
lockedReport := service.Deploy(t.Context(), []Target{{Server: server}}, original)
lockExists, lockErr := lockClient.Exists(t.Context(), lockPath)
unlockErr := lockClient.Unlock(t.Context(), lockPath)
_ = lockClient.Close()
if lockedReport.Status != "failed" || !strings.Contains(lockedReport.Results[0].Message, "无法取得部署锁") || !lockExists || lockErr != nil || unlockErr != nil {
t.Fatalf("真实部署锁冲突未安全拒绝:%+v exists=%t check=%v unlock=%v", lockedReport, lockExists, lockErr, unlockErr)
}
t.Log("真实部署锁冲突已拒绝,其他事务的锁保持不变")
verify()
for _, scenario := range []string{"validation_failure", "reload_failure", "validation_timeout"} { for _, scenario := range []string{"validation_failure", "reload_failure", "validation_timeout"} {
candidate := server candidate := server
ctx := t.Context() ctx := t.Context()

View File

@ -4,14 +4,20 @@
```text ```text
build/release/ build/release/
├── manifest.json ├── Scert-20260915-V0.1.0/
├── windows-amd64/ │ ├── manifest.json
│ ├── scert.exe │ ├── web-build/
│ ├── scert-admin.exe │ ├── windows-amd64/
│ └── ScertDesktop.exe │ │ ├── scert.exe
└── linux-amd64/ │ │ ├── scert-admin.exe
├── scert │ │ └── ScertDesktop.exe
└── scert-admin │ └── linux-amd64/
│ ├── scert
│ ├── scert-admin
│ ├── web/
│ └── SHA256SUMS
├── Scert-20260915-V0.1.0-linux-amd64.tar.gz
└── Scert-20260915-V0.1.0-windows-amd64.zip
``` ```
## 使用方法 ## 使用方法
@ -42,6 +48,16 @@ powershell -ExecutionPolicy Bypass -File .\build\build-release.ps1 -OutputDirect
桌面版构建需要已安装 Wails v2、Go 和 Node.js。Wails 桌面版使用当前主机原生平台构建;Windows 主机不能直接完成 Linux GTK 桌面交叉构建,Linux 桌面产物需要在 Linux 主机上执行同一脚本。 桌面版构建需要已安装 Wails v2、Go 和 Node.js。Wails 桌面版使用当前主机原生平台构建;Windows 主机不能直接完成 Linux GTK 桌面交叉构建,Linux 桌面产物需要在 Linux 主机上执行同一脚本。
脚本不会删除输出目录中的其他文件,只会创建目录并覆盖本次目标平台对应的程序文件和校验清单。 脚本仅创建新的发布目录。发布目录或对应归档已存在时,在构建前拒绝执行,不覆盖旧包。前端直接构建到本次发布目录中的 `web-build/`,不清空或覆盖已有 `frontend/dist/`。失败产生的目录保留供排查,再次构建请使用新的输出目录。
`manifest.json` 记录源码提交、已跟踪文件是否未提交、实际 Go 工具链、目标平台、是否请求桌面端以及文件和归档的 SHA256。`source_dirty=true` 的包仅用于开发,正式发布必须从已复审提交构建。未跟踪本地产物不计入源码脏状态,也不会自动打包。
仅构建本轮 Linux 服务候选包示例:
```powershell
powershell -ExecutionPolicy Bypass -File .\build\build-release.ps1 -Target linux-amd64 -SkipDesktop -OutputDirectory .\build\release-candidate-20260915
```
此命令不构建 Windows 或 Wails 桌面端,不代表跨平台发布验收。归档包含 `scert`、`scert-admin`、Web 静态资源、安装器、systemd 模板和部署说明。解压后先运行 `sha256sum -c SHA256SUMS`,再按部署说明操作。
构建通过不等于生产验收通过;真实 DNS、ACME、SSH、Windows Service、systemd、WebView2 和 HTTPS 仍需在授权环境复核。 构建通过不等于生产验收通过;真实 DNS、ACME、SSH、Windows Service、systemd、WebView2 和 HTTPS 仍需在授权环境复核。

View File

@ -261,6 +261,28 @@ $releaseDirectory = Join-Path $output $releaseName
if (Test-Path -LiteralPath $releaseDirectory) { if (Test-Path -LiteralPath $releaseDirectory) {
throw "发布目录已存在,拒绝覆盖:$releaseDirectory" throw "发布目录已存在,拒绝覆盖:$releaseDirectory"
} }
if (@($Target | Select-Object -Unique).Count -ne $Target.Count) {
throw "目标平台不能重复。"
}
foreach ($item in $Target) {
$suffix = if ($item.StartsWith("linux-")) { "tar.gz" } else { "zip" }
$archivePath = Join-Path $output "$releaseName-$item.$suffix"
if (Test-Path -LiteralPath $archivePath) {
throw "发布归档已存在,拒绝覆盖:$archivePath"
}
}
$sourceCommit = (& git -C $backend rev-parse HEAD)
if ($LASTEXITCODE -ne 0) {
throw "无法取得源码提交,不能生成可追溯发布包。"
}
$sourceChanges = @(& git -C $backend status --porcelain --untracked-files=no)
if ($LASTEXITCODE -ne 0) {
throw "无法检查源码工作区状态。"
}
$sourceDirty = $sourceChanges.Count -gt 0
if ($sourceDirty) {
Write-Warning "存在未提交的已跟踪文件改动,清单将标记 source_dirty=true,不能作为正式发布包。"
}
New-Item -ItemType Directory -Path $releaseDirectory | Out-Null New-Item -ItemType Directory -Path $releaseDirectory | Out-Null
$frontendDistribution = Join-Path $releaseDirectory "web-build" $frontendDistribution = Join-Path $releaseDirectory "web-build"
@ -287,6 +309,10 @@ $hostDesktopTarget = Get-HostDesktopTarget
Push-Location $backend Push-Location $backend
try { try {
$goVersion = (& go version)
if ($LASTEXITCODE -ne 0) {
throw "无法取得实际构建使用的 Go 工具链版本。"
}
foreach ($item in $Target) { foreach ($item in $Target) {
$parts = $item.Split("-", 2) $parts = $item.Split("-", 2)
$goos = $parts[0] $goos = $parts[0]
@ -379,6 +405,11 @@ $manifest = [ordered]@{
product = "Scert" product = "Scert"
release_date = $ReleaseDate release_date = $ReleaseDate
version = $Version version = $Version
source_commit = $sourceCommit.Trim()
source_dirty = $sourceDirty
go_toolchain = $goVersion.Trim()
targets = @($Target)
desktop_requested = -not [bool]$SkipDesktop
generated_at = [DateTimeOffset]::UtcNow.ToString("o") generated_at = [DateTimeOffset]::UtcNow.ToString("o")
artifacts = $manifestArtifacts artifacts = $manifestArtifacts
} }