From 9f23c07b7bab04857c6e3599d30422deb9a773a4 Mon Sep 17 00:00:00 2001 From: smail Date: Tue, 1 Sep 2026 11:57:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DWindows=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/install-windows.ps1 | 15 +++++++++++---- deploy/uninstall-windows.ps1 | 2 +- ...�置与邮箱验证码实施任务清单.md | 11 +++++++++++ docs/配置系统重构实施任务清单.md | 10 ++++++++++ .../windows_deployment_policy_test.go | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/deploy/install-windows.ps1 b/deploy/install-windows.ps1 index f9f217d..a9f48c3 100644 --- a/deploy/install-windows.ps1 +++ b/deploy/install-windows.ps1 @@ -1,10 +1,14 @@ -[CmdletBinding()] +[CmdletBinding()] param( - [string]$PackageRoot = (Split-Path -Parent $PSScriptRoot), + [string]$PackageRoot = $null, [string]$InstallRoot = (Join-Path $env:ProgramData "Smail"), [switch]$StartService ) +if ([string]::IsNullOrWhiteSpace($PackageRoot)) { + $PackageRoot = Split-Path -Parent $PSScriptRoot +} + $ErrorActionPreference = "Stop" function Assert-Administrator { @@ -130,8 +134,6 @@ try { if (Test-Path -LiteralPath $adminTarget) { Move-Item -LiteralPath $adminTarget -Destination $adminBackup } Move-Item -LiteralPath (Join-Path $transactionRoot "smail.exe") -Destination $mainTarget Move-Item -LiteralPath (Join-Path $transactionRoot "smail-admin.exe") -Destination $adminTarget - Set-RestrictedAcl -Root $resolvedInstallRoot - $service = Get-Service -Name "Smail" -ErrorAction SilentlyContinue if ($null -eq $service) { $binaryPath = '"' + $mainTarget + '"' @@ -147,6 +149,11 @@ try { "start=", "auto", "obj=", "NT SERVICE\Smail" ) } + Invoke-Native -FilePath "sc.exe" -Arguments @( + "sidtype", "Smail", "unrestricted" + ) + Set-RestrictedAcl -Root $resolvedInstallRoot + $failureActions = "restart/60000/restart/60000/" Invoke-Native -FilePath "sc.exe" -Arguments @( "failure", "Smail", "reset=", "86400", "actions=", $failureActions diff --git a/deploy/uninstall-windows.ps1 b/deploy/uninstall-windows.ps1 index e91e9f4..6be850b 100644 --- a/deploy/uninstall-windows.ps1 +++ b/deploy/uninstall-windows.ps1 @@ -1,4 +1,4 @@ -[CmdletBinding()] +[CmdletBinding()] param( [string]$InstallRoot = (Join-Path $env:ProgramData "Smail") ) diff --git a/docs/超级管理员重置与邮箱验证码实施任务清单.md b/docs/超级管理员重置与邮箱验证码实施任务清单.md index 507dd19..6dac6a8 100644 --- a/docs/超级管理员重置与邮箱验证码实施任务清单.md +++ b/docs/超级管理员重置与邮箱验证码实施任务清单.md @@ -1426,3 +1426,14 @@ AD-065 + CG-053~CG-056 → AD-066 → CG-057 ### 发布结论 三份清单要求的可在当前工作区完成的代码实现已进入待复审状态;真实 Ubuntu 22.04/24.04、Windows Service/ACL/Named Pipe、真实 SMTP Relay、MySQL、PostgreSQL、浏览器三角色、多实例和最终发布安全评审保持阻塞。超级管理员自身重置仍只允许受保护的 UDS 或 Named Pipe,本轮未增加 HTTP、localhost TCP、局域网或公网 TCP 重置入口。 + + +## 2026-09-01 Windows 部署脚本兼容性复核记录 + +- **关联任务**:AD-009~AD-011、AD-061~AD-063。 +- **本轮状态**:`[x] 已完成,等待 Windows 实机复审`。 +- **发现并修复**:Windows PowerShell 5.1 直接执行时,原部署脚本因中文内容缺少 UTF-8 BOM 发生解析错误;安装脚本的 `$PSScriptRoot` 参数默认值在参数绑定阶段为空;ACL 在创建 `NT SERVICE\Smail` 服务 SID 前执行,存在真实安装失败风险。 +- **修改内容**:为 `deploy/install-windows.ps1` 和 `deploy/uninstall-windows.ps1` 增加 UTF-8 BOM;将安装脚本的 `PackageRoot` 改为参数绑定后回退到 `$PSScriptRoot`;创建或配置 `Smail` 服务后先执行 `sc.exe sidtype Smail unrestricted`,再应用服务 ACL。 +- **自动化验证**:`go test ./cmd/smail-admin -count=1 -v` 通过;两个脚本使用 Windows PowerShell 5.1 解析并进入预期的管理员权限拒绝分支;`git diff --check` 通过。 +- **未完成真实验收**:当前用户不是管理员,尚未执行真实 SCM 注册、服务 SID、ACL 拒绝和升级失败回滚验收;Ubuntu、MySQL、PostgreSQL、真实 SMTP、浏览器三角色和最终安全评审仍保持原阻塞。 +- **复审日期**:2026-09-01(本机自动化复核)。 diff --git a/docs/配置系统重构实施任务清单.md b/docs/配置系统重构实施任务清单.md index fd97cba..22c5c1a 100644 --- a/docs/配置系统重构实施任务清单.md +++ b/docs/配置系统重构实施任务清单.md @@ -1328,3 +1328,13 @@ CG-053~CG-056 → AD-066 → CG-057 - **自动化验证**:新增外键等价比较和差异拒绝测试;`go test ./... -count=1 -timeout 30m`、`go test -race ./... -count=1 -timeout 45m`、`go vet ./...`、前端 ESLint 和生产构建均通过。 - **发布边界**:真实 MySQL/PostgreSQL/SQLite 迁移、跨平台部署、Nginx、浏览器、邮件系统、多实例和生产故障恢复仍待实机复审;CG-051、CG-056、CG-057 不升级为最终复审通过。 - **复审日期**:2026-08-31(本机自动化复审)。 + + +## 2026-09-01 Windows 部署脚本兼容性复核记录 + +- **关联任务**:CG-031、CG-051~CG-057。 +- **本轮状态**:`[x] 已完成,等待 Windows 实机复审`。 +- **实施内容**:修复 Windows PowerShell 5.1 的 UTF-8 中文脚本解析兼容性、安装脚本 `PackageRoot` 参数绑定问题,并确保 `Smail` 服务创建和 SID 类型设置完成后再应用服务 ACL;新增静态顺序和脚本编码回归检查。 +- **自动化验证**:`go test ./cmd/smail-admin -count=1 -v`、`go test ./... -count=1 -timeout 30m`、`go vet ./...`、前端生产构建、ESLint 和 `git diff --check` 均通过。 +- **发布边界**:真实 Windows 管理员安装、Ubuntu systemd、三数据库、SMTP、浏览器三角色、多实例和最终安全评审仍未完成,CG-045、CG-051、CG-056、CG-057 不升级为最终复审通过。 +- **复审日期**:2026-09-01(本机自动化复核)。 diff --git a/server/cmd/smail-admin/windows_deployment_policy_test.go b/server/cmd/smail-admin/windows_deployment_policy_test.go index b0e79ba..485598d 100644 --- a/server/cmd/smail-admin/windows_deployment_policy_test.go +++ b/server/cmd/smail-admin/windows_deployment_policy_test.go @@ -18,6 +18,9 @@ func TestWindowsDeploymentScriptsKeepAdminChannelManual(t *testing.T) { "sc.exe", "smail-admin.exe", "Set-RestrictedAcl", + "sidtype", + "$PackageRoot = $null", + "$PackageRoot = Split-Path -Parent $PSScriptRoot", "NT SERVICE\\Smail:(F)", ".backup-", "$mainBackup", @@ -28,6 +31,14 @@ func TestWindowsDeploymentScriptsKeepAdminChannelManual(t *testing.T) { t.Fatalf("Windows installer is missing %q", required) } } + if !hasUTF8BOM(installer) { + t.Fatal("Windows installer must be encoded as UTF-8 with BOM for Windows PowerShell") + } + serviceSetup := strings.Index(content, `"sidtype", "Smail", "unrestricted"`) + aclSetup := strings.Index(content, "Set-RestrictedAcl -Root $resolvedInstallRoot") + if serviceSetup < 0 || aclSetup < 0 || serviceSetup > aclSetup { + t.Fatal("Windows installer must create the service SID before applying service ACLs") + } for _, forbidden := range []string{ "create SmailAdmin", "Start-Service -Name \"SmailAdmin\"", @@ -39,6 +50,10 @@ func TestWindowsDeploymentScriptsKeepAdminChannelManual(t *testing.T) { } } +func hasUTF8BOM(data []byte) bool { + return len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF +} + func TestWindowsUninstallerPreservesData(t *testing.T) { root := filepath.Clean(filepath.Join("..", "..", "..")) uninstaller, err := os.ReadFile(filepath.Join(root, "deploy", "uninstall-windows.ps1")) @@ -46,6 +61,9 @@ func TestWindowsUninstallerPreservesData(t *testing.T) { t.Fatal(err) } content := string(uninstaller) + if !hasUTF8BOM(uninstaller) { + t.Fatal("Windows uninstaller must be encoded as UTF-8 with BOM for Windows PowerShell") + } if strings.Contains(content, "Remove-Item -LiteralPath $resolvedInstallRoot -Recurse") { t.Fatal("Windows uninstaller recursively removes the data root") }