fix(packaging): 修复 Windows 安装与固件打包并完善客户端文案
- fix(installer): 修复低完整性产物无法写入临时目录的问题,并在 CI 打包前后检查完整性标签 - fix(firmware): 从本次 Cargo ELF 打包并同步配套镜像,修正短目录说明与 LLVM 搜索顺序 - fix(installer): 修复中文完成页和页眉遮挡,侧栏使用等比缩放的原始 Logo - style(client): 应用图标增加圆角,统一“裁判系统客户端”命名 - fix(config): 安装提示改用可选本地热点配置,排除 Wi-Fi 凭据 - docs: 润色项目说明并统一裁判系统术语 - test(packaging): 补充 Windows 完整性和固件打包回归,覆盖外部输出目录、旧镜像与重复生成 验证:Windows NSIS 构建及原生页面预览;通用与本地安装配置编译;固件 release 构建、重复打包和客户端离线校验;6 项固件打包回归与 Windows 权限回归;git diff --check。未执行实机烧录。
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Windows uses the executable's integrity label when creating its process. A Low
|
||||
# label inherited from a sandboxed workspace prevents NSIS from writing to TEMP,
|
||||
# before .onInit or any installer hook can run.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputDir,
|
||||
[switch]$CheckProcessOnly,
|
||||
[switch]$CheckOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# whoami's group names are localized; the mandatory-label SID is stable.
|
||||
$groups = (& whoami.exe /groups) -join "`n"
|
||||
if ($LASTEXITCODE -ne 0 -or $groups -notmatch 'S-1-16-(\d+)') {
|
||||
throw 'Cannot determine the Windows process integrity level.'
|
||||
}
|
||||
if ([int]$Matches[1] -lt 8192) {
|
||||
throw 'Windows packaging requires Medium integrity or higher. Start the terminal / CI runner outside the Low integrity sandbox. Redirecting TEMP or changing directory write permissions does not fix executable integrity labels.'
|
||||
}
|
||||
if ($CheckProcessOnly) { return }
|
||||
|
||||
if (-not ('RmicBundleIntegrity' -as [type])) {
|
||||
$compilerOptions = @{}
|
||||
if ($PSVersionTable.PSEdition -eq 'Desktop') {
|
||||
# Framework CodeDOM otherwise tries to label its private temp directory
|
||||
# High when an admin token has been capped at Medium by an exe label.
|
||||
$parameters = New-Object System.CodeDom.Compiler.CompilerParameters
|
||||
$parameters.ReferencedAssemblies.Add('System.dll') | Out-Null
|
||||
$parameters.TempFiles = New-Object System.CodeDom.Compiler.TempFileCollection([IO.Path]::GetTempPath(), $false)
|
||||
$compilerOptions.CompilerParameters = $parameters
|
||||
}
|
||||
Add-Type @compilerOptions -TypeDefinition @'
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class RmicBundleIntegrity {
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint GetNamedSecurityInfoW(string name, int type, uint info,
|
||||
out IntPtr owner, out IntPtr group, out IntPtr dacl, out IntPtr sacl, out IntPtr sd);
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool ConvertSecurityDescriptorToStringSecurityDescriptorW(
|
||||
IntPtr sd, uint revision, uint info, out IntPtr text, out uint length);
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr LocalFree(IntPtr memory);
|
||||
|
||||
public static string ReadLabel(string path) {
|
||||
// LABEL_SECURITY_INFORMATION reads only the label, without requiring the
|
||||
// SeSecurityPrivilege needed to read an entire SACL (Get-Acl -Audit).
|
||||
IntPtr owner, group, dacl, sacl, sd, text = IntPtr.Zero;
|
||||
uint error = GetNamedSecurityInfoW(path, 1, 0x10, out owner, out group,
|
||||
out dacl, out sacl, out sd);
|
||||
if (error != 0) throw new Win32Exception((int)error, path);
|
||||
try {
|
||||
uint length;
|
||||
if (!ConvertSecurityDescriptorToStringSecurityDescriptorW(sd, 1, 0x10,
|
||||
out text, out length)) throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
return Marshal.PtrToStringUni(text);
|
||||
} finally {
|
||||
if (text != IntPtr.Zero) LocalFree(text);
|
||||
LocalFree(sd);
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
}
|
||||
|
||||
function Assert-MediumIntegrity([string]$Path) {
|
||||
$label = [RmicBundleIntegrity]::ReadLabel($Path)
|
||||
# An absent label is Medium. Windows may render known SIDs as SDDL aliases.
|
||||
if ($label -match ';(LW|UN|S-1-16-(\d+))\)') {
|
||||
if ($Matches[1] -in @('LW', 'UN') -or [int]$Matches[2] -lt 8192) {
|
||||
throw "Low / Untrusted integrity label on $Path. Run prepare-windows-bundle.ps1 from a normal Windows terminal before distributing this artifact."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hostOutputDir = $null
|
||||
if (-not $OutputDir) {
|
||||
# Ask Cargo so CARGO_TARGET_DIR and Cargo's target-dir setting are respected.
|
||||
$manifest = Join-Path $PSScriptRoot '../src-tauri/Cargo.toml'
|
||||
$metadata = & cargo +stable metadata --no-deps --format-version 1 --manifest-path $manifest
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Cannot resolve the Cargo output directory.' }
|
||||
$target = $env:TAURI_ENV_TARGET_TRIPLE
|
||||
if (-not $target) { $target = 'x86_64-pc-windows-msvc' }
|
||||
$profile = if ($env:TAURI_ENV_DEBUG -eq 'true') { 'debug' } else { 'release' }
|
||||
$targetDir = ($metadata | ConvertFrom-Json).target_directory
|
||||
$OutputDir = Join-Path $targetDir "$target/$profile"
|
||||
# Cross-target Cargo builds also run host build scripts from target/<profile>.
|
||||
# Old Low executables there can fail before the bundling hook is reached.
|
||||
$hostOutputDir = Join-Path $targetDir $profile
|
||||
}
|
||||
$OutputDir = [IO.Path]::GetFullPath($OutputDir)
|
||||
|
||||
# Limit changes to build outputs. Do not relabel the workspace, TEMP, user data,
|
||||
# or DACLs, and do not request elevation for the installer.
|
||||
$directories = @(
|
||||
$OutputDir
|
||||
(Join-Path $OutputDir 'bundle')
|
||||
(Join-Path $OutputDir 'bundle/nsis')
|
||||
(Join-Path $OutputDir 'nsis')
|
||||
)
|
||||
if ($hostOutputDir) { $directories = @($hostOutputDir) + $directories }
|
||||
if (Test-Path -LiteralPath (Join-Path $OutputDir 'nsis')) {
|
||||
$directories += @(Get-ChildItem -LiteralPath (Join-Path $OutputDir 'nsis') -Directory -Recurse |
|
||||
Select-Object -ExpandProperty FullName)
|
||||
}
|
||||
foreach ($directory in $directories) {
|
||||
if (-not $CheckOnly) {
|
||||
New-Item -ItemType Directory -Force -Path $directory | Out-Null
|
||||
& icacls.exe $directory /setintegritylevel '(OI)(CI)M' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "Cannot set Medium integrity on $directory" }
|
||||
}
|
||||
Assert-MediumIntegrity $directory
|
||||
}
|
||||
|
||||
$executables = @(Get-ChildItem -LiteralPath $OutputDir -Filter '*.exe' -File)
|
||||
foreach ($directory in @('nsis', 'bundle/nsis')) {
|
||||
$executables += @(Get-ChildItem -LiteralPath (Join-Path $OutputDir $directory) -Filter '*.exe' -File -Recurse)
|
||||
}
|
||||
if ($hostOutputDir -and (Test-Path -LiteralPath (Join-Path $hostOutputDir 'build'))) {
|
||||
$executables += @(Get-ChildItem -LiteralPath (Join-Path $hostOutputDir 'build') -Filter '*.exe' -File -Recurse)
|
||||
}
|
||||
foreach ($executable in $executables) {
|
||||
if (-not $CheckOnly) {
|
||||
# Also replace explicit Low labels left by an earlier Low integrity build.
|
||||
& icacls.exe $executable.FullName /setintegritylevel M | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "Cannot set Medium integrity on $($executable.FullName)" }
|
||||
}
|
||||
Assert-MediumIntegrity $executable.FullName
|
||||
}
|
||||
if ($CheckOnly -and -not (Get-ChildItem -LiteralPath (Join-Path $OutputDir 'bundle/nsis') -Filter '*.exe' -File)) {
|
||||
throw "No NSIS installer found in $OutputDir/bundle/nsis."
|
||||
}
|
||||
Write-Host "Windows bundle integrity verified: $OutputDir ($($executables.Count) executables)."
|
||||
Reference in New Issue
Block a user