Repository navigation
release #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Release pipeline: tag v* -> build, sign, notarize, package, SBOM, publish. | |
| # | |
| # Signing steps are guarded by secret presence so a fork (or a tag pushed | |
| # before certificates exist) still produces unsigned-but-labelled artifacts | |
| # instead of a red pipeline. Real releases require the secrets: | |
| # macOS: MACOS_CERT_P12 (base64), MACOS_CERT_PASSWORD, | |
| # MACOS_SIGN_IDENTITY, APPLE_ID, APPLE_TEAM_ID, APPLE_APP_PASSWORD | |
| # Windows: WINDOWS_CERT_PFX (base64), WINDOWS_CERT_PASSWORD | |
| name: release | |
| on: | |
| push: | |
| tags: ["v*"] | |
| # Manual dry-run of the whole pipeline without tagging; artifacts are | |
| # uploaded but no GitHub Release is created. | |
| workflow_dispatch: | |
| permissions: | |
| contents: write # create the GitHub Release | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: "0" | |
| jobs: | |
| # Gate: a release must never ship what CI would have rejected. | |
| compliance: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: compliance | |
| cache-all-crates: "true" | |
| - run: scripts/ci-check.sh | |
| shell: bash | |
| - run: scripts/ci-compliance.sh | |
| shell: bash | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: sbom | |
| path: dist/sbom/ | |
| macos: | |
| needs: compliance | |
| runs-on: macos-15 | |
| env: | |
| # Evaluated here because `secrets` is unavailable in step-level `if:`. | |
| HAVE_MACOS_CERT: ${{ secrets.MACOS_CERT_P12 != '' }} | |
| HAVE_APPLE_NOTARY: ${{ secrets.APPLE_APP_PASSWORD != '' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: release-macos | |
| - name: Import Developer ID certificate | |
| # Secrets are unavailable in forks, so this must skip rather than fail. | |
| # The check reads a job-level env var because the `secrets` context is | |
| # not available to step-level `if:` conditions. | |
| if: env.HAVE_MACOS_CERT == 'true' | |
| env: | |
| MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} | |
| MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} | |
| run: | | |
| # Ephemeral keychain: the cert exists only for this job and dies | |
| # with the runner. Never imported into the login keychain. | |
| KEYCHAIN="$RUNNER_TEMP/release.keychain-db" | |
| KEYCHAIN_PW="$(uuidgen)" | |
| security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN" | |
| security set-keychain-settings -lut 3600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN" | |
| echo "$MACOS_CERT_P12" | base64 --decode > "$RUNNER_TEMP/cert.p12" | |
| security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" \ | |
| -P "$MACOS_CERT_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple: \ | |
| -s -k "$KEYCHAIN_PW" "$KEYCHAIN" | |
| security list-keychains -d user -s "$KEYCHAIN" login.keychain-db | |
| rm -f "$RUNNER_TEMP/cert.p12" | |
| - name: Store notarytool credentials | |
| if: env.HAVE_APPLE_NOTARY == 'true' | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} | |
| run: | | |
| xcrun notarytool store-credentials release-profile \ | |
| --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" \ | |
| --password "$APPLE_APP_PASSWORD" | |
| echo "MACOS_NOTARY_PROFILE=release-profile" >> "$GITHUB_ENV" | |
| - name: Build, sign, notarize, package | |
| env: | |
| MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }} | |
| run: scripts/build-macos-release.sh | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-universal | |
| path: dist/*.dmg | |
| windows: | |
| needs: compliance | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [x86_64-pc-windows-msvc, aarch64-pc-windows-msvc] | |
| runs-on: windows-2025 | |
| env: | |
| HAVE_WINDOWS_CERT: ${{ secrets.WINDOWS_CERT_PFX != '' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: release-${{ matrix.target }} | |
| - name: Import Authenticode certificate | |
| if: env.HAVE_WINDOWS_CERT == 'true' | |
| shell: pwsh | |
| env: | |
| WINDOWS_CERT_PFX: ${{ secrets.WINDOWS_CERT_PFX }} | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| $pfx = Join-Path $env:RUNNER_TEMP 'cert.pfx' | |
| [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:WINDOWS_CERT_PFX)) | |
| $pw = ConvertTo-SecureString $env:WINDOWS_CERT_PASSWORD -AsPlainText -Force | |
| $cert = Import-PfxCertificate -FilePath $pfx -CertStoreLocation Cert:\CurrentUser\My -Password $pw | |
| Remove-Item $pfx | |
| "WINDOWS_CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File -Append $env:GITHUB_ENV | |
| - name: Install NSIS | |
| run: choco install nsis -y --no-progress | |
| - name: Build and package | |
| shell: bash | |
| run: scripts/build-windows.sh ${{ matrix.target }} | |
| - name: Verify x86-64 ISA baseline (pre-Haswell guard) | |
| if: matrix.target == 'x86_64-pc-windows-msvc' | |
| shell: bash | |
| # objdump ships with the Git-for-Windows/MSYS toolchain on runners. | |
| run: scripts/ci-verify-baseline.sh dist/windows/${{ matrix.target }}/outloud-spike.exe | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-${{ matrix.target }} | |
| path: | | |
| dist/windows/${{ matrix.target }}/*.zip | |
| dist/windows/${{ matrix.target }}/*.exe | |
| dist/windows/${{ matrix.target }}/*.msi | |
| linux: | |
| needs: compliance | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: | |
| - x86_64-unknown-linux-gnu | |
| - x86_64-unknown-linux-musl | |
| - aarch64-unknown-linux-gnu | |
| - aarch64-unknown-linux-musl | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: release-${{ matrix.target }} | |
| - name: Install packaging tools | |
| run: | | |
| sudo apt-get update | |
| # desktop-file-utils supplies desktop-file-validate, which | |
| # appimagetool shells out to and refuses to run without. Omitting | |
| # it failed all three Linux jobs of the v0.1.0 release AFTER every | |
| # other platform had built successfully, with the only clue being | |
| # "desktop-file-validate command is missing" 200 lines into a log. | |
| sudo apt-get install -y musl-tools rpm binutils desktop-file-utils | |
| # appimagetool: single static binary, pinned release. | |
| curl -fsSL -o /usr/local/bin/appimagetool \ | |
| https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage | |
| sudo chmod +x /usr/local/bin/appimagetool | |
| - name: Build and package | |
| run: scripts/build-linux.sh ${{ matrix.target }} | |
| - name: Verify x86-64 ISA baseline (pre-Haswell guard) | |
| if: startsWith(matrix.target, 'x86_64') | |
| run: scripts/ci-verify-baseline.sh dist/linux/${{ matrix.target }}/outloud-spike | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-${{ matrix.target }} | |
| path: | | |
| dist/linux/${{ matrix.target }}/*.tar.gz | |
| dist/linux/${{ matrix.target }}/*.AppImage | |
| dist/linux/${{ matrix.target }}/*.deb | |
| dist/linux/${{ matrix.target }}/*.rpm | |
| dist/linux/${{ matrix.target }}/*.yml | |
| headless: | |
| needs: compliance | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: release-headless | |
| - name: Build static headless daemons | |
| run: | | |
| sudo apt-get update && sudo apt-get install -y musl-tools | |
| rustup target add x86_64-unknown-linux-musl | |
| scripts/build-headless.sh x86_64-unknown-linux-musl | |
| cargo install cross --locked | |
| scripts/build-headless.sh aarch64-unknown-linux-musl || true | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: headless | |
| path: dist/headless/ | |
| publish: | |
| # workflow_dispatch runs stop here: artifacts exist, no release created. | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| needs: [macos, windows, linux, headless] | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| # The double-click installer is a source file, not a build output, so | |
| # nothing in the matrix produces it and it was absent from every | |
| # release. The one attached to v2026.08.06-1649 was uploaded by hand | |
| # and fetched install.sh from a feature branch; the next release would | |
| # simply have dropped the install path the notes recommend first. | |
| - uses: actions/checkout@v4 | |
| with: | |
| path: repo | |
| - name: Attach the double-click installer | |
| run: cp repo/scripts/Install-OutLoud.command artifacts/ | |
| - name: Refuse to publish an installer pinned to a branch | |
| # A release is the one artifact nobody re-reads before strangers use | |
| # it. This is the defect that broke the last one. | |
| run: | | |
| if grep -q 'raw.githubusercontent.com/[^"]*refs/heads/' artifacts/Install-OutLoud.command; then | |
| grep -n 'refs/heads/' artifacts/Install-OutLoud.command | |
| echo "::error title=installer pinned to a branch::Install-OutLoud.command fetches install.sh from a branch ref. Use /main/, or the installer breaks when that branch is deleted." | |
| exit 1 | |
| fi | |
| - name: Generate checksums | |
| run: | | |
| cd artifacts | |
| find . -type f ! -name 'SHA256SUMS' -exec shasum -a 256 {} \; > SHA256SUMS | |
| cat SHA256SUMS | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| draft: true # a human sanity-checks artifacts before publishing | |
| # Written here rather than by hand on the release page. The | |
| # hand-written notes on the last release pointed at a feature | |
| # branch, and nothing could notice: notes are not code, so no | |
| # check ever read them. Generating them from the workflow means | |
| # the install instructions are reviewed like everything else. | |
| body: | | |
| ### Install | |
| Download **Install-OutLoud.command** below, then double-click it. | |
| The first time, macOS may say it is from an unidentified | |
| developer. Right-click the file and choose **Open**, then | |
| **Open** again. | |
| Apple Silicon, macOS 26 or newer. | |
| <details><summary>Terminal alternative</summary> | |
| ``` | |
| curl -fsSL https://raw.githubusercontent.com/blarer/outloud/main/scripts/install.sh | bash | |
| ``` | |
| macOS 26 may block pasting this; the download above avoids that. | |
| </details> | |
| files: | | |
| artifacts/**/* |