Install or unpack each desktop package on its native runner, verify OS association metadata, launch it with a freshly built Sample Region Atlas, and require byte-exact intake into an isolated library. Fix Windows GUI-installer exit handling and ad-hoc seal unsigned macOS preview bundles after assembly.
- commit
- b9c833060db74c2aa673e925aa878dc8a32ed82f
- parent
- 0deaa6862cd1767f1816d55574da11125d00f73b
- author
- Dami <dami.etoile@gmail.com>
| M | .github/ | 19 | ++++++---- |
| M | docs/ | 11 | +++++- |
| M | docs/ | 9 | +++-- |
| M | scripts/ | 10 | +++++- |
| A | scripts/ | 55 | ++++++++++++++++++++++++++++++ |
| A | scripts/ | 53 | +++++++++++++++++++++++++++++ |
| A | scripts/ | 53 | +++++++++++++++++++++++++++++ |
| M | tests/ | 18 | ++++++++++ |
@@ -232,6 +232,16 @@ jobs:
232232 shell: bash233233 run: ./scripts/release/package-desktop.sh linux-amd64 "${{ steps.metadata.outputs.version }}" Atlas dist234234 235+ - name: Smoke macOS package and native file intake236+ if: matrix.target == 'darwin-arm64'237+ shell: bash238+ run: ./scripts/release/smoke-macos.sh "dist/Atlas-${{ steps.metadata.outputs.version }}-darwin-arm64.zip" "${{ steps.metadata.outputs.version }}" "$GITHUB_WORKSPACE"239+240+ - name: Smoke Linux install, MIME registration, and native file intake241+ if: matrix.target == 'linux-amd64'242+ shell: bash243+ run: ./scripts/release/smoke-linux.sh "dist/Atlas-${{ steps.metadata.outputs.version }}-linux-amd64.deb" "${{ steps.metadata.outputs.version }}" "$GITHUB_WORKSPACE"244+235245 - name: Package Windows installer236246 if: matrix.target == 'windows-amd64'237247 shell: pwsh@@ -248,14 +258,7 @@ jobs:
248258 - name: Smoke Windows install and .atlas association249259 if: matrix.target == 'windows-amd64'250260 shell: pwsh251- run: |252- $installer = "dist/Atlas-${{ steps.metadata.outputs.version }}-windows-amd64-setup.exe"253- & $installer /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /CURRENTUSER254- if ($LASTEXITCODE -ne 0) { throw "installer exited $LASTEXITCODE" }255- $association = (Get-Item "HKCU:\Software\Classes\.atlas").GetValue("")256- if ($association -ne "Atlas.Volume") { throw "unexpected .atlas association: $association" }257- $command = (Get-Item "HKCU:\Software\Classes\Atlas.Volume\shell\open\command").GetValue("")258- if ($command -notmatch 'Atlas\.exe.*%1') { throw "unexpected open command: $command" }261+ run: ./scripts/release/smoke-windows.ps1 -Installer "dist/Atlas-${{ steps.metadata.outputs.version }}-windows-amd64-setup.exe" -Version "${{ steps.metadata.outputs.version }}" -RepoRoot "${{ github.workspace }}"259262 260263 - uses: actions/upload-artifact@v4261264 with:@@ -33,7 +33,16 @@ macOS signing uses hardened runtime and timestamping, submits with
3333 `notarytool`, staples the accepted ticket, and recreates the final ZIP. Windows3434 signs and verifies both `Atlas.exe` and the installer with SHA-256 and a public3535 timestamp service. No key material is written to the repository or uploaded as36-an artifact.36+an artifact. A workflow-dispatch macOS preview has no Developer ID identity,37+but the completed bundle is ad-hoc signed after its `Info.plist` is installed so38+its bundle seal is structurally valid. It is never described as notarized.39+40+Every desktop matrix job consumes its produced package rather than trusting41+archive creation. macOS unpacks and verifies the app seal and UTI, Linux42+installs the `.deb` and verifies MIME registration, and Windows installs the43+Inno package and reads the per-user ProgID/open command. Each packaged44+executable is then launched with a freshly built Sample Region Atlas and must45+install the exact bytes into an isolated library.3746 3847 ## Publication transaction3948 @@ -43,9 +43,12 @@ stamps, no hashes and no content.
4343 acceptance. It parses the macOS property list and Linux shared-MIME XML, checks4444 the Windows installer association, and holds the workflow to draft-first,4545 exact-asset publication. The platform jobs perform the dynamic half: Windows46-installs its package and reads the registered open command, while the release47-job inspects macOS/Linux payloads and builds the same Sample Region online and48-offline through the packaged CLI. Byte inequality is a release failure.46+installs its package and reads the registered open command, Linux installs its47+`.deb` and checks MIME registration, and macOS unpacks and verifies its app48+seal and UTI. All three packaged executables must intake the exact same freshly49+built Sample Region `.atlas` into isolated libraries. The release job also50+inspects payloads and builds Sample Region online and offline through the51+packaged CLI. Byte inequality is a release failure.4952 5053 ## What tests are made of5154 @@ -34,9 +34,16 @@ if [[ -n "${MACOS_CERTIFICATE_P12_BASE64:-}" ]]; then
3434 --keychain "$keychain" --sign "$MACOS_SIGNING_IDENTITY" "$app"3535 codesign --verify --deep --strict --verbose=2 "$app"3636 else37- echo "MACOS_CERTIFICATE_P12_BASE64 is empty; packaging an unsigned app"37+ # Go's linker ad-hoc signs the Mach-O, but adding Info.plist around that38+ # executable changes the signed bundle boundary. Re-sign the completed app39+ # so workflow-dispatch previews are structurally valid even though only a40+ # Developer ID + notarization ticket can satisfy Gatekeeper after download.41+ codesign --force --deep --sign - "$app"42+ echo "MACOS_CERTIFICATE_P12_BASE64 is empty; packaging an ad-hoc signed preview"3843 fi3944 45+codesign --verify --deep --strict --verbose=2 "$app"46+4047 ditto -c -k --keepParent "$app" "$output"4148 4249 if [[ -n "${MACOS_NOTARY_KEY_BASE64:-}" ]]; then@@ -52,6 +59,7 @@ if [[ -n "${MACOS_NOTARY_KEY_BASE64:-}" ]]; then
5259 --key "$notary_key" --key-id "$MACOS_NOTARY_KEY_ID" --issuer "$MACOS_NOTARY_ISSUER_ID"5360 xcrun stapler staple "$app"5461 xcrun stapler validate "$app"62+ codesign --verify --deep --strict --verbose=2 "$app"5563 rm -f "$output"5664 ditto -c -k --keepParent "$app" "$output"5765 fi@@ -0,0 +1,55 @@
1+2+set -euo pipefail3+4+if [[ $# -ne 3 ]]; then5+ echo "usage: smoke-linux.sh <package.deb> <version> <repo-root>" >&26+ exit 27+fi8+9+package=$110+version=$211+root=$312+deb_version=${version#v}13+if [[ ! "$deb_version" =~ ^[0-9] ]]; then14+ deb_version="0~$deb_version"15+fi16+stage=$(mktemp -d)17+app_pid=""18+cleanup() {19+ if [[ -n "$app_pid" ]]; then kill "$app_pid" >/dev/null 2>&1 || true; fi20+ rm -rf "$stage"21+}22+trap cleanup EXIT23+24+sudo apt-get install -y "$package" xvfb dbus-x1125+dpkg-query -W -f='${Status}\n${Version}\n${Architecture}\n' atlas-desktop \26+ | grep -Fx 'install ok installed'27+dpkg-query -W -f='${Version}' atlas-desktop | grep -Fx "$deb_version"28+dpkg-query -W -f='${Architecture}' atlas-desktop | grep -Fx amd6429+test -x /usr/bin/Atlas30+test -f /usr/share/applications/dev.felinestatemachine.Atlas.desktop31+test -f /usr/share/mime/packages/dev.felinestatemachine.atlas.xml32+33+go run "$root/cmd/atlas" build -cache "$stage/cache" -bundles "$stage/source" \34+ "$root/examples/sample-region.atlas-project"35+artifact=$(find "$stage/source" -maxdepth 1 -type f -name 'sample-region-*.atlas' -print -quit)36+test -n "$artifact"37+[[ "$(xdg-mime query filetype "$artifact")" == application/vnd.felinestatemachine.atlas ]]38+gio mime application/vnd.felinestatemachine.atlas \39+ | grep -F 'dev.felinestatemachine.Atlas.desktop'40+41+mkdir -p "$stage/library" "$stage/data"42+ATLAS_BUNDLES_DIR="$stage/library" ATLAS_DATA_DIR="$stage/data" \43+ dbus-run-session -- xvfb-run -a /usr/bin/Atlas "$artifact" \44+ >"$stage/atlas.log" 2>&1 &45+app_pid=$!46+for _ in $(seq 1 60); do47+ if find "$stage/library" -maxdepth 1 -type f -name 'sample-region-*.atlas' -print -quit | grep -q .; then48+ break49+ fi50+ kill -0 "$app_pid" 2>/dev/null || { cat "$stage/atlas.log" >&2; exit 1; }51+ sleep 152+done53+installed=$(find "$stage/library" -maxdepth 1 -type f -name 'sample-region-*.atlas' -print -quit)54+test -n "$installed"55+cmp "$artifact" "$installed"@@ -0,0 +1,53 @@
1+2+set -euo pipefail3+4+if [[ $# -ne 3 ]]; then5+ echo "usage: smoke-macos.sh <package.zip> <version> <repo-root>" >&26+ exit 27+fi8+9+package=$110+version=$211+root=$312+version_number=${version#v}13+mac_version=0.0.014+if [[ "$version_number" =~ ^[0-9]+(\.[0-9]+){0,2} ]]; then15+ mac_version=${BASH_REMATCH[0]}16+fi17+stage=$(mktemp -d)18+app_pid=""19+cleanup() {20+ if [[ -n "$app_pid" ]]; then kill "$app_pid" >/dev/null 2>&1 || true; fi21+ rm -rf "$stage"22+}23+trap cleanup EXIT24+25+ditto -x -k "$package" "$stage/unpacked"26+app="$stage/unpacked/Atlas.app"27+executable="$app/Contents/MacOS/Atlas"28+test -x "$executable"29+codesign --verify --deep --strict --verbose=2 "$app"30+[[ "$(plutil -extract CFBundleIdentifier raw "$app/Contents/Info.plist")" == dev.felinestatemachine.atlas ]]31+[[ "$(plutil -extract CFBundleShortVersionString raw "$app/Contents/Info.plist")" == "$mac_version" ]]32+plutil -extract UTExportedTypeDeclarations.0.UTTypeIdentifier raw "$app/Contents/Info.plist" \33+ | grep -Fx dev.felinestatemachine.atlas.volume34+"$executable" --version | grep -Fx "Atlas $version"35+36+go run "$root/cmd/atlas" build -cache "$stage/cache" -bundles "$stage/source" \37+ "$root/examples/sample-region.atlas-project"38+artifact=$(find "$stage/source" -maxdepth 1 -type f -name 'sample-region-*.atlas' -print -quit)39+test -n "$artifact"40+mkdir -p "$stage/library" "$stage/data"41+ATLAS_BUNDLES_DIR="$stage/library" ATLAS_DATA_DIR="$stage/data" \42+ "$executable" "$artifact" >"$stage/atlas.log" 2>&1 &43+app_pid=$!44+for _ in $(seq 1 60); do45+ if find "$stage/library" -maxdepth 1 -type f -name 'sample-region-*.atlas' -print -quit | grep -q .; then46+ break47+ fi48+ kill -0 "$app_pid" 2>/dev/null || { cat "$stage/atlas.log" >&2; exit 1; }49+ sleep 150+done51+installed=$(find "$stage/library" -maxdepth 1 -type f -name 'sample-region-*.atlas' -print -quit)52+test -n "$installed"53+cmp "$artifact" "$installed"@@ -0,0 +1,53 @@
1+param(2+ [Parameter(Mandatory = $true)][string]$Installer,3+ [Parameter(Mandatory = $true)][string]$Version,4+ [Parameter(Mandatory = $true)][string]$RepoRoot5+)6+7+$ErrorActionPreference = "Stop"8+$stage = Join-Path $env:RUNNER_TEMP ("atlas-windows-smoke-" + [Guid]::NewGuid().ToString("N"))9+New-Item -ItemType Directory -Path $stage | Out-Null10+$app = $null11+12+try {13+ $install = Start-Process -FilePath $Installer -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/CURRENTUSER' -Wait -PassThru14+ if ($install.ExitCode -ne 0) { throw "installer exited $($install.ExitCode)" }15+16+ $association = (Get-Item "HKCU:\Software\Classes\.atlas").GetValue("")17+ if ($association -ne "Atlas.Volume") { throw "unexpected .atlas association: $association" }18+ $contentType = (Get-Item "HKCU:\Software\Classes\.atlas").GetValue("Content Type")19+ if ($contentType -ne "application/vnd.felinestatemachine.atlas") { throw "unexpected content type: $contentType" }20+ $command = (Get-Item "HKCU:\Software\Classes\Atlas.Volume\shell\open\command").GetValue("")21+ if ($command -notmatch '^"([^"]+Atlas\.exe)" "%1"$') { throw "unexpected open command: $command" }22+ $executable = $Matches[1]23+ if (-not (Test-Path $executable -PathType Leaf)) { throw "installed executable missing: $executable" }24+25+ $cache = Join-Path $stage "cache"26+ $source = Join-Path $stage "source"27+ & go run "$RepoRoot/cmd/atlas" build -cache $cache -bundles $source "$RepoRoot/examples/sample-region.atlas-project"28+ if ($LASTEXITCODE -ne 0) { throw "Sample Region fixture build exited $LASTEXITCODE" }29+ $artifact = Get-ChildItem $source -Filter "sample-region-*.atlas" | Select-Object -First 130+ if ($null -eq $artifact) { throw "Sample Region fixture was not built" }31+32+ $library = Join-Path $stage "library"33+ $data = Join-Path $stage "data"34+ New-Item -ItemType Directory -Path $library, $data | Out-Null35+ $env:ATLAS_BUNDLES_DIR = $library36+ $env:ATLAS_DATA_DIR = $data37+ $app = Start-Process -FilePath $executable -ArgumentList $artifact.FullName -PassThru38+ $installed = $null39+ foreach ($attempt in 1..60) {40+ $installed = Get-ChildItem $library -Filter "sample-region-*.atlas" -ErrorAction SilentlyContinue | Select-Object -First 141+ if ($null -ne $installed) { break }42+ if ($app.HasExited) { throw "installed Atlas exited before file intake with $($app.ExitCode)" }43+ Start-Sleep -Seconds 144+ }45+ if ($null -eq $installed) { throw "installed Atlas did not intake Sample Region" }46+ if ((Get-FileHash $artifact.FullName -Algorithm SHA256).Hash -ne (Get-FileHash $installed.FullName -Algorithm SHA256).Hash) {47+ throw "installed Atlas changed the handed-off artifact"48+ }49+}50+finally {51+ if ($null -ne $app -and -not $app.HasExited) { Stop-Process -Id $app.Id -Force }52+ if (Test-Path $stage) { Remove-Item -Recurse -Force $stage }53+}@@ -62,6 +62,10 @@ class NativePackagingTest(unittest.TestCase):
6262 self.assertIn("package-desktop.sh", workflow)6363 self.assertIn("sign-windows.ps1", workflow)6464 self.assertIn("package-smoke.sh", workflow)65+ self.assertIn("smoke-macos.sh", workflow)66+ self.assertIn("smoke-linux.sh", workflow)67+ self.assertIn("smoke-windows.ps1", workflow)68+ self.assertNotIn("& $installer /VERYSILENT", workflow)6569 mac_package = (ROOT / "scripts/release/package-desktop.sh").read_text()6670 self.assertIn("sign-macos.sh", mac_package)6771 self.assertIn("stage=$(mktemp -d)", mac_package)@@ -71,6 +75,20 @@ class NativePackagingTest(unittest.TestCase):
7175 self.assertIn('--keychain "$keychain"', mac_signer)7276 self.assertNotIn("security list-keychains", mac_signer)7377 self.assertIn('xcrun stapler validate "$app"', mac_signer)78+ self.assertIn('codesign --force --deep --sign - "$app"', mac_signer)79+80+ windows_smoke = (ROOT / "scripts/release/smoke-windows.ps1").read_text()81+ self.assertIn("Start-Process -FilePath $Installer", windows_smoke)82+ self.assertIn("$install.ExitCode", windows_smoke)83+ self.assertIn("application/vnd.felinestatemachine.atlas", windows_smoke)84+ linux_smoke = (ROOT / "scripts/release/smoke-linux.sh").read_text()85+ self.assertIn("dpkg-query", linux_smoke)86+ self.assertIn("xdg-mime query filetype", linux_smoke)87+ self.assertIn("ATLAS_BUNDLES_DIR", linux_smoke)88+ mac_smoke = (ROOT / "scripts/release/smoke-macos.sh").read_text()89+ self.assertIn("codesign --verify --deep --strict", mac_smoke)90+ self.assertIn("dev.felinestatemachine.atlas.volume", mac_smoke)91+ self.assertIn("ATLAS_BUNDLES_DIR", mac_smoke)7492 7593 def test_release_asset_allowlist_is_exact(self) -> None:7694 version = "v1.2.3"