feat(windows): MSIX packaging + publish workflow for the WinUI client
apple / swift (push) Successful in 54s
ci / rust (push) Failing after 55s
windows-msix / package (push) Successful in 1m2s
ci / web (push) Successful in 31s
windows / build (push) Successful in 55s
ci / docs-site (push) Successful in 31s
android / android (push) Successful in 2m6s
deb / build-publish (push) Successful in 2m24s
decky / build-publish (push) Successful in 11s
ci / bench (push) Successful in 4m21s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m39s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m32s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m49s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 1m21s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 3m20s
docker / deploy-docs (push) Successful in 22s
apple / swift (push) Successful in 54s
ci / rust (push) Failing after 55s
windows-msix / package (push) Successful in 1m2s
ci / web (push) Successful in 31s
windows / build (push) Successful in 55s
ci / docs-site (push) Successful in 31s
android / android (push) Successful in 2m6s
deb / build-publish (push) Successful in 2m24s
decky / build-publish (push) Successful in 11s
ci / bench (push) Successful in 4m21s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m39s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m32s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m49s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 1m21s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 3m20s
docker / deploy-docs (push) Successful in 22s
Package the Windows client as a signed MSIX (Start tile, clean install/uninstall) and publish it to
Gitea's generic registry, mirroring the host's .deb/.rpm and the Mac's DMG. Validated end-to-end on
the build VM: cargo build --release -> makeappx pack (16 payload files, 58 MB) -> signtool ->
Add-AppxPackage deploy -> framework-dependency resolution all green.
- packaging/AppxManifest.xml: full-trust Win32 app (Windows.FullTrustApplication + runFullTrust),
templated {VERSION}/{PUBLISHER}. windows-reactor packages cleanly despite being built "unpackaged"
because it calls MddBootstrapInitialize2 with OnPackageIdentity_NOOP — under MSIX identity the
bootstrapper no-ops and the App SDK resolves from the manifest's PackageDependency on
Microsoft.WindowsAppRuntime.2 (reactor pins MAJORMINOR 0x20000 = 2.0).
- packaging/pack-msix.ps1: assemble layout (exe + reactor/SDL3 auto-staged DLLs + resources.pri +
FFmpeg DLLs + tile assets), makeappx, signtool. Cert precedence: MSIX_CERT_PFX_B64 secret, else an
ephemeral self-signed cert whose .cer is published alongside (swap in a real cert later, no
manifest change).
- assets: tile/store logos rasterized from packaging/flatpak/io.unom.Punktfunk.svg.
- .gitea/workflows/windows-msix.yml: runs on the Windows runner on main pushes + win-v* tags +
dispatch. MSIX version is 4-part numeric — win-vX.Y.Z -> X.Y.Z.0, else 0.2.<run>.0. shell: pwsh +
CARGO_TARGET_DIR=C:\t like windows.yml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
# Build the punktfunk Windows client as a signed MSIX and publish it to Gitea's generic package
|
||||||
|
# registry, so Windows boxes can download + install a real package (Start tile, clean
|
||||||
|
# install/uninstall) instead of a loose exe. Runs on the self-hosted Windows runner (host mode;
|
||||||
|
# scripts/ci/setup-windows-runner.ps1) — the MSVC/WinUI/FFmpeg toolchain + the Windows SDK's
|
||||||
|
# makeappx/signtool are baked into the runner's daemon env, same as windows.yml.
|
||||||
|
#
|
||||||
|
# Registry (public, unom org): https://git.unom.io/unom/-/packages (generic group)
|
||||||
|
# Packaging internals: crates/punktfunk-client-windows/packaging/README.md. BOM/MAX_PATH runner
|
||||||
|
# gotchas baked into the daemon env + windows.yml: see that workflow.
|
||||||
|
#
|
||||||
|
# Versioning — MSIX requires a strictly 4-part numeric version (no ~/- suffixes), so:
|
||||||
|
# win-vX.Y.Z tag -> X.Y.Z.0 (a real Windows-client release; `win-v*` is its own tag namespace,
|
||||||
|
# kept off the host's `host-v*` and the Apple `v*` to avoid the
|
||||||
|
# version-shadow class of bug — see deb.yml).
|
||||||
|
# main push / dispatch -> 0.2.<run_number>.0 (rolling; climbs monotonically by run number).
|
||||||
|
#
|
||||||
|
# Signing (packaging/pack-msix.ps1): if the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD Actions secrets
|
||||||
|
# are set (a real or shared code-signing .pfx whose subject DN == Publisher), the package is signed
|
||||||
|
# with them. Otherwise an ephemeral self-signed cert is generated and its public .cer is published
|
||||||
|
# next to the .msix (users import it to Trusted People before install). Drop in a real cert later
|
||||||
|
# with no workflow change — just add the secrets (+ pass -Publisher if its subject differs).
|
||||||
|
name: windows-msix
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'crates/punktfunk-client-windows/**'
|
||||||
|
- 'crates/punktfunk-core/**'
|
||||||
|
- 'Cargo.lock'
|
||||||
|
- 'Cargo.toml'
|
||||||
|
- '.gitea/workflows/windows-msix.yml'
|
||||||
|
tags: ['win-v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: git.unom.io
|
||||||
|
OWNER: unom
|
||||||
|
PKG: punktfunk-client-windows
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
runs-on: windows-amd64
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure + version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# windows-reactor's build.rs unwraps CARGO_WORKSPACE_DIR; CARGO_TARGET_DIR=C:\t dodges the
|
||||||
|
# MAX_PATH wall in the CMake-from-source crates (see windows.yml). Both via GITHUB_ENV.
|
||||||
|
"CARGO_WORKSPACE_DIR=$env:GITHUB_WORKSPACE" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
"CARGO_TARGET_DIR=C:\t" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
$parts = if ($env:GITHUB_REF -like 'refs/tags/win-v*') {
|
||||||
|
($env:GITHUB_REF_NAME -replace '^win-v', '').Split('.')
|
||||||
|
} else {
|
||||||
|
@('0', '2', $env:GITHUB_RUN_NUMBER)
|
||||||
|
}
|
||||||
|
while ($parts.Count -lt 4) { $parts += '0' }
|
||||||
|
$v = ($parts[0..3] -join '.')
|
||||||
|
"MSIX_VERSION=$v" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
Write-Output "MSIX version $v"
|
||||||
|
|
||||||
|
- name: Build (release)
|
||||||
|
shell: pwsh
|
||||||
|
run: cargo build --release -p punktfunk-client-windows
|
||||||
|
|
||||||
|
- name: Pack + sign MSIX
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
MSIX_CERT_PFX_B64: ${{ secrets.MSIX_CERT_PFX_B64 }}
|
||||||
|
MSIX_CERT_PASSWORD: ${{ secrets.MSIX_CERT_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
& crates/punktfunk-client-windows/packaging/pack-msix.ps1 `
|
||||||
|
-Version $env:MSIX_VERSION -TargetDir C:\t\release -OutDir C:\t\msix
|
||||||
|
|
||||||
|
- name: Publish to Gitea generic registry
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$files = @($env:MSIX_PATH, $env:MSIX_CER_PATH) | Where-Object { $_ -and (Test-Path $_) }
|
||||||
|
if (-not $files) { throw "pack produced no artifacts to publish" }
|
||||||
|
foreach ($f in $files) {
|
||||||
|
$name = Split-Path $f -Leaf
|
||||||
|
$url = "https://$($env:REGISTRY)/api/packages/$($env:OWNER)/generic/$($env:PKG)/$($env:MSIX_VERSION)/$name"
|
||||||
|
curl.exe -fsS --user "enricobuehler:$($env:REGISTRY_TOKEN)" --upload-file "$f" "$url"
|
||||||
|
Write-Output "published $name -> $url"
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
MSIX package manifest for the punktfunk Windows client (WinUI 3 via windows-reactor).
|
||||||
|
|
||||||
|
This is a TEMPLATE: packaging/pack-msix.ps1 substitutes {VERSION} (4-part numeric, e.g.
|
||||||
|
0.2.137.0) and {PUBLISHER} (must EXACTLY equal the signing cert's subject DN — default
|
||||||
|
`CN=unom` for the self-signed CI cert; a real code-signing cert just passes its own subject).
|
||||||
|
|
||||||
|
Why this packages cleanly even though the app was built "unpackaged": windows-reactor calls
|
||||||
|
MddBootstrapInitialize2 with OnPackageIdentity_NOOP (crates/libs/reactor/src/app.rs), so under
|
||||||
|
MSIX package identity the App SDK bootstrapper is a no-op and the runtime is resolved from the
|
||||||
|
<PackageDependency> below instead. The framework family + min version mirror what the runner has
|
||||||
|
installed and what reactor pins (WINDOWSAPPSDK_RELEASE_MAJORMINOR = 0x20000 = 2.0 ->
|
||||||
|
Microsoft.WindowsAppRuntime.2).
|
||||||
|
|
||||||
|
Full-trust Win32 app (EntryPoint Windows.FullTrustApplication + runFullTrust) — it owns raw D3D11,
|
||||||
|
Win32 low-level input hooks, WASAPI and SDL3, none of which fit the UWP app container.
|
||||||
|
-->
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
|
IgnorableNamespaces="uap rescap">
|
||||||
|
|
||||||
|
<Identity
|
||||||
|
Name="unom.Punktfunk"
|
||||||
|
Publisher="{PUBLISHER}"
|
||||||
|
Version="{VERSION}"
|
||||||
|
ProcessorArchitecture="x64" />
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>Punktfunk</DisplayName>
|
||||||
|
<PublisherDisplayName>unom</PublisherDisplayName>
|
||||||
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0" />
|
||||||
|
<PackageDependency
|
||||||
|
Name="Microsoft.WindowsAppRuntime.2"
|
||||||
|
MinVersion="2.2.0.0"
|
||||||
|
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||||
|
</Dependencies>
|
||||||
|
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="en-us" />
|
||||||
|
</Resources>
|
||||||
|
|
||||||
|
<Applications>
|
||||||
|
<Application Id="Punktfunk" Executable="punktfunk-client.exe" EntryPoint="Windows.FullTrustApplication">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="Punktfunk"
|
||||||
|
Description="Low-latency desktop and game streaming client"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||||
|
<uap:DefaultTile Square71x71Logo="Assets\Square71x71Logo.png" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
</Capabilities>
|
||||||
|
</Package>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# punktfunk Windows client — MSIX packaging
|
||||||
|
|
||||||
|
The Windows client ships as a **signed MSIX** so Windows boxes get a real package (Start tile,
|
||||||
|
clean install/uninstall) instead of a loose exe. CI builds + publishes it from
|
||||||
|
[`.gitea/workflows/windows-msix.yml`](../../../.gitea/workflows/windows-msix.yml) to Gitea's
|
||||||
|
**generic** package registry (`https://git.unom.io/unom/-/packages`), on every `main` push that
|
||||||
|
touches the client and on `win-v*` release tags.
|
||||||
|
|
||||||
|
## What's in the package
|
||||||
|
|
||||||
|
`pack-msix.ps1` assembles a layout from a `cargo build --release` and runs `makeappx` + `signtool`:
|
||||||
|
|
||||||
|
| File | Source |
|
||||||
|
|---|---|
|
||||||
|
| `punktfunk-client.exe` | the release build |
|
||||||
|
| `Microsoft.WindowsAppRuntime.Bootstrap.dll`, `resources.pri` | auto-staged by windows-reactor's `build.rs` |
|
||||||
|
| `SDL3.dll` | auto-staged by the `sdl3` crate |
|
||||||
|
| `avcodec/avformat/avutil/swscale/swresample/...-*.dll` | `FFMPEG_DIR\bin` |
|
||||||
|
| `Assets\*.png` | checked-in tile/store logos (rasterized from `packaging/flatpak/io.unom.Punktfunk.svg`) |
|
||||||
|
| `AppxManifest.xml` | the template here, with `{VERSION}`/`{PUBLISHER}` substituted |
|
||||||
|
|
||||||
|
### Why an "unpackaged" WinUI app packages cleanly
|
||||||
|
|
||||||
|
windows-reactor calls `MddBootstrapInitialize2` with `OnPackageIdentity_NOOP`
|
||||||
|
(`crates/libs/reactor/src/app.rs`), so under MSIX **package identity** the App SDK bootstrapper is
|
||||||
|
a no-op and the runtime is resolved from the manifest's `<PackageDependency>` on
|
||||||
|
`Microsoft.WindowsAppRuntime.2` instead (reactor pins `WINDOWSAPPSDK_RELEASE_MAJORMINOR = 0x20000`
|
||||||
|
= 2.0). It's a full-trust Win32 app (`EntryPoint="Windows.FullTrustApplication"` + `runFullTrust`)
|
||||||
|
because it owns raw D3D11, Win32 low-level input hooks, WASAPI and SDL3.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
MSIX requires a strictly 4-part numeric version. The workflow computes:
|
||||||
|
- `win-vX.Y.Z` tag → `X.Y.Z.0` (a real client release; `win-v*` is its own tag namespace, kept off
|
||||||
|
the host's `host-v*` and Apple's `v*` to avoid the version-shadow bug).
|
||||||
|
- `main` push / `workflow_dispatch` → `0.2.<run_number>.0` (rolling, climbs by run number).
|
||||||
|
|
||||||
|
## Signing & install
|
||||||
|
|
||||||
|
Signing precedence in `pack-msix.ps1`:
|
||||||
|
1. **`MSIX_CERT_PFX_B64` / `MSIX_CERT_PASSWORD`** Actions secrets — a real (or shared) code-signing
|
||||||
|
`.pfx` whose **subject DN must equal `-Publisher`** (default `CN=unom`). Drop these in later to
|
||||||
|
move off self-signed with **no manifest change**; if the cert's subject differs, pass a matching
|
||||||
|
`-Publisher` (it's stamped into the manifest `Identity`).
|
||||||
|
2. otherwise an **ephemeral self-signed** cert (subject = `-Publisher`) is generated and its public
|
||||||
|
`.cer` is published next to the `.msix`.
|
||||||
|
|
||||||
|
To install a self-signed build, trust the cert once, then add the package:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Import-Certificate -FilePath .\punktfunk-client-windows_<ver>_x64.cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople
|
||||||
|
Add-AppxPackage -Path .\punktfunk-client-windows_<ver>_x64.msix
|
||||||
|
```
|
||||||
|
|
||||||
|
The MSIX declares a dependency on the Windows App SDK 2.x runtime; install
|
||||||
|
[the App SDK runtime](https://aka.ms/windowsappsdk) if `Add-AppxPackage` reports a missing
|
||||||
|
`Microsoft.WindowsAppRuntime.2` framework.
|
||||||
|
|
||||||
|
## Building locally
|
||||||
|
|
||||||
|
On the Windows runner / dev VM (MSVC + Windows SDK present), after a release build:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cargo build --release -p punktfunk-client-windows
|
||||||
|
pwsh -File crates/punktfunk-client-windows/packaging/pack-msix.ps1 `
|
||||||
|
-Version 0.2.0.0 -TargetDir C:\t\release -OutDir C:\t\msix
|
||||||
|
```
|
||||||
|
|
||||||
|
Validated end-to-end on the build VM (pack → sign → `Add-AppxPackage` → framework-dependency
|
||||||
|
resolution). The only step that needs a real display is *launching* the WinUI window (same
|
||||||
|
on-glass constraint as the rest of the client).
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,139 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Assemble, pack and sign the punktfunk Windows client as a signed MSIX.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Builds a packaging layout from a release `cargo build` output (exe + the reactor/SDL3 auto-staged
|
||||||
|
DLLs + resources.pri + FFmpeg DLLs + the checked-in Assets + the manifest), runs makeappx, and
|
||||||
|
signs with signtool. Idempotent; safe to re-run.
|
||||||
|
|
||||||
|
Signing cert precedence:
|
||||||
|
1. -PfxBase64 / -PfxPassword (a real or shared code-signing cert, e.g. from CI secrets) — the
|
||||||
|
cert's subject DN MUST match -Publisher (which is stamped into the manifest Identity).
|
||||||
|
2. otherwise an EPHEMERAL self-signed code-signing cert with subject = -Publisher is generated
|
||||||
|
in-process. The package installs only where that cert is trusted, so the matching public
|
||||||
|
.cer is exported next to the .msix for the user to import (Trusted People) before install.
|
||||||
|
Swap in a real cert later with zero manifest changes — just pass -PfxBase64/-Publisher.
|
||||||
|
|
||||||
|
Run on the Windows runner (or the dev VM) with the MSVC/Windows SDK present.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
pwsh -File pack-msix.ps1 -Version 0.2.137.0 -TargetDir C:\t\release -FfmpegBin C:\Users\Public\ffmpeg\bin -OutDir C:\t\msix
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$Version, # 4-part numeric, e.g. 0.2.137.0
|
||||||
|
[Parameter(Mandatory = $true)][string]$TargetDir, # cargo --release output dir (has the exe)
|
||||||
|
[string]$FfmpegBin = $(if ($env:FFMPEG_DIR) { Join-Path $env:FFMPEG_DIR 'bin' } else { 'C:\Users\Public\ffmpeg\bin' }),
|
||||||
|
[string]$OutDir = (Join-Path $TargetDir 'msix'),
|
||||||
|
[string]$Publisher = 'CN=unom', # MUST equal the signing cert subject DN
|
||||||
|
[string]$PfxBase64 = $env:MSIX_CERT_PFX_B64, # optional: base64 of a code-signing .pfx
|
||||||
|
[string]$PfxPassword = $env:MSIX_CERT_PASSWORD
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
if ($Version -notmatch '^\d+\.\d+\.\d+\.\d+$') {
|
||||||
|
throw "Version must be 4-part numeric (Major.Minor.Build.Revision); got '$Version'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$assets = Join-Path $here 'assets'
|
||||||
|
$manifestTemplate = Join-Path $here 'AppxManifest.xml'
|
||||||
|
|
||||||
|
# --- locate the Windows SDK tools (newest makeappx/signtool under the x64 kit bin) ---
|
||||||
|
function Find-SdkTool([string]$name) {
|
||||||
|
$root = 'C:\Program Files (x86)\Windows Kits\10\bin'
|
||||||
|
# match only versioned x64 kit bins (…\10\bin\10.0.NNNNN.N\x64\tool.exe) and pick the newest
|
||||||
|
$hit = Get-ChildItem -Path $root -Recurse -Filter $name -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.FullName -match '\\(10\.0\.\d+\.\d+)\\x64\\' } |
|
||||||
|
Sort-Object { [version]([regex]::Match($_.FullName, '\\(10\.0\.\d+\.\d+)\\x64\\').Groups[1].Value) } |
|
||||||
|
Select-Object -Last 1
|
||||||
|
if (-not $hit) { throw "$name not found under $root — install the Windows 10/11 SDK." }
|
||||||
|
$hit.FullName
|
||||||
|
}
|
||||||
|
$makeappx = Find-SdkTool 'makeappx.exe'
|
||||||
|
$signtool = Find-SdkTool 'signtool.exe'
|
||||||
|
Write-Host "makeappx: $makeappx"
|
||||||
|
Write-Host "signtool: $signtool"
|
||||||
|
|
||||||
|
# --- assemble the package layout ---
|
||||||
|
$layout = Join-Path $OutDir 'layout'
|
||||||
|
if (Test-Path $layout) { Remove-Item -Recurse -Force $layout }
|
||||||
|
New-Item -ItemType Directory -Force -Path (Join-Path $layout 'Assets') | Out-Null
|
||||||
|
|
||||||
|
# binary + auto-staged runtime bits (reactor stages the App SDK bootstrap DLL + resources.pri,
|
||||||
|
# the sdl3 crate stages SDL3.dll — see crate build output).
|
||||||
|
$required = @('punktfunk-client.exe', 'Microsoft.WindowsAppRuntime.Bootstrap.dll', 'SDL3.dll', 'resources.pri')
|
||||||
|
foreach ($f in $required) {
|
||||||
|
$src = Join-Path $TargetDir $f
|
||||||
|
if (-not (Test-Path $src)) { throw "missing build artifact '$f' in $TargetDir (did 'cargo build --release' run?)" }
|
||||||
|
Copy-Item $src (Join-Path $layout $f) -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# FFmpeg runtime DLLs (the exe link-imports the decode set; copy them all — small and correct)
|
||||||
|
$ff = Get-ChildItem -Path $FfmpegBin -Filter *.dll -ErrorAction SilentlyContinue
|
||||||
|
if (-not $ff) { throw "no FFmpeg DLLs in $FfmpegBin" }
|
||||||
|
$ff | ForEach-Object { Copy-Item $_.FullName (Join-Path $layout $_.Name) -Force }
|
||||||
|
|
||||||
|
# tile/store assets
|
||||||
|
Copy-Item (Join-Path $assets '*') (Join-Path $layout 'Assets') -Force
|
||||||
|
|
||||||
|
# manifest with version + publisher substituted
|
||||||
|
$manifest = (Get-Content -Raw $manifestTemplate).Replace('{VERSION}', $Version).Replace('{PUBLISHER}', $Publisher)
|
||||||
|
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
|
||||||
|
|
||||||
|
Write-Host "layout assembled at $layout :"
|
||||||
|
Get-ChildItem $layout -Recurse -File | ForEach-Object { " $($_.FullName.Substring($layout.Length + 1))" }
|
||||||
|
|
||||||
|
# --- pack ---
|
||||||
|
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||||
|
$msix = Join-Path $OutDir "punktfunk-client-windows_${Version}_x64.msix"
|
||||||
|
& $makeappx pack /o /d $layout /p $msix
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
|
# --- signing cert ---
|
||||||
|
$pfxPath = Join-Path $OutDir 'signing.pfx'
|
||||||
|
$cerPath = Join-Path $OutDir "punktfunk-client-windows_${Version}_x64.cer"
|
||||||
|
$selfSigned = $false
|
||||||
|
if ($PfxBase64) {
|
||||||
|
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
|
||||||
|
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($PfxBase64))
|
||||||
|
} else {
|
||||||
|
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
|
||||||
|
$selfSigned = $true
|
||||||
|
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }
|
||||||
|
$cert = New-SelfSignedCertificate -Type Custom -Subject $Publisher `
|
||||||
|
-KeyUsage DigitalSignature -FriendlyName 'punktfunk MSIX (self-signed)' `
|
||||||
|
-CertStoreLocation 'Cert:\CurrentUser\My' `
|
||||||
|
-TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}')
|
||||||
|
$sec = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText
|
||||||
|
Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath $pfxPath -Password $sec | Out-Null
|
||||||
|
Export-Certificate -Cert "Cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath $cerPath | Out-Null
|
||||||
|
Remove-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)" -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- sign (timestamp best-effort; a self-signed cert needs no TSA) ---
|
||||||
|
$signArgs = @('sign', '/fd', 'SHA256', '/f', $pfxPath)
|
||||||
|
if ($PfxPassword) { $signArgs += @('/p', $PfxPassword) }
|
||||||
|
& $signtool ($signArgs + @('/tr', 'http://timestamp.digicert.com', '/td', 'SHA256', $msix))
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Warning "timestamped sign failed — retrying without a timestamp"
|
||||||
|
& $signtool ($signArgs + @($msix))
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "signtool sign failed ($LASTEXITCODE)" }
|
||||||
|
}
|
||||||
|
Remove-Item $pfxPath -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# if self-signed, the public .cer must travel with the package; otherwise drop it (chain is public)
|
||||||
|
if (-not $selfSigned) { Remove-Item $cerPath -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "==> MSIX: $msix"
|
||||||
|
if ($selfSigned) {
|
||||||
|
Write-Host "==> self-signed; trust before install: Import-Certificate -FilePath '$cerPath' -CertStoreLocation Cert:\LocalMachine\TrustedPeople"
|
||||||
|
}
|
||||||
|
# emit paths for the workflow to publish (only under CI, where GITHUB_ENV is set)
|
||||||
|
if ($env:GITHUB_ENV) {
|
||||||
|
"MSIX_PATH=$msix" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
if ($selfSigned) { "MSIX_CER_PATH=$cerPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user