feat.Penguin Logistics Online Network Distribution System

This commit is contained in:
lincube
2026-04-20 23:28:11 +08:00
parent 3f927c41c8
commit a31ae3cd58
47 changed files with 2446 additions and 822 deletions

View File

@@ -0,0 +1,87 @@
param(
[Parameter(Mandatory = $true)]
[string]$CurrentVersion,
[Parameter(Mandatory = $true)]
[string]$CurrentDir,
[Parameter(Mandatory = $true)]
[string]$Platform,
[Parameter(Mandatory = $true)]
[string]$OutputDir,
[Parameter(Mandatory = $false)]
[string]$PreviousVersion = "",
[Parameter(Mandatory = $false)]
[string]$PreviousDir = "",
[Parameter(Mandatory = $false)]
[string]$Channel = "stable",
[Parameter(Mandatory = $false)]
[string]$DistributionId = "",
[Parameter(Mandatory = $false)]
[string]$RepoBaseUrl = "",
[Parameter(Mandatory = $false)]
[string]$FileMapUrl = "",
[Parameter(Mandatory = $false)]
[string]$FileMapSignatureUrl = "",
[Parameter(Mandatory = $false)]
[string]$InstallerDirectory = "",
[Parameter(Mandatory = $false)]
[string]$InstallerBaseUrl = ""
)
$ErrorActionPreference = "Stop"
$toolProject = Join-Path $PSScriptRoot "..\PenguinLogisticsOnlineNetworkDistributionSystem\src\Plonds.Tool\Plonds.Tool.csproj"
if (-not (Test-Path -LiteralPath $toolProject)) {
throw "PLONDS tool project not found: $toolProject"
}
$arguments = @(
"run",
"--project", $toolProject,
"--",
"generate",
"--current-version", $CurrentVersion,
"--current-dir", $CurrentDir,
"--platform", $Platform,
"--output-dir", $OutputDir,
"--previous-version", $(if ([string]::IsNullOrWhiteSpace($PreviousVersion)) { "0.0.0" } else { $PreviousVersion }),
"--channel", $Channel
)
if (-not [string]::IsNullOrWhiteSpace($PreviousDir)) {
$arguments += @("--previous-dir", $PreviousDir)
}
if (-not [string]::IsNullOrWhiteSpace($DistributionId)) {
$arguments += @("--distribution-id", $DistributionId)
}
if (-not [string]::IsNullOrWhiteSpace($RepoBaseUrl)) {
$arguments += @("--repo-base-url", $RepoBaseUrl)
}
if (-not [string]::IsNullOrWhiteSpace($FileMapUrl)) {
$arguments += @("--file-map-url", $FileMapUrl)
}
if (-not [string]::IsNullOrWhiteSpace($FileMapSignatureUrl)) {
$arguments += @("--file-map-signature-url", $FileMapSignatureUrl)
}
if (-not [string]::IsNullOrWhiteSpace($InstallerDirectory)) {
$arguments += @("--installer-directory", $InstallerDirectory)
}
if (-not [string]::IsNullOrWhiteSpace($InstallerBaseUrl)) {
$arguments += @("--installer-base-url", $InstallerBaseUrl)
}
& dotnet @arguments
if ($LASTEXITCODE -ne 0) {
throw "PLONDS generate command failed."
}

301
scripts/Publish-Plonds.ps1 Normal file
View File

@@ -0,0 +1,301 @@
param(
[Parameter(Mandatory = $true)]
[string]$Version,
[Parameter(Mandatory = $true)]
[string]$AppArtifactsRoot,
[Parameter(Mandatory = $true)]
[string]$InstallerArtifactsRoot,
[Parameter(Mandatory = $true)]
[string]$OutputDir,
[Parameter(Mandatory = $true)]
[string]$PrivateKeyPath,
[Parameter(Mandatory = $false)]
[string]$Channel = "stable",
[Parameter(Mandatory = $false)]
[string]$S3Endpoint = "",
[Parameter(Mandatory = $false)]
[string]$S3Bucket = "",
[Parameter(Mandatory = $false)]
[string]$S3Region = ""
)
$ErrorActionPreference = "Stop"
function Get-PlatformConfigurations {
return @(
@{
Platform = "windows-x64"
ArtifactName = "app-payload-windows-x64"
},
@{
Platform = "windows-x86"
ArtifactName = "app-payload-windows-x86"
},
@{
Platform = "linux-x64"
ArtifactName = "app-payload-linux-x64"
}
)
}
function Resolve-AppDirectory {
param(
[Parameter(Mandatory = $true)]
[string]$SearchRoot,
[Parameter(Mandatory = $true)]
[string]$Version
)
$preferred = Get-ChildItem -LiteralPath $SearchRoot -Recurse -Directory -Filter "app-$Version" -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($preferred) {
return $preferred.FullName
}
$fallback = Get-ChildItem -LiteralPath $SearchRoot -Recurse -Directory -Filter "app-*" -ErrorAction SilentlyContinue |
Sort-Object FullName |
Select-Object -First 1
return $fallback?.FullName
}
function Invoke-AwsSyncIfPossible {
param(
[Parameter(Mandatory = $true)]
[string[]]$Arguments,
[Parameter(Mandatory = $false)]
[switch]$IgnoreFailure
)
if ([string]::IsNullOrWhiteSpace($S3Endpoint) -or [string]::IsNullOrWhiteSpace($S3Bucket)) {
return
}
& aws @Arguments
if ($LASTEXITCODE -ne 0 -and -not $IgnoreFailure) {
throw "aws command failed: aws $($Arguments -join ' ')"
}
}
if (-not (Test-Path -LiteralPath $PrivateKeyPath)) {
throw "Private key file not found: $PrivateKeyPath"
}
$toolProject = Join-Path $PSScriptRoot "..\PenguinLogisticsOnlineNetworkDistributionSystem\src\Plonds.Tool\Plonds.Tool.csproj"
if (-not (Test-Path -LiteralPath $toolProject)) {
throw "PLONDS tool project not found: $toolProject"
}
$supportedPlatforms = Get-PlatformConfigurations
$publishedRoot = Join-Path $OutputDir "published"
$releaseAssetsRoot = Join-Path $OutputDir "release-assets"
$baselineRoot = Join-Path $OutputDir "_baselines"
New-Item -ItemType Directory -Path $publishedRoot -Force | Out-Null
New-Item -ItemType Directory -Path $releaseAssetsRoot -Force | Out-Null
New-Item -ItemType Directory -Path $baselineRoot -Force | Out-Null
foreach ($config in $supportedPlatforms) {
$platform = $config.Platform
$platformBaselineRoot = Join-Path $baselineRoot $platform
$baselineCurrentDir = Join-Path $platformBaselineRoot "current"
$baselineVersionPath = Join-Path $platformBaselineRoot "version.txt"
New-Item -ItemType Directory -Path $baselineCurrentDir -Force | Out-Null
if (-not [string]::IsNullOrWhiteSpace($S3Endpoint) -and -not [string]::IsNullOrWhiteSpace($S3Bucket)) {
Invoke-AwsSyncIfPossible -Arguments @(
"--endpoint-url", $S3Endpoint,
"--region", $S3Region,
"s3", "sync",
"s3://$S3Bucket/lanmountain/update/baselines/$platform/current/",
$baselineCurrentDir,
"--only-show-errors"
) -IgnoreFailure
Invoke-AwsSyncIfPossible -Arguments @(
"--endpoint-url", $S3Endpoint,
"--region", $S3Region,
"s3", "cp",
"s3://$S3Bucket/lanmountain/update/baselines/$platform/version.txt",
$baselineVersionPath,
"--only-show-errors"
) -IgnoreFailure
}
}
$repoBaseUrl = if ([string]::IsNullOrWhiteSpace($S3Endpoint) -or [string]::IsNullOrWhiteSpace($S3Bucket)) {
$null
}
else {
"$($S3Endpoint.TrimEnd('/'))/$S3Bucket/lanmountain/update/repo/sha256"
}
$installerBaseUrl = if ([string]::IsNullOrWhiteSpace($S3Endpoint) -or [string]::IsNullOrWhiteSpace($S3Bucket)) {
$null
}
else {
"$($S3Endpoint.TrimEnd('/'))/$S3Bucket/lanmountain/update/installers"
}
$legacySnapshots = @{}
foreach ($config in $supportedPlatforms) {
$platform = $config.Platform
$platformBaselineRoot = Join-Path $baselineRoot $platform
$baselineCurrentDir = Join-Path $platformBaselineRoot "current"
$baselineVersionPath = Join-Path $platformBaselineRoot "version.txt"
$snapshotRoot = Join-Path $platformBaselineRoot "previous-snapshot"
if (Test-Path -LiteralPath $snapshotRoot) {
Remove-Item -LiteralPath $snapshotRoot -Recurse -Force
}
New-Item -ItemType Directory -Path $snapshotRoot -Force | Out-Null
$previousVersion = if (Test-Path -LiteralPath $baselineVersionPath) {
(Get-Content -LiteralPath $baselineVersionPath -Raw).Trim()
}
else {
"0.0.0"
}
$baselineHasContent = Get-ChildItem -LiteralPath $baselineCurrentDir -Force -ErrorAction SilentlyContinue | Select-Object -First 1
if ($baselineHasContent) {
Copy-Item -LiteralPath (Join-Path $baselineCurrentDir '*') -Destination $snapshotRoot -Recurse -Force
$snapshotDir = $snapshotRoot
}
else {
$snapshotDir = Join-Path $platformBaselineRoot "empty"
New-Item -ItemType Directory -Path $snapshotDir -Force | Out-Null
}
$legacySnapshots[$platform] = @{
PreviousVersion = $previousVersion
PreviousDir = $snapshotDir
}
}
$publishArguments = @(
"run",
"--project", $toolProject,
"--",
"publish",
"--version", $Version,
"--app-artifacts-root", $AppArtifactsRoot,
"--installer-artifacts-root", $InstallerArtifactsRoot,
"--output-dir", $publishedRoot,
"--private-key", $PrivateKeyPath,
"--baseline-root", $baselineRoot,
"--channel", $Channel
)
if (-not [string]::IsNullOrWhiteSpace($repoBaseUrl)) {
$publishArguments += @("--repo-base-url", $repoBaseUrl)
}
if (-not [string]::IsNullOrWhiteSpace($installerBaseUrl)) {
$publishArguments += @("--installer-base-url", $installerBaseUrl)
}
& dotnet @publishArguments
if ($LASTEXITCODE -ne 0) {
throw "PLONDS publish command failed."
}
foreach ($config in $supportedPlatforms) {
$platform = $config.Platform
$artifactRoot = Join-Path $AppArtifactsRoot $config.ArtifactName
if (-not (Test-Path -LiteralPath $artifactRoot)) {
throw "App payload artifact root not found for ${platform}: $artifactRoot"
}
$currentAppDir = Resolve-AppDirectory -SearchRoot $artifactRoot -Version $Version
if ([string]::IsNullOrWhiteSpace($currentAppDir)) {
throw "Unable to locate app payload directory for $platform under $artifactRoot"
}
$distributionId = "plonds-$Version-$platform"
$manifestPath = Join-Path $publishedRoot "manifests/$distributionId/plonds-filemap.json"
$manifestSignaturePath = "$manifestPath.sig"
$legacyOutputDir = Join-Path $OutputDir "legacy/$platform"
New-Item -ItemType Directory -Path $legacyOutputDir -Force | Out-Null
$legacyState = $legacySnapshots[$platform]
& (Join-Path $PSScriptRoot "Generate-DeltaPackage.ps1") `
-PreviousVersion $legacyState.PreviousVersion `
-CurrentVersion $Version `
-PreviousDir $legacyState.PreviousDir `
-CurrentDir $currentAppDir `
-OutputDir $legacyOutputDir
if ($LASTEXITCODE -ne 0) {
throw "Generate-DeltaPackage.ps1 failed for $platform"
}
$legacyManifestPath = Join-Path $legacyOutputDir "files.json"
$legacySignaturePath = Join-Path $legacyOutputDir "files.json.sig"
& (Join-Path $PSScriptRoot "Sign-FileMap.ps1") `
-FilesJsonPath $legacyManifestPath `
-PrivateKeyPath $PrivateKeyPath `
-OutputPath $legacySignaturePath
if ($LASTEXITCODE -ne 0) {
throw "Failed to sign legacy manifest for $platform"
}
Copy-Item -LiteralPath $manifestPath -Destination (Join-Path $releaseAssetsRoot "plonds-filemap-$platform.json") -Force
Copy-Item -LiteralPath $manifestSignaturePath -Destination (Join-Path $releaseAssetsRoot "plonds-filemap-$platform.json.sig") -Force
Copy-Item -LiteralPath (Join-Path $publishedRoot "meta/distributions/$distributionId.json") -Destination (Join-Path $releaseAssetsRoot "plonds-distribution-$platform.json") -Force
Copy-Item -LiteralPath (Join-Path $publishedRoot "meta/channels/$Channel/$platform/latest.json") -Destination (Join-Path $releaseAssetsRoot "plonds-latest-$platform.json") -Force
Copy-Item -LiteralPath $legacyManifestPath -Destination (Join-Path $releaseAssetsRoot "files-$platform.json") -Force
Copy-Item -LiteralPath $legacySignaturePath -Destination (Join-Path $releaseAssetsRoot "files-$platform.json.sig") -Force
Copy-Item -LiteralPath (Join-Path $legacyOutputDir "update.zip") -Destination (Join-Path $releaseAssetsRoot "update-$platform.zip") -Force
}
if (-not [string]::IsNullOrWhiteSpace($S3Endpoint) -and -not [string]::IsNullOrWhiteSpace($S3Bucket)) {
Invoke-AwsSyncIfPossible -Arguments @(
"--endpoint-url", $S3Endpoint,
"--region", $S3Region,
"s3", "sync",
$publishedRoot,
"s3://$S3Bucket/lanmountain/update/",
"--only-show-errors"
)
foreach ($config in $supportedPlatforms) {
$platform = $config.Platform
$platformBaselineRoot = Join-Path $baselineRoot $platform
$baselineCurrentDir = Join-Path $platformBaselineRoot "current"
$baselineVersionPath = Join-Path $platformBaselineRoot "version.txt"
Invoke-AwsSyncIfPossible -Arguments @(
"--endpoint-url", $S3Endpoint,
"--region", $S3Region,
"s3", "sync",
$baselineCurrentDir,
"s3://$S3Bucket/lanmountain/update/baselines/$platform/current/",
"--delete",
"--only-show-errors"
)
Invoke-AwsSyncIfPossible -Arguments @(
"--endpoint-url", $S3Endpoint,
"--region", $S3Region,
"s3", "cp",
$baselineVersionPath,
"s3://$S3Bucket/lanmountain/update/baselines/$platform/version.txt",
"--only-show-errors"
)
}
}
Write-Host "PLONDS publish staging completed."
Write-Host "Published root: $publishedRoot"
Write-Host "Release assets: $releaseAssetsRoot"

View File

@@ -1,4 +1,4 @@
param(
param(
[Parameter(Mandatory = $true)]
[string]$FilesJsonPath,
@@ -11,46 +11,16 @@ param(
$ErrorActionPreference = "Stop"
if ($PSVersionTable.PSVersion.Major -lt 7) {
throw "Sign-FileMap.ps1 requires PowerShell 7 or newer."
}
if (-not (Test-Path -LiteralPath $FilesJsonPath)) {
throw "Manifest file not found: $FilesJsonPath"
}
if (-not (Test-Path -LiteralPath $PrivateKeyPath)) {
throw "Private key file not found: $PrivateKeyPath"
}
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
$OutputPath = "$FilesJsonPath.sig"
}
$resolvedManifestPath = (Resolve-Path -LiteralPath $FilesJsonPath).Path
$manifestBytes = [System.IO.File]::ReadAllBytes($resolvedManifestPath)
$privateKeyPem = Get-Content -LiteralPath $PrivateKeyPath -Raw
if ([string]::IsNullOrWhiteSpace($privateKeyPem)) {
throw "Private key PEM is empty: $PrivateKeyPath"
$toolProject = Join-Path $PSScriptRoot "..\PenguinLogisticsOnlineNetworkDistributionSystem\src\Plonds.Tool\Plonds.Tool.csproj"
if (-not (Test-Path -LiteralPath $toolProject)) {
throw "PLONDS tool project not found: $toolProject"
}
$rsa = [System.Security.Cryptography.RSA]::Create()
try {
$rsa.ImportFromPem($privateKeyPem)
$signatureBytes = $rsa.SignData(
$manifestBytes,
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1
)
& dotnet run --project $toolProject -- sign --manifest $FilesJsonPath --private-key $PrivateKeyPath --output $OutputPath
if ($LASTEXITCODE -ne 0) {
throw "PLONDS sign command failed."
}
finally {
$rsa.Dispose()
}
$signatureBase64 = [Convert]::ToBase64String($signatureBytes)
[System.IO.File]::WriteAllText($OutputPath, $signatureBase64, [System.Text.Encoding]::ASCII)
Write-Host "Signed manifest file."
Write-Host "Manifest: $FilesJsonPath"
Write-Host "Signature: $OutputPath"