diff --git a/.changeset/bumpy-goats-hide.md b/.changeset/bumpy-goats-hide.md new file mode 100644 index 00000000..6b113682 --- /dev/null +++ b/.changeset/bumpy-goats-hide.md @@ -0,0 +1,5 @@ +--- +"kiji-privacy-proxy": patch +--- + +Updated release workflow, deb package and homebrew diff --git a/.github/actions/extract-tokenizers-version/action.yml b/.github/actions/extract-tokenizers-version/action.yml new file mode 100644 index 00000000..042970e3 --- /dev/null +++ b/.github/actions/extract-tokenizers-version/action.yml @@ -0,0 +1,21 @@ +name: Extract tokenizers version +description: > + Read the github.com/daulet/tokenizers version from go.mod (used to key the + prebuilt tokenizers library cache). Requires the repository to be checked out. +outputs: + version: + description: The tokenizers module version, without a leading "v". + value: ${{ steps.deps.outputs.version }} +runs: + using: composite + steps: + - id: deps + shell: bash + run: | + set -euo pipefail + tokenizers="$(awk '/github.com\/daulet\/tokenizers/ { sub(/^v/, "", $2); print $2; exit }' go.mod)" + if [ -z "$tokenizers" ]; then + echo "Failed to determine tokenizers version from go.mod" >&2 + exit 1 + fi + echo "version=$tokenizers" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/extract-version/action.yml b/.github/actions/extract-version/action.yml new file mode 100644 index 00000000..71ff5344 --- /dev/null +++ b/.github/actions/extract-version/action.yml @@ -0,0 +1,24 @@ +name: Extract Version +description: > + Resolve the release version from the pushed git tag (refs/tags/vX.Y.Z) or, + for non-tag builds, from src/frontend/package.json. Requires the repository + to be checked out first. +outputs: + version: + description: The resolved version, without a leading "v". + value: ${{ steps.extract.outputs.version }} +runs: + using: composite + steps: + - id: extract + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + # Extract version from tag (v1.2.3 -> 1.2.3) + VERSION=${GITHUB_REF#refs/tags/v} + else + # For non-tag builds, extract version from package.json + VERSION=$(cat src/frontend/package.json | grep '"version"' | head -1 | awk -F '"' '{print $4}') + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Resolved version: $VERSION" diff --git a/.github/actions/verify-lfs-model/action.yml b/.github/actions/verify-lfs-model/action.yml new file mode 100644 index 00000000..c8540330 --- /dev/null +++ b/.github/actions/verify-lfs-model/action.yml @@ -0,0 +1,21 @@ +name: Verify Git LFS Model +description: > + Pull Git LFS objects and verify the ONNX model file is materialized rather + than left as an LFS pointer. Requires the repository to be checked out first. +runs: + using: composite + steps: + - shell: bash + run: | + set -euo pipefail + git lfs pull + # Verify model file is not an LFS pointer. Use GNU stat (Linux) with a + # fallback to BSD stat (macOS) so this works on every build runner. + MODEL_SIZE=$(stat -c%s "model/quantized/model.onnx" 2>/dev/null \ + || stat -f%z "model/quantized/model.onnx" 2>/dev/null \ + || echo "0") + echo "Model file size: ${MODEL_SIZE} bytes" + if [ "$MODEL_SIZE" -lt 1000 ]; then + echo "❌ Model file appears to be an LFS pointer (too small)" + exit 1 + fi diff --git a/.github/workflows/_build-chrome.yml b/.github/workflows/_build-chrome.yml new file mode 100644 index 00000000..b4e49c86 --- /dev/null +++ b/.github/workflows/_build-chrome.yml @@ -0,0 +1,68 @@ +# Reusable workflow: package the Chrome extension. Called by release.yml. +name: Package Chrome Extension (reusable) + +on: + workflow_call: + +jobs: + build-chrome: + name: Package Chrome Extension + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Extract Version + id: version + uses: ./.github/actions/extract-version + + - name: Stamp Version in Manifest + run: | + cd chrome-extension + # Update manifest.json version to match the release tag + jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' manifest.json > manifest.tmp + mv manifest.tmp manifest.json + echo "Manifest version set to: ${{ steps.version.outputs.version }}" + cat manifest.json | jq .version + + - name: Package Extension + run: | + cd chrome-extension + zip -r ../kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip . \ + -x '*.DS_Store' '*.git*' + echo "Package contents:" + unzip -l ../kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip + + - name: Generate SHA256 Checksum + run: | + sha256sum kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip \ + > kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip.sha256 + cat kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip.sha256 + + - name: Upload as Artifact + uses: actions/upload-artifact@v7 + with: + name: chrome-assets + path: | + kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip + kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip.sha256 + retention-days: 90 + + - name: Build Summary + if: always() + run: | + echo "## Chrome Extension Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Package:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + ls -lh kiji-privacy-proxy-extension-*.zip 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No zip found" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**SHA256:**" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat kiji-privacy-proxy-extension-*.sha256 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "No checksum found" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/_build-dmg.yml b/.github/workflows/_build-dmg.yml new file mode 100644 index 00000000..c7faa79e --- /dev/null +++ b/.github/workflows/_build-dmg.yml @@ -0,0 +1,206 @@ +# Reusable workflow: macOS DMG build (Apple Silicon + Intel universal). +# Called by release.yml. Trigger gating lives in the caller's `gate` job. +name: Build DMG (reusable) + +on: + workflow_call: + +jobs: + build-dmg: + name: Build DMG for Release + runs-on: macos-latest + environment: "DMG Build Environment" + + permissions: + contents: read + + steps: + - name: Verify signing secrets are available + run: | + MISSING="" + [ -z "$CSC_LINK" ] && MISSING="$MISSING CSC_LINK" + [ -z "$CSC_KEY_PASSWORD" ] && MISSING="$MISSING CSC_KEY_PASSWORD" + [ -z "$APPLE_API_KEY" ] && MISSING="$MISSING APPLE_API_KEY" + [ -z "$APPLE_API_KEY_ID" ] && MISSING="$MISSING APPLE_API_KEY_ID" + [ -z "$APPLE_API_ISSUER" ] && MISSING="$MISSING APPLE_API_ISSUER" + if [ -n "$MISSING" ]; then + echo "::error::Missing required environment secrets:$MISSING" + echo "Ensure the 'DMG Build Environment' environment is configured with all required secrets." + exit 1 + fi + echo "All signing and notarization secrets are set." + env: + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + + - name: Checkout Repository + uses: actions/checkout@v6 + with: + lfs: true + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: false + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + enable-cache: true + + - name: Setup Rust + uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable + with: + toolchain: stable + + - name: Extract dependency versions + id: deps + uses: ./.github/actions/extract-tokenizers-version + + - name: Cache LFS objects + uses: actions/cache@v5 + with: + path: .git/lfs + key: lfs-${{ runner.os }}-${{ hashFiles('.gitattributes') }} + restore-keys: | + lfs-${{ runner.os }}- + + - name: Cache tokenizers library + uses: actions/cache@v5 + with: + path: build/tokenizers/libtokenizers.a + key: ${{ runner.os }}-tokenizers-v${{ steps.deps.outputs.version }}-prebuilt-v1 + + - name: Cache ONNX Runtime library + uses: actions/cache@v5 + with: + path: build/libonnxruntime.*.dylib + key: ${{ runner.os }}-onnx-1.24.2 + restore-keys: | + ${{ runner.os }}-onnx- + + - name: Extract Version + id: version + uses: ./.github/actions/extract-version + + - name: Verify Git LFS Files + uses: ./.github/actions/verify-lfs-model + + - name: Install Python Dependencies + run: | + uv venv --python 3.13 + uv pip install onnxruntime==1.24.2 + + - name: Install Root Dependencies + run: npm ci + + - name: Install Frontend Dependencies + run: | + cd src/frontend + npm ci + + - name: Write Apple API key to file + run: | + mkdir -p "$RUNNER_TEMP/apple" + echo "$APPLE_API_KEY" > "$RUNNER_TEMP/apple/AuthKey.p8" + chmod 600 "$RUNNER_TEMP/apple/AuthKey.p8" + env: + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + + - name: Build DMG + # Invoke the script directly (no skip-sign arg) so signing + notarization run. + # `make build-dmg` passes `skip-sign` for local dev builds. + run: | + chmod +x src/scripts/build_dmg.sh + ./src/scripts/build_dmg.sh + env: + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + APPLE_API_KEY: ${{ runner.temp }}/apple/AuthKey.p8 + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + # Force signing on `pull_request` event runs (notably the merged + # release-PR path: this workflow's trigger includes merged PRs with + # the `release` label, and on those runs GITHUB_BASE_REF is set so + # electron-builder treats it as a PR build and silently skips + # signing — producing a half-signed bundle that fails + # `codesign --verify` with "code has no resources but signature + # indicates they must be present". Safe here: the job uses the + # protected "DMG Build Environment", gated on maintainer approval + # before secrets are exposed. + CSC_FOR_PULL_REQUEST: "true" + + - name: Normalize macOS Artifact Names + run: | + cd src/frontend/release + for artifact in *.dmg *-mac.zip; do + if [ -f "$artifact" ]; then + # Match the artifact names generated in latest-mac.yml. + # Example: "Kiji Privacy Proxy-0.2.0-arm64-mac.zip" -> "Kiji-Privacy-Proxy-0.2.0-arm64-mac.zip" + new_name=$(echo "$artifact" | tr ' ' '-') + if [ "$artifact" != "$new_name" ]; then + mv "$artifact" "$new_name" + echo "Renamed: $artifact -> $new_name" + else + echo "Already clean: $artifact" + fi + fi + done + + - name: Generate DMG SHA256 Checksums + working-directory: src/frontend/release + run: | + set -euo pipefail + shopt -s nullglob + dmgs=(*.dmg) + if [ ${#dmgs[@]} -eq 0 ]; then + echo "::error::No DMG files found in src/frontend/release" + exit 1 + fi + for dmg in "${dmgs[@]}"; do + shasum -a 256 "$dmg" > "${dmg}.sha256" + echo "Generated checksum:" + cat "${dmg}.sha256" + done + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v7 + with: + name: dmg-assets + path: | + src/frontend/release/*.dmg + src/frontend/release/*.dmg.sha256 + src/frontend/release/*.zip + src/frontend/release/latest-mac.yml + retention-days: 90 + + - name: Build Summary + if: always() + run: | + echo "## 📦 macOS Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**DMG Files:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + ls -lh src/frontend/release/*.dmg 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No DMG files found" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**SHA256 Checksums:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat src/frontend/release/*.dmg.sha256 2>/dev/null | awk '{print "- `" $1 "` " $2}' >> $GITHUB_STEP_SUMMARY || echo "No checksums found" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/_build-linux.yml b/.github/workflows/_build-linux.yml new file mode 100644 index 00000000..acffdba1 --- /dev/null +++ b/.github/workflows/_build-linux.yml @@ -0,0 +1,148 @@ +# Reusable workflow: Linux binary build (amd64), built inside an almalinux:9 +# container for a low GLIBC floor. Called by release.yml. +name: Build Linux Binary (reusable) + +on: + workflow_call: + +jobs: + build-linux: + name: Build Linux Binary + runs-on: ubuntu-latest + container: + image: almalinux:9 + + permissions: + contents: read + + steps: + - name: Install system packages + run: | + set -euo pipefail + dnf -y install \ + bash \ + binutils \ + file \ + findutils \ + gcc \ + gcc-c++ \ + git \ + git-lfs \ + gzip \ + jq \ + make \ + nodejs \ + npm \ + tar \ + unzip \ + wget \ + which \ + zip + + - name: Checkout Repository + uses: actions/checkout@v6 + with: + lfs: true + fetch-depth: 0 + + - name: Mark Repository as Safe for Git + run: | + set -euo pipefail + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git config --global --get-all safe.directory + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Setup Rust + uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable + with: + toolchain: stable + + - name: Cache LFS objects + uses: actions/cache@v5 + with: + path: .git/lfs + key: lfs-${{ runner.os }}-almalinux9-${{ hashFiles('.gitattributes') }} + restore-keys: | + lfs-${{ runner.os }}-almalinux9- + + - name: Extract dependency versions + id: deps + uses: ./.github/actions/extract-tokenizers-version + + - name: Cache Go modules + uses: actions/cache@v5 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-almalinux9-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-almalinux9-go- + + - name: Cache tokenizers library + uses: actions/cache@v5 + with: + path: build/tokenizers/libtokenizers.a + key: ${{ runner.os }}-almalinux9-tokenizers-v${{ steps.deps.outputs.version }}-prebuilt-v1 + + - name: Cache ONNX Runtime library + uses: actions/cache@v5 + with: + path: | + build/libonnxruntime.so.1.24.2 + build/libonnxruntime.so + key: ${{ runner.os }}-almalinux9-onnx-1.24.2 + + - name: Extract Version + id: version + uses: ./.github/actions/extract-version + + - name: Verify Git LFS Files + uses: ./.github/actions/verify-lfs-model + + - name: Build Linux Binary + run: | + chmod +x src/scripts/build_linux.sh + ./src/scripts/build_linux.sh + + - name: Report GLIBC requirements + run: | + chmod +x src/scripts/report_glibc_requirements.sh + GLIBC_REPORT="$( + ./src/scripts/report_glibc_requirements.sh \ + build/kiji-proxy \ + build/libonnxruntime.so.1.24.2 + )" + echo "$GLIBC_REPORT" + + GLIBC_REQUIREMENT="$(printf '%s\n' "$GLIBC_REPORT" | awk -F': ' '/^Release minimum required GLIBC:/ {print $2}' | tail -n 1)" + if [ -n "$GLIBC_REQUIREMENT" ]; then + echo "**Minimum required GLIBC:** $GLIBC_REQUIREMENT" >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload Linux Archive as Artifact + uses: actions/upload-artifact@v7 + with: + name: linux-assets + path: | + release/linux/*.tar.gz + release/linux/*.tar.gz.sha256 + retention-days: 90 + + - name: Build Summary + if: always() + run: | + echo "## 📦 Linux Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Archive Files:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + ls -lh release/linux/*.tar.gz 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No archive files found" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**SHA256 Checksums:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat release/linux/*.sha256 2>/dev/null | awk '{print "- `" $1 "`"}' >> $GITHUB_STEP_SUMMARY || echo "No checksums found" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/_package-deb.yml b/.github/workflows/_package-deb.yml new file mode 100644 index 00000000..b0acfa9e --- /dev/null +++ b/.github/workflows/_package-deb.yml @@ -0,0 +1,58 @@ +# Reusable workflow: package the Linux binary as a .deb (Debian-native tooling, +# so this runs on ubuntu-latest rather than the almalinux build container). +# Consumes the linux-assets artifact produced by _build-linux.yml. +name: Package Linux .deb (reusable) + +on: + workflow_call: + +jobs: + package-linux-deb: + name: Package Linux .deb + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Download Linux binary artifact + uses: actions/download-artifact@v8 + with: + name: linux-assets + path: release/linux + + - name: Install deb packaging tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential debhelper devscripts fakeroot dpkg-dev + + - name: Build .deb package + run: | + chmod +x src/scripts/build_deb.sh + ./src/scripts/build_deb.sh + + - name: Upload .deb as Artifact + uses: actions/upload-artifact@v7 + with: + name: linux-deb + path: | + release/linux/*.deb + release/linux/*.deb.sha256 + retention-days: 90 + + - name: Build Summary + if: always() + run: | + echo "## 📦 Linux .deb Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Deb Packages:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + ls -lh release/linux/*.deb 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No deb packages found" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**SHA256 Checksums:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat release/linux/*.deb.sha256 2>/dev/null | awk '{print "- `" $1 "`"}' >> $GITHUB_STEP_SUMMARY || echo "No checksums found" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c556c2cf..79e8d2d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,557 +17,79 @@ on: jobs: # ────────────────────────────────────────────────────────────────────── - # macOS DMG build (Apple Silicon + Intel universal) + # Single source of truth for "what should this run do?". Every other job + # keys off these two outputs instead of repeating the trigger matrix. + # + # should_build - produce the platform artifacts (DMG, Linux, .deb, Chrome) + # should_release - tag, publish the GitHub Release, and update the tap + # + # The distinction matters for manual runs: a plain `workflow_dispatch` builds + # artifacts for testing but does NOT release unless create_release=true. # ────────────────────────────────────────────────────────────────────── - build-dmg: - name: Build DMG for Release - if: | - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')) - - runs-on: macos-latest - environment: "DMG Build Environment" - - permissions: - contents: read - + gate: + name: Resolve trigger + runs-on: ubuntu-latest + outputs: + should_build: ${{ steps.decide.outputs.build }} + should_release: ${{ steps.decide.outputs.release }} steps: - - name: Verify signing secrets are available - run: | - MISSING="" - [ -z "$CSC_LINK" ] && MISSING="$MISSING CSC_LINK" - [ -z "$CSC_KEY_PASSWORD" ] && MISSING="$MISSING CSC_KEY_PASSWORD" - [ -z "$APPLE_API_KEY" ] && MISSING="$MISSING APPLE_API_KEY" - [ -z "$APPLE_API_KEY_ID" ] && MISSING="$MISSING APPLE_API_KEY_ID" - [ -z "$APPLE_API_ISSUER" ] && MISSING="$MISSING APPLE_API_ISSUER" - if [ -n "$MISSING" ]; then - echo "::error::Missing required environment secrets:$MISSING" - echo "Ensure the 'DMG Build Environment' environment is configured with all required secrets." - exit 1 - fi - echo "All signing and notarization secrets are set." + - id: decide env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - - - name: Checkout Repository - uses: actions/checkout@v6 - with: - lfs: true - fetch-depth: 0 - - - name: Setup Go - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - cache: false - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: package-lock.json - - - name: Install uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - with: - enable-cache: true - - - name: Setup Rust - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - with: - toolchain: stable - - - name: Extract dependency versions - id: deps - run: | - set -euo pipefail - tokenizers="$(awk '/github.com\/daulet\/tokenizers/ { sub(/^v/, "", $2); print $2; exit }' go.mod)" - if [ -z "$tokenizers" ]; then - echo "Failed to determine tokenizers version from go.mod" >&2 - exit 1 + IS_TAG: ${{ startsWith(github.ref, 'refs/tags/v') }} + IS_DISPATCH: ${{ github.event_name == 'workflow_dispatch' }} + CREATE_RELEASE_INPUT: ${{ github.event.inputs.create_release }} + IS_RELEASE_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') }} + run: | + build=false + release=false + # Build on tag pushes, any manual dispatch, or a merged release-labelled PR. + if [ "$IS_TAG" = "true" ] || [ "$IS_DISPATCH" = "true" ] || [ "$IS_RELEASE_PR" = "true" ]; then + build=true fi - echo "tokenizers=$tokenizers" >> "$GITHUB_OUTPUT" - - - name: Cache LFS objects - uses: actions/cache@v5 - with: - path: .git/lfs - key: lfs-${{ runner.os }}-${{ hashFiles('.gitattributes') }} - restore-keys: | - lfs-${{ runner.os }}- - - - name: Cache tokenizers library - uses: actions/cache@v5 - with: - path: build/tokenizers/libtokenizers.a - key: ${{ runner.os }}-tokenizers-v${{ steps.deps.outputs.tokenizers }}-prebuilt-v1 - - - name: Cache ONNX Runtime library - uses: actions/cache@v5 - with: - path: build/libonnxruntime.*.dylib - key: ${{ runner.os }}-onnx-1.24.2 - restore-keys: | - ${{ runner.os }}-onnx- - - - name: Extract Version - id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - # Extract version from tag (v1.2.3 -> 1.2.3) - VERSION=${GITHUB_REF#refs/tags/v} - else - # For non-tag builds, extract version from package.json - VERSION=$(cat src/frontend/package.json | grep '"version"' | head -1 | awk -F '"' '{print $4}') - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Building version: $VERSION" - - - name: Verify Git LFS Files - run: | - git lfs pull - # Verify model file is not an LFS pointer - MODEL_SIZE=$(stat -f%z "model/quantized/model.onnx" 2>/dev/null || echo "0") - echo "Model file size: ${MODEL_SIZE} bytes" - if [ "$MODEL_SIZE" -lt 1000 ]; then - echo "❌ Model file appears to be an LFS pointer (too small)" - exit 1 + # Release on tag pushes, an opt-in dispatch, or a merged release-labelled PR. + if [ "$IS_TAG" = "true" ] || [ "$CREATE_RELEASE_INPUT" = "true" ] || [ "$IS_RELEASE_PR" = "true" ]; then + release=true fi - - - name: Install Python Dependencies - run: | - uv venv --python 3.13 - uv pip install onnxruntime==1.24.2 - - - name: Install Root Dependencies - run: npm ci - - - name: Install Frontend Dependencies - run: | - cd src/frontend - npm ci - - - name: Write Apple API key to file - run: | - mkdir -p "$RUNNER_TEMP/apple" - echo "$APPLE_API_KEY" > "$RUNNER_TEMP/apple/AuthKey.p8" - chmod 600 "$RUNNER_TEMP/apple/AuthKey.p8" - env: - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - - - name: Build DMG - # Invoke the script directly (no skip-sign arg) so signing + notarization run. - # `make build-dmg` passes `skip-sign` for local dev builds. - run: | - chmod +x src/scripts/build_dmg.sh - ./src/scripts/build_dmg.sh - env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} - APPLE_API_KEY: ${{ runner.temp }}/apple/AuthKey.p8 - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - # Force signing on `pull_request` event runs (notably the merged - # release-PR path: this workflow's trigger includes merged PRs with - # the `release` label, and on those runs GITHUB_BASE_REF is set so - # electron-builder treats it as a PR build and silently skips - # signing — producing a half-signed bundle that fails - # `codesign --verify` with "code has no resources but signature - # indicates they must be present". Safe here: the job uses the - # protected "DMG Build Environment", gated on maintainer approval - # before secrets are exposed. - CSC_FOR_PULL_REQUEST: "true" - - - name: Normalize macOS Artifact Names - run: | - cd src/frontend/release - for artifact in *.dmg *-mac.zip; do - if [ -f "$artifact" ]; then - # Match the artifact names generated in latest-mac.yml. - # Example: "Kiji Privacy Proxy-0.2.0-arm64-mac.zip" -> "Kiji-Privacy-Proxy-0.2.0-arm64-mac.zip" - new_name=$(echo "$artifact" | tr ' ' '-') - if [ "$artifact" != "$new_name" ]; then - mv "$artifact" "$new_name" - echo "Renamed: $artifact -> $new_name" - else - echo "Already clean: $artifact" - fi - fi - done - - - name: Generate DMG SHA256 Checksums - working-directory: src/frontend/release - run: | - set -euo pipefail - shopt -s nullglob - dmgs=(*.dmg) - if [ ${#dmgs[@]} -eq 0 ]; then - echo "::error::No DMG files found in src/frontend/release" - exit 1 - fi - for dmg in "${dmgs[@]}"; do - shasum -a 256 "$dmg" > "${dmg}.sha256" - echo "Generated checksum:" - cat "${dmg}.sha256" - done - - - name: Upload Build Artifacts - uses: actions/upload-artifact@v7 - with: - name: dmg-assets - path: | - src/frontend/release/*.dmg - src/frontend/release/*.dmg.sha256 - src/frontend/release/*.zip - src/frontend/release/latest-mac.yml - retention-days: 90 - - - name: Build Summary - if: always() - run: | - echo "## 📦 macOS Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "**DMG Files:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - ls -lh src/frontend/release/*.dmg 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No DMG files found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**SHA256 Checksums:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - cat src/frontend/release/*.dmg.sha256 2>/dev/null | awk '{print "- `" $1 "` " $2}' >> $GITHUB_STEP_SUMMARY || echo "No checksums found" >> $GITHUB_STEP_SUMMARY + echo "build=$build" >> "$GITHUB_OUTPUT" + echo "release=$release" >> "$GITHUB_OUTPUT" + echo "should_build=$build should_release=$release" # ────────────────────────────────────────────────────────────────────── - # Linux binary build (amd64) + # Platform builds (reusable workflows). Each fans out independently and + # uploads its artifacts for create-release to collect. # ────────────────────────────────────────────────────────────────────── + build-dmg: + name: Build DMG + needs: gate + if: needs.gate.outputs.should_build == 'true' + uses: ./.github/workflows/_build-dmg.yml + secrets: inherit + build-linux: name: Build Linux Binary - if: | - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')) + needs: gate + if: needs.gate.outputs.should_build == 'true' + uses: ./.github/workflows/_build-linux.yml - runs-on: ubuntu-latest - container: - image: almalinux:9 - - permissions: - contents: read - - steps: - - name: Install system packages - run: | - set -euo pipefail - dnf -y install \ - bash \ - binutils \ - file \ - findutils \ - gcc \ - gcc-c++ \ - git \ - git-lfs \ - gzip \ - jq \ - make \ - nodejs \ - npm \ - tar \ - unzip \ - wget \ - which \ - zip - - - name: Checkout Repository - uses: actions/checkout@v6 - with: - lfs: true - fetch-depth: 0 - - - name: Mark Repository as Safe for Git - run: | - set -euo pipefail - git config --global --add safe.directory "$GITHUB_WORKSPACE" - git config --global --get-all safe.directory - - - name: Setup Go - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - - - name: Setup Rust - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - with: - toolchain: stable - - - name: Cache LFS objects - uses: actions/cache@v5 - with: - path: .git/lfs - key: lfs-${{ runner.os }}-almalinux9-${{ hashFiles('.gitattributes') }} - restore-keys: | - lfs-${{ runner.os }}-almalinux9- - - - name: Extract dependency versions - id: deps - run: | - set -euo pipefail - tokenizers="$(awk '/github.com\/daulet\/tokenizers/ { sub(/^v/, "", $2); print $2; exit }' go.mod)" - if [ -z "$tokenizers" ]; then - echo "Failed to determine tokenizers version from go.mod" >&2 - exit 1 - fi - echo "tokenizers=$tokenizers" >> "$GITHUB_OUTPUT" - - - name: Cache Go modules - uses: actions/cache@v5 - with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ runner.os }}-almalinux9-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-almalinux9-go- - - - name: Cache tokenizers library - uses: actions/cache@v5 - with: - path: build/tokenizers/libtokenizers.a - key: ${{ runner.os }}-almalinux9-tokenizers-v${{ steps.deps.outputs.tokenizers }}-prebuilt-v1 - - - name: Cache ONNX Runtime library - uses: actions/cache@v5 - with: - path: | - build/libonnxruntime.so.1.24.2 - build/libonnxruntime.so - key: ${{ runner.os }}-almalinux9-onnx-1.24.2 - - - name: Extract Version - id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - # Extract version from tag (v1.2.3 -> 1.2.3) - VERSION=${GITHUB_REF#refs/tags/v} - else - # For non-tag builds, extract version from package.json - VERSION=$(cat src/frontend/package.json | grep '"version"' | head -1 | awk -F '"' '{print $4}') - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Building version: $VERSION" - - - name: Verify Git LFS Files - run: | - set -euo pipefail - git lfs pull - # Verify model file is not an LFS pointer - MODEL_SIZE=$(stat -c%s "model/quantized/model.onnx" 2>/dev/null || echo "0") - echo "Model file size: ${MODEL_SIZE} bytes" - if [ "$MODEL_SIZE" -lt 1000 ]; then - echo "❌ Model file appears to be an LFS pointer (too small)" - exit 1 - fi - - - name: Build Linux Binary - run: | - chmod +x src/scripts/build_linux.sh - ./src/scripts/build_linux.sh - - - name: Report GLIBC requirements - run: | - chmod +x src/scripts/report_glibc_requirements.sh - GLIBC_REPORT="$( - ./src/scripts/report_glibc_requirements.sh \ - build/kiji-proxy \ - build/libonnxruntime.so.1.24.2 - )" - echo "$GLIBC_REPORT" - - GLIBC_REQUIREMENT="$(printf '%s\n' "$GLIBC_REPORT" | awk -F': ' '/^Release minimum required GLIBC:/ {print $2}' | tail -n 1)" - if [ -n "$GLIBC_REQUIREMENT" ]; then - echo "**Minimum required GLIBC:** $GLIBC_REQUIREMENT" >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload Linux Archive as Artifact - uses: actions/upload-artifact@v7 - with: - name: linux-assets - path: | - release/linux/*.tar.gz - release/linux/*.tar.gz.sha256 - retention-days: 90 - - - name: Build Summary - if: always() - run: | - echo "## 📦 Linux Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "**Archive Files:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - ls -lh release/linux/*.tar.gz 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No archive files found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**SHA256 Checksums:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - cat release/linux/*.sha256 2>/dev/null | awk '{print "- `" $1 "`"}' >> $GITHUB_STEP_SUMMARY || echo "No checksums found" >> $GITHUB_STEP_SUMMARY - - # ────────────────────────────────────────────────────────────────────── - # Package the Linux binary as a .deb (Debian-native tooling, so this - # runs on ubuntu-latest rather than the almalinux build container). - # ────────────────────────────────────────────────────────────────────── package-linux-deb: name: Package Linux .deb - needs: build-linux - if: | - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')) - - runs-on: ubuntu-latest - - permissions: - contents: read - - steps: - - name: Checkout Repository - uses: actions/checkout@v6 - - - name: Download Linux binary artifact - uses: actions/download-artifact@v8 - with: - name: linux-assets - path: release/linux - - - name: Install deb packaging tools - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - debhelper devscripts fakeroot dpkg-dev - - - name: Build .deb package - run: | - chmod +x src/scripts/build_deb.sh - ./src/scripts/build_deb.sh + needs: [gate, build-linux] + if: needs.gate.outputs.should_build == 'true' + uses: ./.github/workflows/_package-deb.yml - - name: Upload .deb as Artifact - uses: actions/upload-artifact@v7 - with: - name: linux-deb - path: | - release/linux/*.deb - release/linux/*.deb.sha256 - retention-days: 90 - - - name: Build Summary - if: always() - run: | - echo "## 📦 Linux .deb Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Deb Packages:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - ls -lh release/linux/*.deb 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No deb packages found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**SHA256 Checksums:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - cat release/linux/*.deb.sha256 2>/dev/null | awk '{print "- `" $1 "`"}' >> $GITHUB_STEP_SUMMARY || echo "No checksums found" >> $GITHUB_STEP_SUMMARY - - # ────────────────────────────────────────────────────────────────────── - # Chrome Extension package - # ────────────────────────────────────────────────────────────────────── build-chrome: name: Package Chrome Extension - if: | - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')) - - runs-on: ubuntu-latest - - permissions: - contents: read - - steps: - - name: Checkout Repository - uses: actions/checkout@v6 - - - name: Extract Version - id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/v} - else - VERSION=$(cat src/frontend/package.json | grep '"version"' | head -1 | awk -F '"' '{print $4}') - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Building version: $VERSION" - - - name: Stamp Version in Manifest - run: | - cd chrome-extension - # Update manifest.json version to match the release tag - jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' manifest.json > manifest.tmp - mv manifest.tmp manifest.json - echo "Manifest version set to: ${{ steps.version.outputs.version }}" - cat manifest.json | jq .version - - - name: Package Extension - run: | - cd chrome-extension - zip -r ../kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip . \ - -x '*.DS_Store' '*.git*' - echo "Package contents:" - unzip -l ../kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip - - - name: Generate SHA256 Checksum - run: | - sha256sum kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip \ - > kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip.sha256 - cat kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip.sha256 - - - name: Upload as Artifact - uses: actions/upload-artifact@v7 - with: - name: chrome-assets - path: | - kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip - kiji-privacy-proxy-extension-${{ steps.version.outputs.version }}.zip.sha256 - retention-days: 90 - - - name: Build Summary - if: always() - run: | - echo "## Chrome Extension Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "**Package:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - ls -lh kiji-privacy-proxy-extension-*.zip 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY || echo "No zip found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**SHA256:**" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat kiji-privacy-proxy-extension-*.sha256 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "No checksum found" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + needs: gate + if: needs.gate.outputs.should_build == 'true' + uses: ./.github/workflows/_build-chrome.yml # ────────────────────────────────────────────────────────────────────── # Create a single GitHub Release with all assets # ────────────────────────────────────────────────────────────────────── create-release: name: Create GitHub Release - needs: [build-dmg, build-linux, package-linux-deb, build-chrome] - if: | - startsWith(github.ref, 'refs/tags/v') || - github.event.inputs.create_release == 'true' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')) + needs: [gate, build-dmg, build-linux, package-linux-deb, build-chrome] + if: needs.gate.outputs.should_release == 'true' runs-on: ubuntu-latest @@ -582,14 +104,7 @@ jobs: - name: Extract Version id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/v} - else - VERSION=$(cat src/frontend/package.json | grep '"version"' | head -1 | awk -F '"' '{print $4}') - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Releasing version: $VERSION" + uses: ./.github/actions/extract-version - name: Download all build artifacts uses: actions/download-artifact@v8 @@ -765,11 +280,8 @@ jobs: # ────────────────────────────────────────────────────────────────────── publish-homebrew-tap: name: Publish Homebrew Tap - needs: [create-release] - if: | - startsWith(github.ref, 'refs/tags/v') || - github.event.inputs.create_release == 'true' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')) + needs: [gate, create-release] + if: needs.gate.outputs.should_release == 'true' runs-on: ubuntu-latest @@ -786,14 +298,12 @@ jobs: - name: Extract Version id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/v} - else - VERSION=$(cat src/frontend/package.json | grep '"version"' | head -1 | awk -F '"' '{print $4}') - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT + uses: ./.github/actions/extract-version + - name: Determine prerelease + id: prerelease + run: | + VERSION="${{ steps.version.outputs.version }}" # Tap only ships stable releases; skip alpha/beta/rc. if [[ "$VERSION" == *"-alpha"* ]] || [[ "$VERSION" == *"-beta"* ]] || [[ "$VERSION" == *"-rc"* ]]; then echo "is_prerelease=true" >> $GITHUB_OUTPUT @@ -803,7 +313,7 @@ jobs: fi - name: Download DMG artifacts - if: steps.version.outputs.is_prerelease == 'false' + if: steps.prerelease.outputs.is_prerelease == 'false' uses: actions/download-artifact@v8 with: name: dmg-assets @@ -811,7 +321,7 @@ jobs: - name: Compute DMG SHA256 id: cask - if: steps.version.outputs.is_prerelease == 'false' + if: steps.prerelease.outputs.is_prerelease == 'false' run: | set -euo pipefail VERSION="${{ steps.version.outputs.version }}" @@ -834,7 +344,7 @@ jobs: echo "DMG SHA256: $SHA" - name: Generate GitHub App token for homebrew-tap - if: steps.version.outputs.is_prerelease == 'false' + if: steps.prerelease.outputs.is_prerelease == 'false' id: app-token uses: actions/create-github-app-token@v3 with: @@ -844,7 +354,7 @@ jobs: repositories: homebrew-tap - name: Checkout homebrew-tap - if: steps.version.outputs.is_prerelease == 'false' + if: steps.prerelease.outputs.is_prerelease == 'false' uses: actions/checkout@v6 with: repository: dataiku/homebrew-tap @@ -852,7 +362,7 @@ jobs: path: homebrew-tap - name: Render cask and push - if: steps.version.outputs.is_prerelease == 'false' + if: steps.prerelease.outputs.is_prerelease == 'false' env: VERSION: ${{ steps.version.outputs.version }} SHA256: ${{ steps.cask.outputs.sha256 }} diff --git a/packaging/debian/rules b/packaging/debian/rules index 57d83578..2341b657 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -17,6 +17,14 @@ override_dh_auto_configure: override_dh_auto_build: override_dh_auto_test: +# The Go binary and prebuilt ONNX Runtime .so are shipped as-is. Go emits +# compressed/non-standard DWARF that dwz cannot process (it exits non-zero) and +# the .so carries no .debug_info, so dh_dwz aborts the build. dh_strip is also +# skipped so the packaged binary stays byte-identical to the tarball build and +# no stray -dbgsym .deb is produced. +override_dh_dwz: +override_dh_strip: + override_dh_auto_install: @if [ -z "$(STAGED)" ]; then \ echo "ERROR: no staging tree under release/linux/." >&2; \