bb11b2faf7
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>
140 lines
7.2 KiB
PowerShell
140 lines
7.2 KiB
PowerShell
<#
|
|
.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 }
|
|
}
|