90 lines
6.6 KiB
PowerShell
90 lines
6.6 KiB
PowerShell
[CmdletBinding()]
|
|
param([string]$Configuration = 'Release', [switch]$SkipTests)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$projectRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
$distributionRoot = Join-Path $projectRoot 'dist'
|
|
$payloadRoot = Join-Path $distributionRoot 'payload'
|
|
$setupPublishRoot = Join-Path $distributionRoot 'setup-publish'
|
|
|
|
function Reset-BuildDirectory([string]$Path) {
|
|
$absoluteTarget = [IO.Path]::GetFullPath($Path)
|
|
$allowedRoot = [IO.Path]::GetFullPath($distributionRoot) + [IO.Path]::DirectorySeparatorChar
|
|
if (-not $absoluteTarget.StartsWith($allowedRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to remove outside dist: $absoluteTarget"
|
|
}
|
|
if (Test-Path -LiteralPath $absoluteTarget) {
|
|
if ((Get-Item -LiteralPath $absoluteTarget -Force).Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
throw "Build directory is a reparse point: $absoluteTarget"
|
|
}
|
|
$linkedEntry = Get-ChildItem -LiteralPath $absoluteTarget -Recurse -Force | Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint } | Select-Object -First 1
|
|
if ($linkedEntry) { throw "Build directory contains a reparse point: $($linkedEntry.FullName)" }
|
|
Remove-Item -LiteralPath $absoluteTarget -Recurse -Force
|
|
}
|
|
New-Item -ItemType Directory -Path $absoluteTarget -Force | Out-Null
|
|
}
|
|
|
|
function Invoke-DotNet([string[]]$Arguments) {
|
|
& dotnet @Arguments
|
|
if ($LASTEXITCODE -ne 0) { throw "dotnet failed ($LASTEXITCODE): $($Arguments -join ' ')" }
|
|
}
|
|
|
|
$coreRoot = Join-Path $projectRoot 'tools/core-unpacked/sing-box-1.14.2-windows-amd64'
|
|
$rulesRoot = Join-Path $projectRoot 'tools/rules'
|
|
foreach ($coreFile in @('sing-box.exe', 'libcronet.dll', 'LICENSE')) {
|
|
if (-not (Test-Path -LiteralPath (Join-Path $coreRoot $coreFile) -PathType Leaf)) { throw "Missing packaged VPN core: $coreFile" }
|
|
}
|
|
if (-not (Test-Path -LiteralPath $rulesRoot) -or -not (Get-ChildItem -LiteralPath $rulesRoot -Filter '*.srs' -File)) {
|
|
throw 'Missing routing rules in tools/rules. The installer never downloads dependencies.'
|
|
}
|
|
foreach ($project in @('src/QVPN.App/QVPN.App.csproj', 'src/QVPN.Service/QVPN.Service.csproj', 'src/QVPN.Setup/QVPN.Setup.csproj')) {
|
|
if (-not (Test-Path -LiteralPath (Join-Path $projectRoot $project))) { throw "Missing project: $project" }
|
|
}
|
|
|
|
if (-not $SkipTests) {
|
|
$testProjects = @(Get-ChildItem -LiteralPath (Join-Path $projectRoot 'tests') -Filter '*.csproj' -File -Recurse -ErrorAction SilentlyContinue)
|
|
foreach ($testProject in $testProjects) { Invoke-DotNet -Arguments @('run', '--project', $testProject.FullName, '-c', $Configuration) }
|
|
}
|
|
|
|
New-Item -ItemType Directory -Path $distributionRoot -Force | Out-Null
|
|
Reset-BuildDirectory $payloadRoot
|
|
Reset-BuildDirectory $setupPublishRoot
|
|
Write-Host 'Publishing Windows UI and service with the bundled .NET runtime...'
|
|
Invoke-DotNet -Arguments @('publish', (Join-Path $projectRoot 'src/QVPN.App/QVPN.App.csproj'), '-c', $Configuration, '-r', 'win-x64', '--self-contained', 'true', '-p:PublishSingleFile=true', '-p:IncludeNativeLibrariesForSelfExtract=true', '-o', (Join-Path $payloadRoot 'app'))
|
|
Invoke-DotNet -Arguments @('publish', (Join-Path $projectRoot 'src/QVPN.Service/QVPN.Service.csproj'), '-c', $Configuration, '-r', 'win-x64', '--self-contained', 'true', '-p:PublishSingleFile=false', '-o', (Join-Path $payloadRoot 'service'))
|
|
New-Item -ItemType Directory -Path (Join-Path $payloadRoot 'core'), (Join-Path $payloadRoot 'rules') -Force | Out-Null
|
|
Copy-Item -LiteralPath (Join-Path $coreRoot 'sing-box.exe'), (Join-Path $coreRoot 'libcronet.dll'), (Join-Path $coreRoot 'LICENSE') -Destination (Join-Path $payloadRoot 'core')
|
|
Get-ChildItem -LiteralPath $rulesRoot -Filter '*.srs' -File | Copy-Item -Destination (Join-Path $payloadRoot 'rules')
|
|
if (Test-Path -LiteralPath (Join-Path $projectRoot 'README.md')) { Copy-Item -LiteralPath (Join-Path $projectRoot 'README.md') -Destination (Join-Path $payloadRoot 'README.md') }
|
|
if (Test-Path -LiteralPath (Join-Path $PSScriptRoot '../src/QVPN.Setup/COPYING')) { Copy-Item -LiteralPath (Join-Path $PSScriptRoot '../src/QVPN.Setup/COPYING') -Destination (Join-Path $payloadRoot 'core/COPYING') }
|
|
if (Test-Path -LiteralPath (Join-Path $projectRoot 'tools/sing-box-1.14.2-source.zip')) { Copy-Item -LiteralPath (Join-Path $projectRoot 'tools/sing-box-1.14.2-source.zip') -Destination (Join-Path $payloadRoot 'core/sing-box-1.14.2-source.zip') }
|
|
@'
|
|
QVPN includes sing-box v1.14.2 (Windows amd64), copyright nekohasekai / SagerNet.
|
|
sing-box is distributed under GNU GPL v3 or later with the notice in core/LICENSE.
|
|
Upstream source archive for the unmodified binary: core/sing-box-1.14.2-source.zip
|
|
Source tree and module/build instructions: https://github.com/SagerNet/sing-box/tree/v1.14.2
|
|
Release and original archive: https://github.com/SagerNet/sing-box/releases/tag/v1.14.2
|
|
The bundled rule sets are from https://github.com/SagerNet/sing-geosite and https://github.com/SagerNet/sing-geoip.
|
|
See the respective repositories for source data and notices.
|
|
'@ | Set-Content -LiteralPath (Join-Path $payloadRoot 'THIRD-PARTY-NOTICES.txt') -Encoding utf8
|
|
|
|
Get-ChildItem -LiteralPath $payloadRoot -Filter '*.pdb' -Recurse -File | Remove-Item -Force
|
|
$manifest = [ordered]@{}
|
|
foreach ($file in (Get-ChildItem -LiteralPath $payloadRoot -Recurse -File | Sort-Object FullName)) {
|
|
$relativePath = [IO.Path]::GetRelativePath($payloadRoot, $file.FullName)
|
|
$manifest[$relativePath] = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
|
|
}
|
|
$manifest | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (Join-Path $payloadRoot 'payload-manifest.json') -Encoding utf8
|
|
$payloadZip = Join-Path $distributionRoot 'payload.zip'
|
|
if (Test-Path -LiteralPath $payloadZip) { Remove-Item -LiteralPath $payloadZip -Force }
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[IO.Compression.ZipFile]::CreateFromDirectory($payloadRoot, $payloadZip, [IO.Compression.CompressionLevel]::Optimal, $false)
|
|
Write-Host 'Embedding the complete payload in the offline Windows installer...'
|
|
Invoke-DotNet -Arguments @('publish', (Join-Path $projectRoot 'src/QVPN.Setup/QVPN.Setup.csproj'), '-c', $Configuration, '-r', 'win-x64', '--self-contained', 'true', '-o', $setupPublishRoot)
|
|
$installer = Join-Path $distributionRoot 'QVPN-Setup-1.0.0.exe'
|
|
Copy-Item -LiteralPath (Join-Path $setupPublishRoot 'QVPN-Setup.exe') -Destination $installer -Force
|
|
$installerHash = (Get-FileHash -LiteralPath $installer -Algorithm SHA256).Hash
|
|
"$installerHash QVPN-Setup-1.0.0.exe" | Set-Content -LiteralPath (Join-Path $distributionRoot 'QVPN-Setup-1.0.0.sha256') -Encoding ascii
|
|
Write-Host "Installer: $installer"
|
|
Write-Host "SHA256: $installerHash"
|