mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
Introduce install checkpoint support and resume logic for updates, plus related locking and validation. Adds InstallCheckpoint model, AppJsonContext serialization, and UpdatePaths helpers for deployment lock, apply-in-progress lock and install-checkpoint path. UpdateEngineService gains checkpoint load/save/delete, incoming-state validation, resume logic for PLONDS and legacy updates, apply lock handling, and safer cleanup; ApplyPendingPlondsUpdateAsync and ApplyPendingUpdate flow updated accordingly. Add DeploymentLock contract and extend UpdateState with pause/resume/cancel helpers. Tests updated to cover stale/valid checkpoint resume and legacy/PLONDS flows. CI: enhance ddss-publish to detect release channel, validate S3 assets, prepare and atomically publish channel pointer; add ddss-rollback workflow to publish rollbacks; adjust plonds-build concurrency and release events.
279 lines
10 KiB
YAML
279 lines
10 KiB
YAML
name: PLONDS
|
|
|
|
concurrency:
|
|
group: plonds-${{ github.event_name }}-${{ github.event.release.tag_name || github.event.inputs.tag || github.run_id }}
|
|
cancel-in-progress: false
|
|
|
|
on:
|
|
release:
|
|
types:
|
|
- published
|
|
- prereleased
|
|
- edited
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Release tag'
|
|
required: true
|
|
type: string
|
|
baseline_tag:
|
|
description: 'Optional baseline tag'
|
|
required: false
|
|
type: string
|
|
channel:
|
|
description: 'Update channel'
|
|
required: false
|
|
type: choice
|
|
default: stable
|
|
options:
|
|
- stable
|
|
- preview
|
|
|
|
env:
|
|
DOTNET_VERSION: '10.0.x'
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
submodules: recursive
|
|
|
|
- name: Resolve release context
|
|
shell: bash
|
|
run: |
|
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
TAG="${{ github.event.release.tag_name }}"
|
|
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
|
|
CHANNEL="preview"
|
|
else
|
|
CHANNEL="stable"
|
|
fi
|
|
BASELINE_TAG=""
|
|
else
|
|
RAW_TAG="${{ github.event.inputs.tag }}"
|
|
if [[ "${RAW_TAG}" == v* ]]; then
|
|
TAG="${RAW_TAG}"
|
|
else
|
|
TAG="v${RAW_TAG}"
|
|
fi
|
|
CHANNEL="${{ github.event.inputs.channel }}"
|
|
BASELINE_TAG="${{ github.event.inputs.baseline_tag }}"
|
|
fi
|
|
|
|
echo "RELEASE_TAG=${TAG}" >> "$GITHUB_ENV"
|
|
echo "RELEASE_VERSION=${TAG#v}" >> "$GITHUB_ENV"
|
|
echo "RELEASE_CHANNEL=${CHANNEL}" >> "$GITHUB_ENV"
|
|
echo "BASELINE_TAG_INPUT=${BASELINE_TAG}" >> "$GITHUB_ENV"
|
|
PUBLIC_BASE="${{ vars.S3_PUBLIC_BASE_URL }}"
|
|
if [[ -z "$PUBLIC_BASE" ]]; then
|
|
PUBLIC_BASE="https://cn-nb1.rains3.com/lmdesktop/lanmountain/update"
|
|
fi
|
|
echo "S3_PUBLIC_BASE_URL=${PUBLIC_BASE%/}" >> "$GITHUB_ENV"
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
dotnet-quality: preview
|
|
|
|
- name: Prepare signing key
|
|
env:
|
|
UPDATE_PRIVATE_KEY_PEM: ${{ secrets.UPDATE_PRIVATE_KEY_PEM }}
|
|
PLONDS_SIGNING_KEY: ${{ secrets.PLONDS_SIGNING_KEY }}
|
|
PDC_SIGNING_KEY: ${{ secrets.PDC_SIGNING_KEY }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
KEY="${PLONDS_SIGNING_KEY:-}"
|
|
if [[ -z "$KEY" ]]; then KEY="${UPDATE_PRIVATE_KEY_PEM:-}"; fi
|
|
if [[ -z "$KEY" ]]; then KEY="${PDC_SIGNING_KEY:-}"; fi
|
|
if [[ -z "$KEY" ]]; then
|
|
echo "No signing key is configured."
|
|
exit 1
|
|
fi
|
|
printf '%s' "$KEY" > update-private-key.pem
|
|
echo "UPDATE_PRIVATE_KEY_PATH=$PWD/update-private-key.pem" >> "$GITHUB_ENV"
|
|
|
|
- name: Build PLONDS tool
|
|
run: dotnet build PenguinLogisticsOnlineNetworkDistributionSystem/src/Plonds.Tool/Plonds.Tool.csproj -c Release
|
|
|
|
- name: Resolve baseline plan
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$repo = '${{ github.repository }}'
|
|
$tag = $env:RELEASE_TAG
|
|
$baselineInput = $env:BASELINE_TAG_INPUT
|
|
$currentRelease = gh release view $tag --repo $repo --json tagName,isPrerelease,assets,publishedAt | ConvertFrom-Json
|
|
$allReleases = gh api "repos/$repo/releases?per_page=100" | ConvertFrom-Json
|
|
$platforms = @('windows-x64', 'windows-x86', 'linux-x64')
|
|
|
|
$entries = foreach ($platform in $platforms) {
|
|
$assetName = "files-$platform.zip"
|
|
$currentAsset = $currentRelease.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
|
|
if (-not $currentAsset) {
|
|
throw "Current release $tag does not contain required asset $assetName"
|
|
}
|
|
|
|
$baselineRelease = $null
|
|
if (-not [string]::IsNullOrWhiteSpace($baselineInput)) {
|
|
$normalizedBaseline = if ($baselineInput.StartsWith('v')) { $baselineInput } else { "v$baselineInput" }
|
|
$baselineRelease = $allReleases | Where-Object { $_.tag_name -eq $normalizedBaseline } | Select-Object -First 1
|
|
if (-not $baselineRelease) {
|
|
throw "Specified baseline tag not found: $normalizedBaseline"
|
|
}
|
|
}
|
|
else {
|
|
$baselineRelease = $allReleases |
|
|
Where-Object {
|
|
$_.tag_name -ne $tag -and
|
|
-not $_.draft -and
|
|
[bool]$_.prerelease -eq [bool]$currentRelease.isPrerelease -and
|
|
($_.assets | Where-Object { $_.name -eq $assetName } | Measure-Object).Count -gt 0
|
|
} |
|
|
Select-Object -First 1
|
|
}
|
|
|
|
[pscustomobject]@{
|
|
platform = $platform
|
|
assetName = $assetName
|
|
baselineTag = if ($baselineRelease) { $baselineRelease.tag_name } else { $null }
|
|
baselineVersion = if ($baselineRelease) { ($baselineRelease.tag_name -replace '^v', '') } else { $null }
|
|
isFullPayload = -not $baselineRelease
|
|
}
|
|
}
|
|
|
|
$plan = [pscustomobject]@{
|
|
tag = $tag
|
|
version = $env:RELEASE_VERSION
|
|
channel = $env:RELEASE_CHANNEL
|
|
platforms = $entries
|
|
}
|
|
|
|
$plan | ConvertTo-Json -Depth 8 | Set-Content plonds-plan.json -Encoding utf8
|
|
Get-Content plonds-plan.json
|
|
|
|
- name: Download payload zips
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$repo = '${{ github.repository }}'
|
|
$plan = Get-Content plonds-plan.json | ConvertFrom-Json
|
|
|
|
foreach ($entry in $plan.platforms) {
|
|
$currentDir = Join-Path $PWD "plonds-input/current/$($entry.platform)"
|
|
New-Item -ItemType Directory -Path $currentDir -Force | Out-Null
|
|
gh release download $plan.tag --repo $repo -p $entry.assetName -D $currentDir
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($entry.baselineTag)) {
|
|
$baselineDir = Join-Path $PWD "plonds-input/baseline/$($entry.platform)"
|
|
New-Item -ItemType Directory -Path $baselineDir -Force | Out-Null
|
|
gh release download $entry.baselineTag --repo $repo -p $entry.assetName -D $baselineDir
|
|
}
|
|
}
|
|
|
|
- name: Build delta assets
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$plan = Get-Content plonds-plan.json | ConvertFrom-Json
|
|
foreach ($entry in $plan.platforms) {
|
|
$currentZip = Join-Path $PWD "plonds-input/current/$($entry.platform)/$($entry.assetName)"
|
|
$args = @(
|
|
'run', '--project', 'PenguinLogisticsOnlineNetworkDistributionSystem/src/Plonds.Tool/Plonds.Tool.csproj', '--configuration', 'Release', '--',
|
|
'build-delta',
|
|
'--platform', $entry.platform,
|
|
'--current-version', $plan.version,
|
|
'--current-tag', $plan.tag,
|
|
'--current-zip', $currentZip,
|
|
'--output-dir', 'plonds-output',
|
|
'--private-key', $env:UPDATE_PRIVATE_KEY_PATH,
|
|
'--channel', $plan.channel,
|
|
'--static-output-dir', 'plonds-output/static',
|
|
'--update-base-url', $env:S3_PUBLIC_BASE_URL
|
|
)
|
|
|
|
if ([bool]$entry.isFullPayload) {
|
|
$args += @('--is-full-payload', 'true')
|
|
}
|
|
else {
|
|
$baselineZip = Join-Path $PWD "plonds-input/baseline/$($entry.platform)/$($entry.assetName)"
|
|
$args += @('--baseline-tag', $entry.baselineTag, '--baseline-version', $entry.baselineVersion, '--baseline-zip', $baselineZip)
|
|
}
|
|
|
|
dotnet @args
|
|
}
|
|
|
|
dotnet run --project PenguinLogisticsOnlineNetworkDistributionSystem/src/Plonds.Tool/Plonds.Tool.csproj --configuration Release -- `
|
|
build-index `
|
|
--release-tag $plan.tag `
|
|
--version $plan.version `
|
|
--channel $plan.channel `
|
|
--platform-summaries-dir plonds-output/platform-summaries `
|
|
--output-dir plonds-output `
|
|
--private-key $env:UPDATE_PRIVATE_KEY_PATH
|
|
|
|
foreach ($entry in $plan.platforms) {
|
|
$summary = Get-Content "plonds-output/platform-summaries/platform-summary-$($entry.platform).json" | ConvertFrom-Json
|
|
$required = @(
|
|
"plonds-output/static/meta/channels/$($plan.channel)/$($entry.platform)/latest.json",
|
|
"plonds-output/static/meta/distributions/$($summary.distributionId).json",
|
|
"plonds-output/static/manifests/$($summary.distributionId)/plonds-filemap.json",
|
|
"plonds-output/static/manifests/$($summary.distributionId)/plonds-filemap.json.sig"
|
|
)
|
|
|
|
foreach ($path in $required) {
|
|
if (-not (Test-Path $path)) {
|
|
throw "Missing PLONDS static output: $path"
|
|
}
|
|
}
|
|
}
|
|
|
|
$objects = Get-ChildItem -Path "plonds-output/static/repo/sha256" -File -Recurse -ErrorAction SilentlyContinue
|
|
if (-not $objects -or $objects.Count -eq 0) {
|
|
throw "PLONDS static object repository is empty."
|
|
}
|
|
|
|
Compress-Archive -Path "plonds-output/static/*" -DestinationPath "plonds-output/release-assets/plonds-static.zip" -Force
|
|
|
|
- name: Upload PLONDS assets to release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
gh release upload "$RELEASE_TAG" plonds-output/release-assets/* --clobber
|
|
|
|
- name: Persist run metadata
|
|
shell: bash
|
|
run: |
|
|
mkdir -p plonds-run-metadata
|
|
printf '%s' "$RELEASE_TAG" > plonds-run-metadata/tag.txt
|
|
|
|
- name: Upload run metadata artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: plonds-run-metadata
|
|
path: plonds-run-metadata/tag.txt
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
- name: Upload PLONDS static artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: plonds-static
|
|
path: plonds-output/static/**
|
|
if-no-files-found: error
|
|
retention-days: 7
|