From 80061fbf6b09838c7042cb4dca4ed9380d166bb3 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 14 Aug 2026 20:07:57 +0200 Subject: [PATCH] =?UTF-8?q?fix(ci):=20an=20unset=20HOST=5FCER=5FPATH=20is?= =?UTF-8?q?=20$null,=20and=20a=20null=20hash=20key=20is=20fatal=20?= =?UTF-8?q?=E2=80=94=20not=20an=20empty=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Azure signing produces no .cer, so HOST_CER_PATH is deliberately unset. The publish step then built its alias map as a single hash literal containing $env:HOST_CER_PATH as a KEY, and an unset $env: var is $null — "A null key is not allowed in a hash literal", which failed the whole step. Canary run 18256: the installer signed fine and published to its versioned path, then this line killed the alias refresh, so `canary/punktfunk-host-setup.exe` went stale. I reasoned about this line while making the .cer optional and concluded an unset variable would give an empty-string key, which is legal. It does not — that only happens through string interpolation. The $files guard just above filters the missing .cer correctly; the hash literal ran before anything could use it. Build the map incrementally instead, adding the .cer entry only when there is one, so the legacy .pfx modes still alias it. windows-client.yml survived the same change only by accident: it writes "$($env:MSIX_CER_PATH)", and interpolating $null yields an empty string, which IS a legal key. Made that explicit too rather than leaving correctness resting on quotes someone could reasonably tidy away. Verified under pwsh 7: the old literal reproduces the exact CI message with the var unset; the new form yields one entry unset and two entries set, with the .cer alias intact. --- .gitea/workflows/windows-client.yml | 11 +++++++---- .gitea/workflows/windows-host.yml | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/windows-client.yml b/.gitea/workflows/windows-client.yml index d0d8e528..9745490e 100644 --- a/.gitea/workflows/windows-client.yml +++ b/.gitea/workflows/windows-client.yml @@ -288,10 +288,13 @@ jobs: # stable release -> `latest/` alias; canary main build -> `canary/` alias. $alias = if ($env:GITHUB_REF -like 'refs/tags/v*') { 'latest' } else { 'canary' } # version-less, arch-suffixed alias names so each channel keeps one predictable URL. - $aliasNames = @{ - "$($env:MSIX_PATH)" = "$($env:PKG)_${{ matrix.arch }}.msix" - "$($env:MSIX_CER_PATH)" = "$($env:PKG)_${{ matrix.arch }}.cer" - } + # Under Azure signing there is no .cer, so MSIX_CER_PATH is unset. The quotes below are + # load-bearing: "$($env:UNSET)" interpolates to an empty string (a legal key), whereas a + # BARE $env:UNSET is $null and a null key is a hard error in a hash literal — which is + # exactly how windows-host.yml's publish step broke. Added explicitly rather than relying + # on that accident, so removing the quotes can't silently reintroduce it. + $aliasNames = @{ "$($env:MSIX_PATH)" = "$($env:PKG)_${{ matrix.arch }}.msix" } + if ($env:MSIX_CER_PATH) { $aliasNames[$env:MSIX_CER_PATH] = "$($env:PKG)_${{ matrix.arch }}.cer" } $files = @($env:MSIX_PATH, $env:MSIX_CER_PATH) | Where-Object { $_ -and (Test-Path $_) } if (-not $files) { throw "pack produced no artifacts to publish" } function Put($f, $url) { diff --git a/.gitea/workflows/windows-host.yml b/.gitea/workflows/windows-host.yml index 1047fbeb..29297c88 100644 --- a/.gitea/workflows/windows-host.yml +++ b/.gitea/workflows/windows-host.yml @@ -472,7 +472,13 @@ jobs: # Refresh the channel alias (delete-then-reupload, like flatpak.yml/decky.yml) for a # predictable download URL: stable release -> `latest/`, canary main build -> `canary/`. $alias = if ($env:GITHUB_REF -like 'refs/tags/v*') { 'latest' } else { 'canary' } - $aliasNames = @{ $env:HOST_SETUP_PATH = 'punktfunk-host-setup.exe'; $env:HOST_CER_PATH = 'punktfunk-host-windows.cer' } + # Build this incrementally, NOT as one literal: under Azure signing there is no .cer, so + # HOST_CER_PATH is unset — and an unset $env: var is $null, which is a HARD ERROR as a hash + # literal key ("A null key is not allowed in a hash literal"), not the empty-string key it + # looks like it should be. The $files guard above filters the missing .cer out just fine; + # this line ran before anything could use it and failed the whole publish step. + $aliasNames = @{ $env:HOST_SETUP_PATH = 'punktfunk-host-setup.exe' } + if ($env:HOST_CER_PATH) { $aliasNames[$env:HOST_CER_PATH] = 'punktfunk-host-windows.cer' } foreach ($f in $files) { $an = $aliasNames[$f]; if (-not $an) { continue } curl.exe -fsS -o NUL --user "enricobuehler:$($env:REGISTRY_TOKEN)" -X DELETE "$base/$alias/$an" 2>$null -- 2.54.0