Launcher (#4)

* 激进的更新

* 试试

* fix.可爱的我一直在修CI(

* fix.启动器一定要能够启动

* feat.尝试弄了AOT的启动器。

* fix.修CI,好像是因为Linux那边有个问题,反正修就对了。

* fix.ci难修,为什么liunx跑不起来呢?

* Update build.yml

* Update LanMountainDesktop.csproj

* changed.调整了启动逻辑,优化了更新页面。

* changed.优化了更新体验

* feat.依旧试增量更新这一块,看看velopack

* fix.我们试验性地修复了启动器无法正常启动的问题,原因可能是这个画面没有启动,就GUI没显示。然后还把编译问题修了一下。

* fix.继续修ci,ci怎么天天炸

* changed.velopack,试试rust

* fix.修ci,修融合桌面,修启动器

* fix.GitHub Action工作流怎么天天出问题

* feat.引入velopack,不好,是rust(至少内存很安全了。

* chore: migrate release pipeline to signed filemap and wire rainyun s3

* fix: make optional s3 upload step workflow-parse safe

* fix: make delta pack generation robust for empty diffs and linux paths

* chore: rotate launcher update public key for pdc signing

* fix: restore stable launcher update public key

* fix: sync launcher public key with update signing secret

* fix: normalize PEM line endings in signing key validation

* fix: rotate launcher public key to match ci signing secret

* fix: compare signing keys by SPKI instead of PEM text

* refactor update backend to host-managed PDC pipeline

* fix release workflow env key collisions

* relax publish-pdc precheck to require S3 only

* set GH_TOKEN for PDCC installer step

* ci: add local pdc mock fallback for release publish

* ci: fix pdc mock process log redirection

* ci: fallback pdcc signing key to update private key

* ci: ensure pdcc signing passphrase env is always set

* ci: create pdcc publish root before invoking client

* ci: set pdcc version variable from release version

* ci: decouple pdcc installer version from publish config version

* ci: package pdcc subchannels with generated filemap and changelog

* ci: make local pdc mock diff return empty for fast fallback

* ci: fix pdcc variable mapping and pdc signing prechecks

* Update App.axaml.cs

* ci: wire aws cli credentials for rainyun s3

* ci: pin pdcc client version separately from app version

* ci: harden local pdc mock transport handling

* ci: publish pdcc subchannels in one pass

* ci: add pdcc publish heartbeat and timeout

* ci: fix pdcc publish workdir bootstrap

* feat.Penguin Logistics Online Network Distribution System

* ci: fix plonds s3 probe and signing fallback

* ci: validate signing key and quiet missing baselines

* ci: relax aws checksum mode for rainyun s3

* ci: avoid multipart uploads to rainyun s3

* ci: handle empty plonds baselines safely

* ci.plonds

* Rebuild release pipeline around PLONDS and DDSS

* Fix Windows installer script path in release workflow
This commit is contained in:
lincube
2026-04-21 20:59:52 +08:00
committed by GitHub
parent 03e32ee6cb
commit 4cb52e56c7
186 changed files with 44656 additions and 1248 deletions

View File

@@ -1,4 +1,4 @@
name: Build
name: Build
on:
push:
@@ -10,6 +10,7 @@ on:
env:
DOTNET_VERSION: '10.0.x'
Solution_Name: LanMountainDesktop.slnx
DOTNET_gcServer: 1
jobs:
build-windows:
@@ -31,6 +32,7 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}
@@ -63,12 +65,22 @@ jobs:
sudo apt-get install -y \
libfontconfig1 libfreetype6 \
libx11-6 libxrandr2 libxinerama1 \
libxi6 libxcursor1 libxext6
libxi6 libxcursor1 libxext6 \
libxrender1 libxkbcommon-x11-0 \
clang zlib1g-dev
# Ubuntu 24.04+ moved several packages to t64 names.
sudo apt-get install -y libasound2t64 || sudo apt-get install -y libasound2
sudo apt-get install -y libportaudio2t64 || sudo apt-get install -y libportaudio2
# Prefer modern WebKit package, fallback for older images.
sudo apt-get install -y libwebkit2gtk-4.1-dev || sudo apt-get install -y libwebkit2gtk-4.0-dev
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}
@@ -95,10 +107,14 @@ jobs:
fetch-depth: 0
submodules: recursive
- name: Install dependencies
run: brew install portaudio
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}
@@ -129,6 +145,7 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Pack SDK and template packages
shell: pwsh

View File

@@ -1,4 +1,4 @@
name: Quality Check
name: Quality Check
on:
pull_request:
@@ -9,6 +9,7 @@ on:
env:
DOTNET_VERSION: '10.0.x'
Solution_Name: LanMountainDesktop.slnx
DOTNET_gcServer: 1
jobs:
analyze:
@@ -24,12 +25,13 @@ jobs:
with:
fetch-depth: 0
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}

166
.github/workflows/ddss-publish.yml vendored Normal file
View File

@@ -0,0 +1,166 @@
name: DDSS
on:
workflow_run:
workflows:
- PLONDS
types:
- completed
workflow_dispatch:
inputs:
tag:
description: 'Release tag'
required: true
type: string
env:
DOTNET_VERSION: '10.0.x'
jobs:
publish:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: Resolve release tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
RAW_TAG="${{ github.event.inputs.tag }}"
if [[ "$RAW_TAG" == v* ]]; then
TAG="$RAW_TAG"
else
TAG="v$RAW_TAG"
fi
else
gh run download "${{ github.event.workflow_run.id }}" -n plonds-run-metadata -D plonds-run-metadata
TAG="$(tr -d '\r\n' < plonds-run-metadata/tag.txt)"
fi
echo "RELEASE_TAG=${TAG}" >> "$GITHUB_ENV"
echo "S3_BASE_URL=${{ vars.S3_ENDPOINT }}/${{ vars.S3_BUCKET }}/lanmountain/update/releases/${TAG}/assets" >> "$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: Download release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
mkdir -p release-assets
gh release download "$RELEASE_TAG" -D release-assets
find release-assets -maxdepth 1 -type f | sort
- name: Upload release assets to Rainyun S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
AWS_DEFAULT_REGION: ${{ vars.S3_REGION }}
AWS_REGION: ${{ vars.S3_REGION }}
S3_ENDPOINT: ${{ vars.S3_ENDPOINT }}
S3_BUCKET: ${{ vars.S3_BUCKET }}
shell: bash
run: |
set -euo pipefail
aws --version
for file in release-assets/*; do
[[ -f "$file" ]] || continue
name="$(basename "$file")"
if [[ "$name" == "ddss.json" || "$name" == "ddss.json.sig" ]]; then
continue
fi
key="lanmountain/update/releases/${RELEASE_TAG}/assets/${name}"
sha256="$(sha256sum "$file" | awk '{print $1}')"
existing_sha="$(aws --endpoint-url "$S3_ENDPOINT" --region "$AWS_REGION" s3api head-object --bucket "$S3_BUCKET" --key "$key" --query 'Metadata.sha256' --output text 2>/dev/null || true)"
if [[ "$existing_sha" == "$sha256" ]]; then
echo "Skip existing asset: $name"
continue
fi
aws --endpoint-url "$S3_ENDPOINT" --region "$AWS_REGION" s3api put-object \
--bucket "$S3_BUCKET" \
--key "$key" \
--body "$file" \
--metadata "sha256=$sha256"
done
- name: Build DDSS manifest
shell: bash
run: |
set -euo pipefail
mkdir -p ddss-output
dotnet run --project PenguinLogisticsOnlineNetworkDistributionSystem/src/Plonds.Tool/Plonds.Tool.csproj --configuration Release -- \
build-ddss \
--release-tag "$RELEASE_TAG" \
--assets-dir release-assets \
--output-dir ddss-output \
--private-key "$UPDATE_PRIVATE_KEY_PATH" \
--repository "${{ github.repository }}" \
--s3-base-url "$S3_BASE_URL"
- name: Upload DDSS manifest to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
gh release upload "$RELEASE_TAG" ddss-output/ddss.json ddss-output/ddss.json.sig --clobber
- name: Upload DDSS manifest to Rainyun S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
AWS_DEFAULT_REGION: ${{ vars.S3_REGION }}
AWS_REGION: ${{ vars.S3_REGION }}
S3_ENDPOINT: ${{ vars.S3_ENDPOINT }}
S3_BUCKET: ${{ vars.S3_BUCKET }}
shell: bash
run: |
set -euo pipefail
for file in ddss-output/ddss.json ddss-output/ddss.json.sig; do
name="$(basename "$file")"
key="lanmountain/update/releases/${RELEASE_TAG}/assets/${name}"
sha256="$(sha256sum "$file" | awk '{print $1}')"
aws --endpoint-url "$S3_ENDPOINT" --region "$AWS_REGION" s3api put-object \
--bucket "$S3_BUCKET" \
--key "$key" \
--body "$file" \
--metadata "sha256=$sha256"
done

235
.github/workflows/plonds-build.yml vendored Normal file
View File

@@ -0,0 +1,235 @@
name: PLONDS
on:
release:
types:
- published
- prereleased
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"
- 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
)
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
- 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

View File

@@ -19,6 +19,7 @@ on:
env:
DOTNET_VERSION: '10.0.x'
Solution_Name: LanMountainDesktop.slnx
DOTNET_gcServer: 1
jobs:
prepare:
@@ -29,14 +30,22 @@ jobs:
informational_version: ${{ steps.version.outputs.informational_version }}
tag: ${{ steps.version.outputs.tag }}
checkout_ref: ${{ steps.version.outputs.checkout_ref }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
release_channel: ${{ steps.version.outputs.release_channel }}
steps:
- name: Checkout repository metadata
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get release info
id: version
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
TAG="${GITHUB_REF#refs/tags/}"
CHECKOUT_REF="${GITHUB_REF}"
IS_PRERELEASE="false"
else
RAW_TAG="${{ github.event.inputs.tag }}"
if [[ "${RAW_TAG}" == refs/tags/* ]]; then
@@ -46,19 +55,40 @@ jobs:
else
TAG="v${RAW_TAG}"
fi
CHECKOUT_REF="${GITHUB_SHA}"
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
CHECKOUT_REF="refs/tags/${TAG}"
else
CHECKOUT_REF="${GITHUB_SHA}"
fi
if [[ "${{ github.event.inputs.is_prerelease }}" == "true" ]]; then
IS_PRERELEASE="true"
else
IS_PRERELEASE="false"
fi
fi
VERSION="${TAG#v}"
IFS='.' read -r -a VERSION_PARTS <<< "${VERSION}"
while [ "${#VERSION_PARTS[@]}" -lt 4 ]; do
VERSION_PARTS+=("0")
done
ASSEMBLY_VERSION="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}.${VERSION_PARTS[3]}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "assembly_version=${ASSEMBLY_VERSION}" >> $GITHUB_OUTPUT
echo "informational_version=${VERSION}" >> $GITHUB_OUTPUT
echo "checkout_ref=${CHECKOUT_REF}" >> $GITHUB_OUTPUT
if [[ "${IS_PRERELEASE}" == "true" ]]; then
RELEASE_CHANNEL="preview"
else
RELEASE_CHANNEL="stable"
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "assembly_version=${ASSEMBLY_VERSION}" >> "$GITHUB_OUTPUT"
echo "informational_version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "checkout_ref=${CHECKOUT_REF}" >> "$GITHUB_OUTPUT"
echo "is_prerelease=${IS_PRERELEASE}" >> "$GITHUB_OUTPUT"
echo "release_channel=${RELEASE_CHANNEL}" >> "$GITHUB_OUTPUT"
build-windows:
needs: prepare
@@ -67,7 +97,6 @@ jobs:
fail-fast: false
matrix:
include:
# 完整版(自包含 .NET 运行时)
- arch: x64
self_contained: true
suffix: ''
@@ -75,7 +104,7 @@ jobs:
self_contained: true
suffix: ''
name: Build_Windows_${{ matrix.arch }}${{ matrix.suffix }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -88,6 +117,7 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}
@@ -100,7 +130,35 @@ jobs:
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }}
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Publish
- name: Publish Launcher (AOT)
run: |
$version = "${{ needs.prepare.outputs.version }}"
$arch = "${{ matrix.arch }}"
$launcherPublishDir = "publish/launcher-win-$arch"
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj `
-c Release `
-o ./$launcherPublishDir `
--self-contained `
-r win-$arch `
-p:PublishAot=true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-p:EnableCompressionInSingleFile=true `
-p:DebugType=none `
-p:DebugSymbols=false `
-p:Version=${{ needs.prepare.outputs.version }} `
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} `
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} `
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
if ($LASTEXITCODE -ne 0) {
Write-Error "Launcher AOT publish failed"
exit 1
}
shell: pwsh
- name: Publish Main App
run: |
$selfContained = "${{ matrix.self_contained }}" -eq "true"
$publishDir = if ($selfContained) { "publish/windows-${{ matrix.arch }}" } else { "publish/windows-${{ matrix.arch }}-lite" }
@@ -135,79 +193,69 @@ jobs:
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} `
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
}
Write-Host "Published to: $publishDir"
Write-Host "Self-contained: $selfContained"
shell: pwsh
- name: Install Inno Setup
run: choco install innosetup -y --no-progress
- name: Restructure for Launcher
run: |
$version = "${{ needs.prepare.outputs.version }}"
$arch = "${{ matrix.arch }}"
$selfContained = "${{ matrix.self_contained }}" -eq "true"
$publishDir = if ($selfContained) { "publish/windows-$arch" } else { "publish/windows-$arch-lite" }
$launcherPublishDir = "publish/launcher-win-$arch"
$appDir = "app-$version"
$newStructure = "publish-launcher/windows-$arch"
New-Item -ItemType Directory -Path $newStructure -Force | Out-Null
$appPath = Join-Path $newStructure $appDir
Move-Item -Path $publishDir -Destination $appPath -Force
if (Test-Path $launcherPublishDir) {
Copy-Item -Path "$launcherPublishDir\*" -Destination $newStructure -Recurse -Force
}
New-Item -ItemType File -Path (Join-Path $appPath ".current") -Force | Out-Null
Remove-Item -Path $publishDir -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path $launcherPublishDir -Recurse -Force -ErrorAction SilentlyContinue
Move-Item -Path $newStructure -Destination $publishDir -Force
shell: pwsh
- name: Install Inno Setup and 7z
run: |
choco install innosetup -y --no-progress
choco install 7zip -y --no-progress
shell: pwsh
- name: Build Installer
run: |
$version = "${{ needs.prepare.outputs.version }}"
$arch = "${{ matrix.arch }}"
$selfContained = "${{ matrix.self_contained }}" -eq "true"
$suffix = "${{ matrix.suffix }}"
$publishDir = if ($selfContained) { "publish\windows-$arch" } else { "publish\windows-$arch-lite" }
$installerScript = "LanMountainDesktop\installer\LanMountainDesktop.iss"
$selfContained = "${{ matrix.self_contained }}" -eq "true"
$publishDir = if ($selfContained) { "publish/windows-$arch" } else { "publish/windows-$arch-lite" }
$outputDir = "build-installer"
# Verify source directory exists
if (-not (Test-Path -Path $publishDir)) {
Write-Error "Publish directory not found: $publishDir"
Get-ChildItem -Path "publish" -Directory -ErrorAction SilentlyContinue | Select-Object Name
exit 1
}
# Create output directory
$installerScript = "LanMountainDesktop/installer/LanMountainDesktop.iss"
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
# Verify installer script exists
if (-not (Test-Path -Path $installerScript)) {
Write-Error "Installer script not found: $installerScript"
exit 1
}
# Find Inno Setup compiler (choco may install a shim in PATH)
$isccPath = $null
$isccCommand = Get-Command ISCC.exe -ErrorAction SilentlyContinue
if ($isccCommand) {
$isccPath = $isccCommand.Source
}
$candidatePaths = @(
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
"C:\Program Files\Inno Setup 6\ISCC.exe",
"$env:ChocolateyInstall\bin\ISCC.exe",
(Get-Command iscc.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -ErrorAction SilentlyContinue),
"$env:ProgramFiles(x86)\Inno Setup 6\ISCC.exe",
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
"$env:ChocolateyInstall\lib\innosetup\tools\ISCC.exe"
)
) | Where-Object { $_ -and (Test-Path $_) }
$isccPath = $candidatePaths | Select-Object -First 1
if (-not $isccPath) {
foreach ($candidate in $candidatePaths) {
if ($candidate -and (Test-Path -Path $candidate)) {
$isccPath = $candidate
break
}
}
}
if (-not $isccPath) {
Write-Host "ISCC.exe was not found in PATH or known locations."
Write-Host "Checked locations:"
$candidatePaths | ForEach-Object { Write-Host " - $_" }
Write-Host "Chocolatey bin listing (if exists):"
Get-ChildItem "$env:ChocolateyInstall\bin" -Filter "*iscc*" -ErrorAction SilentlyContinue | Select-Object FullName
Write-Error "Inno Setup compiler not found."
exit 1
}
Write-Host "Found Inno Setup at: $isccPath"
# Build installer with iscc.exe
Write-Host "Building installer for Windows $arch with version $version..."
if (-not (Test-Path $installerScript)) {
Write-Error "Installer script not found: $(Join-Path $PWD $installerScript)"
exit 1
}
$publishDir = (Resolve-Path $publishDir).Path
$outputDir = (Resolve-Path $outputDir).Path
$installerScript = (Resolve-Path $installerScript).Path
@@ -221,32 +269,65 @@ jobs:
"/DIsSelfContained=$selfContained",
$installerScript
)
Write-Host "Compile command: `"$isccPath`" $($compileArgs -join ' ')"
# Execute the compiler
& $isccPath @compileArgs
if ($LASTEXITCODE -ne 0) {
Write-Error "Inno Setup compiler exited with code $LASTEXITCODE"
exit 1
}
# Check if build was successful
$installerFile = Get-ChildItem -Path $outputDir -Filter "*.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $installerFile) {
Write-Error "Failed to create installer"
exit 1
}
Write-Host "✅ Successfully created: $($installerFile.Name)"
Write-Host "Installer size: $([Math]::Round($installerFile.Length / 1MB, 2)) MB"
shell: pwsh
- name: Upload Installer
- name: Package Payload Zip
run: |
$version = "${{ needs.prepare.outputs.version }}"
$arch = "${{ matrix.arch }}"
$payloadRoot = Join-Path (Join-Path $PWD "publish/windows-$arch") "app-$version"
if (-not (Test-Path $payloadRoot)) {
Write-Error "Payload root not found: $payloadRoot"
exit 1
}
$stageDir = Join-Path $PWD "payload-stage/windows-$arch"
$releaseDir = Join-Path $PWD "release-assets"
Remove-Item -Path $stageDir -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $stageDir -Force | Out-Null
New-Item -ItemType Directory -Path $releaseDir -Force | Out-Null
Get-ChildItem -Path $payloadRoot -Recurse -File | ForEach-Object {
$relative = [System.IO.Path]::GetRelativePath($payloadRoot, $_.FullName).Replace('\', '/')
if ($relative -eq '.current' -or $relative -eq '.partial' -or $relative -eq '.destroy' -or $relative.StartsWith('.current/') -or $relative.StartsWith('.partial/') -or $relative.StartsWith('.destroy/')) {
return
}
$destination = Join-Path $stageDir ($relative -replace '/', [System.IO.Path]::DirectorySeparatorChar)
$destinationDir = Split-Path -Path $destination -Parent
if (-not [string]::IsNullOrWhiteSpace($destinationDir)) {
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
}
Copy-Item -Path $_.FullName -Destination $destination -Force
}
$payloadZip = Join-Path $releaseDir "files-windows-$arch.zip"
if (Test-Path $payloadZip) {
Remove-Item $payloadZip -Force
}
Compress-Archive -Path (Join-Path $stageDir '*') -DestinationPath $payloadZip -Force
shell: pwsh
- name: Upload Release Assets
uses: actions/upload-artifact@v4
with:
name: release-windows-${{ matrix.arch }}${{ matrix.suffix }}
path: build-installer/*.exe
name: release-windows-${{ matrix.arch }}
path: |
release-assets/files-windows-${{ matrix.arch }}.zip
build-installer/*.exe
if-no-files-found: error
retention-days: 30
@@ -254,7 +335,7 @@ jobs:
needs: prepare
runs-on: ubuntu-latest
name: Build_Linux
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -270,12 +351,18 @@ jobs:
libfontconfig1 libfreetype6 \
libx11-6 libxrandr2 libxinerama1 \
libxi6 libxcursor1 libxext6 \
libxrender1 libxkbcommon-x11-0
libxrender1 libxkbcommon-x11-0 \
clang zlib1g-dev zip rsync
sudo apt-get install -y libasound2t64 || sudo apt-get install -y libasound2
sudo apt-get install -y libportaudio2t64 || sudo apt-get install -y libportaudio2
sudo apt-get install -y libwebkit2gtk-4.1-dev || sudo apt-get install -y libwebkit2gtk-4.0-dev
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}
@@ -288,11 +375,29 @@ jobs:
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }}
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Publish
- name: Publish Launcher (AOT)
run: |
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
-c Release \
-o ./publish/launcher-linux-x64 \
--self-contained \
-r linux-x64 \
-p:PublishAot=true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \
-p:DebugType=none \
-p:DebugSymbols=false \
-p:Version=${{ needs.prepare.outputs.version }} \
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Publish Main App
run: |
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj \
-c Release \
-o ./publish/linux-x64 \
-o ./publish/linux-x64-app \
--self-contained \
-r linux-x64 \
-p:PublishSingleFile=false \
@@ -306,6 +411,24 @@ jobs:
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Restructure for Launcher
run: |
version="${{ needs.prepare.outputs.version }}"
publishDir="publish/linux-x64"
appDir="app-$version"
launcherDir="publish/launcher-linux-x64"
mkdir -p "$publishDir"
mv "publish/linux-x64-app" "$publishDir/$appDir"
if [ -d "$launcherDir" ]; then
cp -r "$launcherDir"/* "$publishDir/"
chmod +x "$publishDir/LanMountainDesktop.Launcher" 2>/dev/null || true
fi
touch "$publishDir/$appDir/.current"
rm -rf "$launcherDir"
- name: Package as DEB
run: |
version="${{ needs.prepare.outputs.version }}"
@@ -315,41 +438,17 @@ jobs:
arch="amd64"
desktop_template="LanMountainDesktop/packaging/linux/LanMountainDesktop.desktop"
icon_source="LanMountainDesktop/packaging/linux/lanmountaindesktop.png"
# Verify source directory exists
if [ ! -d "$source" ]; then
echo "Error: Source directory not found: $source"
ls -la publish/ || echo "publish directory not found"
exit 1
fi
# Create DEB package structure
mkdir -p "build-deb/DEBIAN"
mkdir -p "build-deb/usr/local/bin"
mkdir -p "build-deb/usr/share/applications"
mkdir -p "build-deb/usr/share/pixmaps"
mkdir -p "build-deb/usr/share/icons/hicolor/256x256/apps"
# Copy application files
cp -r "$source"/* "build-deb/usr/local/bin/"
# Verify copy was successful
item_count=$(find build-deb/usr/local/bin -type f 2>/dev/null | wc -l)
echo "DEB package contains $item_count files"
if [ "$item_count" -eq 0 ]; then
echo "Error: DEB package is empty after copy"
exit 1
fi
if [ ! -f "$desktop_template" ] || [ ! -f "$icon_source" ]; then
echo "Error: Linux desktop resources are missing"
ls -la "LanMountainDesktop/packaging/linux" || true
exit 1
fi
cp -r "$source"/* "build-deb/usr/local/bin/"
sed \
-e "s|@@EXEC@@|/usr/local/bin/LanMountainDesktop|g" \
-e "s|@@EXEC@@|/usr/local/bin/LanMountainDesktop.Launcher|g" \
-e "s|@@ICON@@|lanmountaindesktop|g" \
"$desktop_template" > "build-deb/usr/share/applications/LanMountainDesktop.desktop"
@@ -366,49 +465,69 @@ jobs:
printf '%s\n' ' gtk-update-icon-cache /usr/share/icons/hicolor >/dev/null 2>&1 || true'
printf '%s\n' 'fi'
} > "build-deb/DEBIAN/postinst"
# Create control file (NOTE: No leading spaces in control file)
{
printf '%s\n' "Package: $package_name"
printf '%s\n' "Version: $package_version"
printf '%s\n' "Architecture: $arch"
printf '%s\n' "Maintainer: LanMountain Team <dev@example.com>"
printf '%s\n' "Description: LanMountain Desktop Application"
printf '%s\n' " A desktop application for LanMountain."
printf '%s\n' 'Maintainer: LanMountain Team <dev@example.com>'
printf '%s\n' 'Description: LanMountain Desktop Application'
printf '%s\n' ' A desktop application for LanMountain.'
} > "build-deb/DEBIAN/control"
# Set proper permissions
chmod 755 "build-deb/usr/local/bin/LanMountainDesktop" || chmod 755 "build-deb/usr/local/bin"/*
chmod 755 "build-deb/usr/local/bin/LanMountainDesktop.Launcher" 2>/dev/null || chmod 755 "build-deb/usr/local/bin"/*
chmod 644 "build-deb/usr/share/applications/LanMountainDesktop.desktop"
chmod 644 "build-deb/usr/share/pixmaps/lanmountaindesktop.png"
chmod 644 "build-deb/usr/share/icons/hicolor/256x256/apps/lanmountaindesktop.png"
chmod 755 "build-deb/DEBIAN/postinst"
# Create DEB file
if dpkg-deb --build "build-deb" "${package_name}_${package_version}_${arch}.deb"; then
echo "Successfully created: ${package_name}_${package_version}_${arch}.deb"
ls -lh "${package_name}_${package_version}_${arch}.deb"
else
echo "Error: Failed to build DEB package"
dpkg-deb --build "build-deb" "${package_name}_${package_version}_${arch}.deb"
- name: Package Payload Zip
run: |
version="${{ needs.prepare.outputs.version }}"
payload_root="publish/linux-x64/app-$version"
release_dir="$PWD/release-assets"
stage_dir="$PWD/payload-stage/linux-x64"
if [ ! -d "$payload_root" ]; then
echo "Payload root not found: $payload_root"
exit 1
fi
- name: Upload
rm -rf "$stage_dir"
mkdir -p "$stage_dir" "$release_dir"
rsync -a \
--exclude '.current' \
--exclude '.partial' \
--exclude '.destroy' \
"$payload_root/" "$stage_dir/"
(
cd "$stage_dir"
zip -qr "$release_dir/files-linux-x64.zip" .
)
- name: Upload Release Assets
uses: actions/upload-artifact@v4
with:
name: release-linux
path: "*.deb"
name: release-linux-x64
path: |
release-assets/files-linux-x64.zip
*.deb
if-no-files-found: error
retention-days: 30
build-macos:
needs: prepare
runs-on: macos-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
arch: [x64, arm64]
name: Build_macOS_${{ matrix.arch }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -417,10 +536,14 @@ jobs:
submodules: recursive
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Install dependencies
run: brew install portaudio
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
dotnet-quality: 'preview'
- name: Restore
run: dotnet restore ${{ env.Solution_Name }}
@@ -433,11 +556,29 @@ jobs:
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }}
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Publish
- name: Publish Launcher (AOT)
run: |
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
-c Release \
-o ./publish/launcher-macos-${{ matrix.arch }} \
--self-contained \
-r osx-${{ matrix.arch }} \
-p:PublishAot=true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \
-p:DebugType=none \
-p:DebugSymbols=false \
-p:Version=${{ needs.prepare.outputs.version }} \
-p:AssemblyVersion=${{ needs.prepare.outputs.assembly_version }} \
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Publish Main App
run: |
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj \
-c Release \
-o ./publish/macos-${{ matrix.arch }} \
-o ./publish/macos-${{ matrix.arch }}-app \
--self-contained \
-r osx-${{ matrix.arch }} \
-p:PublishSingleFile=false \
@@ -451,45 +592,50 @@ jobs:
-p:FileVersion=${{ needs.prepare.outputs.assembly_version }} \
-p:InformationalVersion=${{ needs.prepare.outputs.informational_version }}
- name: Package as DMG
- name: Package Payload Zip
run: |
release_dir="$PWD/release-assets"
stage_dir="$PWD/payload-stage/macos-${{ matrix.arch }}"
payload_root="publish/macos-${{ matrix.arch }}-app"
rm -rf "$stage_dir"
mkdir -p "$stage_dir" "$release_dir"
rsync -a "$payload_root/" "$stage_dir/"
(
cd "$stage_dir"
zip -qr "$release_dir/files-macos-${{ matrix.arch }}.zip" .
)
- name: Restructure and Package as DMG
continue-on-error: true
run: |
version="${{ needs.prepare.outputs.version }}"
arch="${{ matrix.arch }}"
source="publish/macos-$arch"
app_name="LanMountainDesktop"
package_name="${app_name}-${version}-macos-${arch}"
# Verify source directory exists
if [ ! -d "$source" ]; then
echo "Error: Source directory not found: $source"
ls -la publish/ || echo "publish directory not found"
exit 1
fi
# Create app bundle structure
launcherDir="publish/launcher-macos-$arch"
appSourceDir="publish/macos-$arch-app"
mkdir -p "${app_name}.app/Contents/MacOS"
mkdir -p "${app_name}.app/Contents/Resources"
# Copy application files
cp -r "$source"/* "${app_name}.app/Contents/MacOS/"
# Verify copy was successful
item_count=$(find "${app_name}.app/Contents/MacOS" -type f | wc -l)
echo "App bundle contains $item_count files"
if [ "$item_count" -eq 0 ]; then
echo "Error: App bundle is empty after copy"
exit 1
appDir="app-$version"
mkdir -p "${app_name}.app/Contents/MacOS/$appDir"
cp -r "$appSourceDir"/* "${app_name}.app/Contents/MacOS/$appDir/"
if [ -d "$launcherDir" ]; then
cp -r "$launcherDir"/* "${app_name}.app/Contents/MacOS/"
chmod +x "${app_name}.app/Contents/MacOS/LanMountainDesktop.Launcher" 2>/dev/null || true
fi
# Create Info.plist
touch "${app_name}.app/Contents/MacOS/$appDir/.current"
mkdir -p "${app_name}.app/Contents/Resources"
{
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>'
printf '%s\n' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
printf '%s\n' '<plist version="1.0">'
printf '%s\n' '<dict>'
printf '%s\n' ' <key>CFBundleExecutable</key>'
printf '%s\n' ' <string>LanMountainDesktop</string>'
printf '%s\n' ' <string>LanMountainDesktop.Launcher</string>'
printf '%s\n' ' <key>CFBundleName</key>'
printf '%s\n' ' <string>LanMountain Desktop</string>'
printf '%s\n' ' <key>CFBundleVersion</key>'
@@ -503,96 +649,76 @@ jobs:
printf '%s\n' '</dict>'
printf '%s\n' '</plist>'
} > "${app_name}.app/Contents/Info.plist"
# Create DMG
mkdir -p dmg-temp
cp -r "${app_name}.app" dmg-temp/
if hdiutil create -volname "${app_name}" -srcfolder dmg-temp -ov -format UDZO "${package_name}.dmg" 2>&1; then
echo "Successfully created: ${package_name}.dmg"
ls -lh "${package_name}.dmg"
else
echo "Error: Failed to create DMG"
exit 1
fi
# Cleanup
rm -rf dmg-temp "${app_name}.app"
hdiutil create -volname "${app_name}" -srcfolder dmg-temp -ov -format UDZO "${package_name}.dmg"
- name: Upload
- name: Upload Release Assets
if: always()
uses: actions/upload-artifact@v4
with:
name: release-macos-${{ matrix.arch }}
path: "*.dmg"
if-no-files-found: error
path: |
release-assets/files-macos-${{ matrix.arch }}.zip
*.dmg
if-no-files-found: ignore
retention-days: 30
github-release:
needs: [ prepare, build-windows, build-linux, build-macos ]
needs: [prepare, build-windows, build-linux]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download artifacts
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
path: release-files
pattern: release-*
merge-multiple: true
- name: List artifacts structure
- name: Validate release files
run: |
echo "🔍 Artifact directory structure:"
find artifacts -type f -o -type d | sort
echo ""
echo "📊 Files found:"
find artifacts -type f -exec ls -lh {} \;
echo ""
echo "📁 Full tree:"
tree artifacts || find artifacts -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'
echo "Release files:"
find release-files -maxdepth 1 -type f -exec ls -lh {} \;
- name: Flatten artifacts for release
run: |
echo "📦 Organizing artifacts..."
mkdir -p release-files
find artifacts -type f \( -name "*.exe" -o -name "*.deb" -o -name "*.dmg" \) -exec cp -v {} release-files/ \;
echo ""
echo "✅ Files ready for release:"
ls -lh release-files/ || echo "⚠️ No files found in release-files"
echo ""
echo "📋 Total files:"
file_count=$(find release-files -type f | wc -l)
echo "$file_count"
if [ "$file_count" -eq 0 ]; then
echo "Error: No installer/package files found for release"
if [ ! -f release-files/files-windows-x64.zip ] || [ ! -f release-files/files-windows-x86.zip ] || [ ! -f release-files/files-linux-x64.zip ]; then
echo "Required payload zips are missing."
exit 1
fi
- name: Create Release
file_count=$(find release-files -maxdepth 1 -type f | wc -l)
if [ "$file_count" -eq 0 ]; then
echo "No release files were produced."
exit 1
fi
- name: Create or Update Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.prepare.outputs.tag }}
name: ${{ needs.prepare.outputs.tag }}
commit: ${{ github.sha }}
allowUpdates: true
draft: false
prerelease: ${{ github.event.inputs.is_prerelease == 'true' }}
artifacts: "release-files/**"
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
artifacts: 'release-files/**'
body: |
## Release ${{ needs.prepare.outputs.version }}
### Windows
- **LanMountainDesktop-Setup-{version}-x64.exe** - 64-bit installer (包含 .NET 运行时)
- **LanMountainDesktop-Setup-{version}-x86.exe** - 32-bit installer (包含 .NET 运行时)
### Installers
- `LanMountainDesktop-Setup-${{ needs.prepare.outputs.version }}-x64.exe`
- `LanMountainDesktop-Setup-${{ needs.prepare.outputs.version }}-x86.exe`
- `LanMountainDesktop_${{ needs.prepare.outputs.version }}_amd64.deb`
Installation: Double-click the .exe file and follow the wizard.
### Linux
- **LanMountainDesktop-{version}-linux-x64.deb** - Debian package (x64)
### Payload Archives
- `files-windows-x64.zip`
- `files-windows-x86.zip`
- `files-linux-x64.zip`
### macOS
- **LanMountainDesktop-{version}-macos-x64.dmg** - Intel processor
- **LanMountainDesktop-{version}-macos-arm64.dmg** - Apple Silicon (M1/M2/M3)
- macOS assets are best-effort and will not block the release.
See commits for changes.
Release keeps only the stable installer and payload outputs. PLONDS delta assets and external mirror metadata are generated by follow-up workflows.
token: ${{ secrets.GITHUB_TOKEN }}