release: v0.3.9 — ship the linter in the release archives #57
Workflow file for this run
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
| name: release | |
| # Builds a single self-contained binary per OS and attaches the artifacts | |
| # to a GitHub Release. The backend embeds the web/dist/ bundle via | |
| # rust-embed, so each artifact is a true single-file deliverable. | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'release tag (e.g. v0.1.0)' | |
| required: true | |
| jobs: | |
| web: | |
| name: build web bundle | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: { node-version: '22' } | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: { targets: wasm32-unknown-unknown } | |
| - name: install wasm-pack | |
| run: curl -sSf https://rustwasm.github.io/wasm-pack/installer/init.sh | sh | |
| - name: build wasm analyzer | |
| run: wasm-pack build crates/analyzer-wasm --target web --out-dir ../../web/src/wasm --release | |
| - name: install web deps | |
| working-directory: web | |
| run: npm ci | |
| - name: build web | |
| working-directory: web | |
| run: npm run build | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: web-dist | |
| path: web/dist/ | |
| retention-days: 1 | |
| binary: | |
| name: ${{ matrix.label }} | |
| needs: web | |
| runs-on: ${{ matrix.os }} | |
| # Job-level env so the signing gates are visible to step `if:` conditions. | |
| # (A step's own `env:` block is NOT in scope for that step's `if:` — GitHub | |
| # evaluates `if` before applying step env — so these MUST live at job level.) | |
| env: | |
| WINDOWS_CERT_BASE64: ${{ secrets.WINDOWS_CERT_BASE64 }} | |
| APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Build on 22.04 (glibc 2.35), NOT ubuntu-latest (24.04 = glibc 2.39) — | |
| # a 2.39 binary refuses to load on most current LTS servers. 2.35 covers | |
| # Ubuntu 22.04+, Debian 12, etc. (A static musl build for universal Linux | |
| # reach is the planned fast-follow.) | |
| - { label: linux-x86_64, os: ubuntu-22.04, target: x86_64-unknown-linux-gnu, archive: tar.gz, binsuffix: '' } | |
| - { label: macos-arm64, os: macos-latest, target: aarch64-apple-darwin, archive: tar.gz, binsuffix: '' } | |
| - { label: windows-x86_64, os: windows-latest, target: x86_64-pc-windows-msvc, archive: zip, binsuffix: '.exe' } | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.target }} | |
| - name: fetch web bundle | |
| uses: actions/download-artifact@v4 | |
| with: { name: web-dist, path: web/dist } | |
| - name: build backend | |
| # Both shipped binaries. Only the backend was built here, so the | |
| # release archives contained no linter at all — `dbopt lint` on a | |
| # downloaded build started the web server instead, while the README | |
| # documents `dbopt lint` as the CI entry point. | |
| run: cargo build --release --target ${{ matrix.target }} -p backend -p dbopt | |
| - name: stage artifact | |
| shell: bash | |
| run: | | |
| mkdir -p staging | |
| # Names match the documentation and `cargo install dbopt`: | |
| # dbopt -> the CLI / linter | |
| # dbopt-backend -> the local app (API + embedded UI on :3690) | |
| cp target/${{ matrix.target }}/release/dbopt${{ matrix.binsuffix }} staging/dbopt${{ matrix.binsuffix }} | |
| cp target/${{ matrix.target }}/release/dbopt-backend${{ matrix.binsuffix }} staging/dbopt-backend${{ matrix.binsuffix }} | |
| cp README.md staging/ 2>/dev/null || true | |
| cp samples/bad.sql staging/sample.sql 2>/dev/null || true | |
| { | |
| echo "dbopt the linter. Analyses .sql files offline, no database needed:" | |
| echo " dbopt lint ./db --format sarif --fail-on warning" | |
| echo " Run 'dbopt --help' for everything else." | |
| echo "" | |
| echo "dbopt-backend the local app. Run it, then open http://127.0.0.1:3690 for the" | |
| echo " UI, live server metrics and the AI assistant." | |
| echo " Set DBOPT_NO_OPEN=1 to stop it opening a browser." | |
| } > staging/WHICH-BINARY.txt | |
| - name: archive | |
| shell: bash | |
| run: | | |
| cd staging | |
| if [ "${{ matrix.archive }}" = "zip" ]; then | |
| 7z a ../dbopt-${{ matrix.label }}.zip ./* | |
| else | |
| tar czf ../dbopt-${{ matrix.label }}.tar.gz ./* | |
| fi | |
| - name: macos dmg | |
| if: startsWith(matrix.label, 'macos-') | |
| run: | | |
| VER="${{ github.event.inputs.tag || github.ref_name }}" | |
| VER="${VER#v}" # strip leading v from e.g. v0.2.0 | |
| VER="${VER:-0.0.0}" # fall back if somehow empty | |
| mkdir -p dbopt-${{ matrix.label }}-app/dbopt.app/Contents/MacOS | |
| mkdir -p dbopt-${{ matrix.label }}-app/dbopt.app/Contents/Resources | |
| cp staging/dbopt dbopt-${{ matrix.label }}-app/dbopt.app/Contents/MacOS/dbopt | |
| chmod +x dbopt-${{ matrix.label }}-app/dbopt.app/Contents/MacOS/dbopt | |
| # Brand icon for the dock / Finder (CFBundleIconFile -> Resources/dbopt.icns). | |
| cp assets/dbopt.icns dbopt-${{ matrix.label }}-app/dbopt.app/Contents/Resources/dbopt.icns | |
| cat > dbopt-${{ matrix.label }}-app/dbopt.app/Contents/Info.plist <<EOF | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"><dict> | |
| <key>CFBundleExecutable</key><string>dbopt</string> | |
| <key>CFBundleIconFile</key><string>dbopt</string> | |
| <key>CFBundleIdentifier</key><string>dev.dbopt.observatory</string> | |
| <key>CFBundleName</key><string>dbopt</string> | |
| <key>CFBundlePackageType</key><string>APPL</string> | |
| <key>CFBundleShortVersionString</key><string>${VER}</string> | |
| <key>CFBundleVersion</key><string>1</string> | |
| <key>NSHighResolutionCapable</key><true/> | |
| </dict></plist> | |
| EOF | |
| # Make it a real drag-to-install image: a visible /Applications alias | |
| # next to the app so the user can drag dbopt.app onto Applications | |
| # (instead of running it from the read-only mounted volume). | |
| ln -s /Applications "dbopt-${{ matrix.label }}-app/Applications" | |
| hdiutil create -volname dbopt -srcfolder dbopt-${{ matrix.label }}-app -ov -format UDZO dbopt-${{ matrix.label }}.dmg | |
| # Apple codesign + notarize + staple for the .dmg. Runs ONLY when the Apple | |
| # Developer secrets are present, and is SKIPPED (job still succeeds) when | |
| # absent — so an unconfigured repo still ships a working (unsigned) .dmg. | |
| # Notarizing + stapling removes the Gatekeeper "unidentified developer" | |
| # first-run wall on end-user Macs. | |
| # | |
| # Required repo secrets to activate (all must be set): | |
| # APPLE_CERT_BASE64 - base64 of the "Developer ID Application" .p12 | |
| # APPLE_CERT_PASSWORD - password for that .p12 | |
| # APPLE_SIGN_IDENTITY - e.g. "Developer ID Application: Acme (TEAMID)" | |
| # APPLE_NOTARY_APPLE_ID - the Apple ID email for notarytool | |
| # APPLE_NOTARY_PASSWORD - an app-specific password for that Apple ID | |
| # APPLE_TEAM_ID - the 10-char Apple Developer Team ID | |
| - name: macos codesign + notarize + staple | |
| if: ${{ startsWith(matrix.label, 'macos-') && env.APPLE_CERT_BASE64 != '' }} | |
| env: | |
| APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }} | |
| APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} | |
| APPLE_SIGN_IDENTITY: ${{ secrets.APPLE_SIGN_IDENTITY }} | |
| APPLE_NOTARY_APPLE_ID: ${{ secrets.APPLE_NOTARY_APPLE_ID }} | |
| APPLE_NOTARY_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| set -euo pipefail | |
| # Import the signing identity into a throwaway keychain (never logged). | |
| KEYCHAIN="$RUNNER_TEMP/codesign.keychain-db" | |
| KEYCHAIN_PW="$(openssl rand -base64 24)" | |
| CERT="$RUNNER_TEMP/cert.p12" | |
| echo "$APPLE_CERT_BASE64" | base64 --decode > "$CERT" | |
| security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN" | |
| security import "$CERT" -k "$KEYCHAIN" -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PW" "$KEYCHAIN" >/dev/null | |
| security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"') | |
| APP="dbopt-${{ matrix.label }}-app/dbopt.app" | |
| # Sign the binary then the .app bundle (hardened runtime is required for notarization). | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$APPLE_SIGN_IDENTITY" "$APP/Contents/MacOS/dbopt" | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$APPLE_SIGN_IDENTITY" "$APP" | |
| codesign --verify --strict --verbose=2 "$APP" | |
| # Rebuild the .dmg now that the .app inside it is signed. | |
| rm -f "dbopt-${{ matrix.label }}.dmg" | |
| hdiutil create -volname dbopt -srcfolder "dbopt-${{ matrix.label }}-app" -ov -format UDZO "dbopt-${{ matrix.label }}.dmg" | |
| codesign --force --timestamp --sign "$APPLE_SIGN_IDENTITY" "dbopt-${{ matrix.label }}.dmg" | |
| # Submit to Apple's notary service and wait, then staple the ticket so | |
| # the .dmg validates offline on the end user's machine. | |
| xcrun notarytool submit "dbopt-${{ matrix.label }}.dmg" \ | |
| --apple-id "$APPLE_NOTARY_APPLE_ID" \ | |
| --password "$APPLE_NOTARY_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait | |
| xcrun stapler staple "dbopt-${{ matrix.label }}.dmg" | |
| xcrun stapler validate "dbopt-${{ matrix.label }}.dmg" | |
| # Also refresh the .tar.gz so the bare binary inside it is signed too. | |
| rm -f "dbopt-${{ matrix.label }}.tar.gz" | |
| cp "$APP/Contents/MacOS/dbopt" staging/dbopt-backend | |
| (cd staging && tar czf "../dbopt-${{ matrix.label }}.tar.gz" ./*) | |
| security delete-keychain "$KEYCHAIN" || true | |
| echo "Notarized + stapled: dbopt-${{ matrix.label }}.dmg" | |
| - name: windows MSI | |
| if: matrix.label == 'windows-x86_64' | |
| # NO continue-on-error: a broken installer must NOT ship "green". cargo-wix | |
| # failing, or finishing without producing a .msi, fails the whole job here | |
| # (the downstream `release` guard only catches a *missing* file — it cannot | |
| # see a cargo-wix non-zero exit that still uploaded nothing). | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| # WiX v3 (candle/light) ships on windows-latest; ensure it's on PATH | |
| # (install only if a future image drops it). | |
| $wix = Get-ChildItem "C:\Program Files (x86)" -Filter "WiX Toolset v*" -Directory | | |
| Sort-Object Name -Descending | Select-Object -First 1 | |
| if (-not $wix) { | |
| choco install wixtoolset -y --no-progress | |
| $wix = Get-ChildItem "C:\Program Files (x86)" -Filter "WiX Toolset v*" -Directory | | |
| Sort-Object Name -Descending | Select-Object -First 1 | |
| } | |
| if ($wix) { $env:PATH = "$($wix.FullName)\bin;$env:PATH"; Write-Host "WiX bin: $($wix.FullName)\bin" } | |
| else { throw "WiX Toolset not found and could not be installed" } | |
| cargo install cargo-wix --locked | |
| # Build from the committed wix/ manifest (stable UpgradeCode). cargo-wix | |
| # needs -p in a workspace, and `light` resolves the relative | |
| # 'wix\License.rtf' from the invocation CWD — so do both: cd into the | |
| # crate AND pass -p backend. | |
| Push-Location crates/backend | |
| cargo wix -p backend --no-build --target ${{ matrix.target }} --nocapture --output "$env:GITHUB_WORKSPACE\dbopt-windows-x86_64.msi" | |
| $exit = $LASTEXITCODE | |
| Pop-Location | |
| if ($exit -ne 0) { throw "cargo wix exited with code $exit" } | |
| if (Test-Path dbopt-windows-x86_64.msi) { | |
| $size = (Get-Item dbopt-windows-x86_64.msi).Length | |
| Write-Host "MSI built: $size bytes" | |
| # A zero/near-zero MSI is a broken installer; treat it as a hard failure. | |
| if ($size -lt 1024) { throw "MSI is implausibly small ($size bytes) — treating as broken" } | |
| } else { | |
| throw "MSI step finished but no .msi was produced" | |
| } | |
| # Authenticode code-signing for the .exe and .msi. Runs ONLY when the | |
| # signing secret is present, and is SKIPPED (job still succeeds) when it is | |
| # absent — so forks / unconfigured repos still produce working unsigned | |
| # artifacts. This removes the unsigned first-run warning wall for end users. | |
| # | |
| # Required repo secrets to activate (all must be set): | |
| # WINDOWS_CERT_BASE64 - base64 of the code-signing .pfx file | |
| # WINDOWS_CERT_PASSWORD - password for that .pfx | |
| # (Optional) WINDOWS_CERT_TS_URL - RFC-3161 timestamp server; defaults below. | |
| - name: windows code-sign (.exe + .msi) | |
| if: ${{ matrix.label == 'windows-x86_64' && env.WINDOWS_CERT_BASE64 != '' }} | |
| env: | |
| WINDOWS_CERT_BASE64: ${{ secrets.WINDOWS_CERT_BASE64 }} | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| WINDOWS_CERT_TS_URL: ${{ secrets.WINDOWS_CERT_TS_URL }} | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| # Materialize the .pfx from the secret into a temp file (never logged). | |
| $pfx = Join-Path $env:RUNNER_TEMP "codesign.pfx" | |
| [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:WINDOWS_CERT_BASE64)) | |
| $ts = if ($env:WINDOWS_CERT_TS_URL) { $env:WINDOWS_CERT_TS_URL } else { "http://timestamp.digicert.com" } | |
| # Locate signtool from the installed Windows SDK. | |
| $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -like "*x64*" } | | |
| Sort-Object FullName -Descending | Select-Object -First 1 | |
| if (-not $signtool) { throw "signtool.exe not found in the Windows SDK" } | |
| $targets = @() | |
| if (Test-Path "staging\dbopt.exe") { $targets += "staging\dbopt.exe" } | |
| if (Test-Path "dbopt-windows-x86_64.msi") { $targets += "dbopt-windows-x86_64.msi" } | |
| foreach ($t in $targets) { | |
| & $signtool.FullName sign /fd SHA256 /tr $ts /td SHA256 ` | |
| /f $pfx /p $env:WINDOWS_CERT_PASSWORD "$t" | |
| if ($LASTEXITCODE -ne 0) { throw "signtool failed for $t (exit $LASTEXITCODE)" } | |
| & $signtool.FullName verify /pa "$t" | |
| if ($LASTEXITCODE -ne 0) { throw "signature verify failed for $t (exit $LASTEXITCODE)" } | |
| } | |
| Remove-Item $pfx -Force | |
| # Re-archive the signed .exe into the .zip so the zip download is signed too. | |
| if (Test-Path "staging\dbopt.exe") { | |
| Remove-Item "dbopt-windows-x86_64.zip" -ErrorAction SilentlyContinue | |
| Push-Location staging | |
| 7z a "..\dbopt-windows-x86_64.zip" .\* | |
| Pop-Location | |
| } | |
| Write-Host "Signed Windows artifacts: $($targets -join ', ')" | |
| - name: linux AppImage (best-effort) | |
| if: matrix.label == 'linux-x86_64' | |
| run: | | |
| mkdir -p dbopt.AppDir/usr/bin | |
| cp staging/dbopt dbopt.AppDir/usr/bin/dbopt | |
| chmod +x dbopt.AppDir/usr/bin/dbopt | |
| cat > dbopt.AppDir/dbopt.desktop <<EOF | |
| [Desktop Entry] | |
| Type=Application | |
| Name=dbopt | |
| Exec=dbopt | |
| Icon=dbopt | |
| Categories=Development;Database; | |
| Terminal=true | |
| EOF | |
| # Real brand icon (was an empty `touch`ed file -> blank AppImage icon). | |
| cp assets/dbopt-256.png dbopt.AppDir/dbopt.png | |
| cat > dbopt.AppDir/AppRun <<'EOF' | |
| #!/bin/sh | |
| HERE="$(dirname "$(readlink -f "${0}")")" | |
| exec "${HERE}/usr/bin/dbopt" "$@" | |
| EOF | |
| chmod +x dbopt.AppDir/AppRun | |
| curl -L -o appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage || true | |
| chmod +x appimagetool || true | |
| ARCH=x86_64 ./appimagetool dbopt.AppDir dbopt-linux-x86_64.AppImage || \ | |
| echo "AppImage build skipped (appimagetool unavailable)" | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dbopt-${{ matrix.label }} | |
| path: | | |
| dbopt-${{ matrix.label }}.tar.gz | |
| dbopt-${{ matrix.label }}.zip | |
| dbopt-${{ matrix.label }}.dmg | |
| dbopt-windows-x86_64.msi | |
| dbopt-linux-x86_64.AppImage | |
| if-no-files-found: ignore | |
| # A fully-static musl binary that runs on ANY x86_64 Linux (Alpine, RHEL 8, | |
| # ancient glibc, no glibc at all) — the universal fallback alongside the | |
| # glibc-2.34 tar.gz. Built with cargo-zigbuild (zig cross-compiles the C deps: | |
| # aws-lc-sys, rusqlite, ring). continue-on-error + NOT a required asset, so a | |
| # musl/zig hiccup can never block the main release. | |
| linux-musl: | |
| name: linux-x86_64-musl (static) | |
| needs: web | |
| runs-on: ubuntu-22.04 | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: x86_64-unknown-linux-musl | |
| - uses: Swatinem/rust-cache@v2 | |
| with: { key: linux-musl } | |
| - name: fetch web bundle | |
| uses: actions/download-artifact@v4 | |
| with: { name: web-dist, path: web/dist } | |
| - name: install zig + cargo-zigbuild | |
| run: | | |
| curl -fsSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz -o /tmp/zig.tar.xz | |
| tar -xf /tmp/zig.tar.xz -C /tmp | |
| echo "/tmp/zig-linux-x86_64-0.13.0" >> "$GITHUB_PATH" | |
| cargo install --locked cargo-zigbuild | |
| - name: build static musl binary | |
| run: cargo zigbuild --release --target x86_64-unknown-linux-musl -p backend -p dbopt | |
| - name: archive | |
| run: | | |
| mkdir -p staging | |
| cp target/x86_64-unknown-linux-musl/release/dbopt staging/dbopt | |
| cp target/x86_64-unknown-linux-musl/release/dbopt-backend staging/dbopt-backend | |
| cp README.md staging/ 2>/dev/null || true | |
| cp samples/bad.sql staging/sample.sql 2>/dev/null || true | |
| (cd staging && tar czf ../dbopt-linux-x86_64-musl.tar.gz ./*) | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dbopt-linux-x86_64-musl | |
| path: dbopt-linux-x86_64-musl.tar.gz | |
| if-no-files-found: ignore | |
| release: | |
| name: publish release | |
| needs: [binary, linux-musl] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| # Build provenance is signed with a short-lived certificate minted from | |
| # this workflow's OIDC identity, so it needs id-token + attestations. | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: { path: artifacts } | |
| - name: stage flat | |
| run: | | |
| mkdir -p out | |
| find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.dmg' -o -name '*.msi' -o -name '*.AppImage' \) -exec cp {} out/ \; | |
| (cd out && sha256sum * > SHA256SUMS) | |
| ls -la out | |
| # Guard against a SILENTLY incomplete release. The MSI step itself now | |
| # fails the binary job hard (no continue-on-error) so a broken installer | |
| # can't ship green — but this belt-and-suspenders check also fails the | |
| # publish if any required asset is missing (e.g. the best-effort | |
| # AppImage masks failures, and an upload glitch could still drop a file), | |
| # so the website's download link can never 404. (AppImage is NOT required.) | |
| missing="" | |
| for f in dbopt-linux-x86_64.tar.gz dbopt-macos-arm64.dmg dbopt-macos-arm64.tar.gz dbopt-windows-x86_64.msi dbopt-windows-x86_64.zip; do | |
| [ -f "out/$f" ] || missing="$missing $f" | |
| done | |
| if [ -n "$missing" ]; then | |
| echo "::error::release is missing required assets:$missing" | |
| exit 1 | |
| fi | |
| echo "All required release assets present." | |
| # Cryptographic build provenance for every published artifact. | |
| # | |
| # Authenticode and Apple notarization above answer "who signed this?" and | |
| # both need a paid certificate tied to a legal identity, so they are gated | |
| # on secrets and skipped when unconfigured. Provenance answers a different | |
| # and, for an open-source tool, more useful question: "was this exact file | |
| # built by this workflow, from this commit, without anyone touching it in | |
| # between?" It is keyless — signed against the workflow's OIDC identity | |
| # and recorded in a public transparency log — so it works with no secrets | |
| # at all, and cannot be forged by someone who merely uploads a file to the | |
| # release page. | |
| # | |
| # Anyone can check a download before running it: | |
| # gh attestation verify dbopt-linux-x86_64.tar.gz --repo singhpratech/dbopt | |
| - name: attest build provenance | |
| uses: actions/attest-build-provenance@v1 | |
| with: | |
| subject-path: 'out/*' | |
| - name: gh release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.event.inputs.tag || github.ref_name }} | |
| files: out/* | |
| generate_release_notes: true |