From b200d997b0fd1caeef4d685733096048b77be658 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Tue, 1 Sep 2026 10:23:07 -0400 Subject: [PATCH] fix(ci): write ZIP entry names with forward slashes Compress-Archive on Windows PowerShell writes the OS path separator into entry names, so the archives came out with "tor\tor.exe". The ZIP format mandates forward slashes, and 5.1.0 shipped "tor/tor.exe" from `zip -X` on Linux -- this was a regression introduced when the packaging moved to the signing host. It is not cosmetic for this application. Extractors that take the backslash literally produce a file named "tor\tor.exe" in the archive root rather than a tor directory, which puts the bundled Tor somewhere wowlet does not look. The daemon is given an exclusive --tx-proxy, so a wallet that cannot find its Tor cannot relay a transaction at all. Archives are now built through System.IO.Compression with entry names normalised explicitly, and the job asserts the result: a backslash in any entry name, or a missing tor/tor.exe, fails the build rather than shipping. Caught by extracting the artifact and diffing it against the 5.1.0 release before publishing. --- .gitea/workflows/build-windows.yml | 42 +++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build-windows.yml b/.gitea/workflows/build-windows.yml index 17ca1a80..61cca3b9 100644 --- a/.gitea/workflows/build-windows.yml +++ b/.gitea/workflows/build-windows.yml @@ -219,10 +219,44 @@ jobs: shell: powershell run: | $ErrorActionPreference = 'Stop' - Compress-Archive -Path 'unsigned\*' -DestinationPath 'wowlet-x86_64-windows-unsigned.zip' -Force - Compress-Archive -Path 'stage\*' -DestinationPath 'wowlet-x86_64-windows.zip' -Force - Get-ChildItem 'wowlet-x86_64-windows*.zip' | - ForEach-Object { "{0} {1} {2}" -f $_.Name, $_.Length, (Get-FileHash $_.FullName -Algorithm SHA256).Hash } + Add-Type -AssemblyName System.IO.Compression.FileSystem + + # Not Compress-Archive: Windows PowerShell writes the OS separator + # into entry names, producing "tor\tor.exe". The ZIP format mandates + # forward slashes, and extractors that take the backslash literally + # leave a file called "tor\tor.exe" in the root instead of a tor + # directory -- at which point the bundled Tor is not where wowlet + # looks for it, and this wallet gives its daemon an exclusive + # --tx-proxy, so no Tor means no transaction relay at all. + # v5.1.0 shipped tor/tor.exe from `zip -X`; keep that shape. + function New-Archive([string] $Source, [string] $Destination) { + if (Test-Path $Destination) { Remove-Item $Destination -Force } + $root = (Resolve-Path $Source).Path.TrimEnd('\') + $zip = [System.IO.Compression.ZipFile]::Open($Destination, 'Create') + try { + foreach ($file in Get-ChildItem $root -Recurse -File | Sort-Object FullName) { + $entry = $file.FullName.Substring($root.Length + 1).Replace('\', '/') + [void][System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile( + $zip, $file.FullName, $entry, 'Optimal') + } + } finally { $zip.Dispose() } + } + + New-Archive 'unsigned' 'wowlet-x86_64-windows-unsigned.zip' + New-Archive 'stage' 'wowlet-x86_64-windows.zip' + + # Prove the shape rather than trusting it: a backslash in any entry + # name fails the build. + foreach ($archive in @('wowlet-x86_64-windows.zip', 'wowlet-x86_64-windows-unsigned.zip')) { + $zip = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path $archive)) + try { + $names = $zip.Entries | ForEach-Object { $_.FullName } + if ($names -match '\\') { throw "$archive contains a backslash entry name: $($names -join ', ')" } + if ($names -notcontains 'tor/tor.exe') { throw "$archive is missing tor/tor.exe (got: $($names -join ', '))" } + } finally { $zip.Dispose() } + "{0} {1} {2}" -f $archive, (Get-Item $archive).Length, + (Get-FileHash $archive -Algorithm SHA256).Hash + } - name: upload Windows zips uses: actions/upload-artifact@v3