Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bumpy-goats-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kiji-privacy-proxy": patch
---

Updated release workflow, deb package and homebrew
21 changes: 21 additions & 0 deletions .github/actions/extract-tokenizers-version/action.yml
Original file line number Diff line number Diff line change
@@ -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"
24 changes: 24 additions & 0 deletions .github/actions/extract-version/action.yml
Original file line number Diff line number Diff line change
@@ -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"
21 changes: 21 additions & 0 deletions .github/actions/verify-lfs-model/action.yml
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions .github/workflows/_build-chrome.yml
Original file line number Diff line number Diff line change
@@ -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
206 changes: 206 additions & 0 deletions .github/workflows/_build-dmg.yml
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading