From ea6467adaf0b76af6bc7a8000a60fa1c0aeaecdf Mon Sep 17 00:00:00 2001 From: Hannes Hapke Date: Fri, 1 May 2026 20:37:12 -0700 Subject: [PATCH 1/7] Homebrew setup --- .github/workflows/release.yml | 22 ++++++++++++++ .../homebrew/Casks/kiji-privacy-proxy.rb | 29 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 packaging/homebrew/Casks/kiji-privacy-proxy.rb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f1480c5..b72314d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -192,12 +192,29 @@ jobs: 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 @@ -211,6 +228,10 @@ jobs: 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 # ────────────────────────────────────────────────────────────────────── # Linux binary build (amd64) @@ -581,6 +602,7 @@ jobs: # Collect all release assets into a flat directory mkdir -p assets cp release-assets/dmg-assets/*.dmg assets/ 2>/dev/null || true + cp release-assets/dmg-assets/*.dmg.sha256 assets/ 2>/dev/null || true cp release-assets/dmg-assets/*.zip assets/ 2>/dev/null || true cp release-assets/dmg-assets/latest-mac.yml assets/ 2>/dev/null || true cp release-assets/linux-assets/*.tar.gz assets/ 2>/dev/null || true diff --git a/packaging/homebrew/Casks/kiji-privacy-proxy.rb b/packaging/homebrew/Casks/kiji-privacy-proxy.rb new file mode 100644 index 00000000..2a39bc93 --- /dev/null +++ b/packaging/homebrew/Casks/kiji-privacy-proxy.rb @@ -0,0 +1,29 @@ +cask "kiji-privacy-proxy" do + version "0.0.0" + sha256 "0000000000000000000000000000000000000000000000000000000000000000" + + url "https://github.com/dataiku/kiji-proxy/releases/download/v#{version}/Kiji-Privacy-Proxy-#{version}.dmg", + verified: "github.com/dataiku/kiji-proxy/" + name "Kiji Privacy Proxy" + desc "Privacy layer that masks PII in requests to AI APIs" + homepage "https://github.com/dataiku/kiji-proxy" + + livecheck do + url :url + strategy :github_latest + end + + depends_on macos: ">= :high_sierra" + + app "Kiji Privacy Proxy.app" + + zap trash: [ + "~/.kiji-proxy", + "~/Library/Application Support/Kiji Privacy Proxy", + "~/Library/Caches/com.kiji.proxy", + "~/Library/Caches/com.kiji.proxy.ShipIt", + "~/Library/Logs/Kiji Privacy Proxy", + "~/Library/Preferences/com.kiji.proxy.plist", + "~/Library/Saved Application State/com.kiji.proxy.savedState", + ] +end From d33959a5cd6bfffd94b9d78996319177a8501ef2 Mon Sep 17 00:00:00 2001 From: Hannes Hapke Date: Wed, 27 May 2026 20:16:25 -0700 Subject: [PATCH 2/7] updated homebrew tap --- .github/workflows/release.yml | 133 ++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b72314d2..38c5cded 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -621,3 +621,136 @@ jobs: --generate-notes \ --latest \ $IS_PRERELEASE + + # ────────────────────────────────────────────────────────────────────── + # Publish Homebrew Cask to dataiku/homebrew-tap + # ────────────────────────────────────────────────────────────────────── + 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')) + + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.merge_commit_sha || github.ref }} + + - 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 + + # Tap only ships stable releases; skip alpha/beta/rc. + if [[ "$VERSION" == *"-alpha"* ]] || [[ "$VERSION" == *"-beta"* ]] || [[ "$VERSION" == *"-rc"* ]]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "::notice::Skipping Homebrew tap publish for prerelease $VERSION" + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + fi + + - name: Download DMG artifacts + if: steps.version.outputs.is_prerelease == 'false' + uses: actions/download-artifact@v8 + with: + name: dmg-assets + path: dmg-assets + + - name: Compute DMG SHA256 + id: cask + if: steps.version.outputs.is_prerelease == 'false' + run: | + set -euo pipefail + VERSION="${{ steps.version.outputs.version }}" + DMG="dmg-assets/Kiji-Privacy-Proxy-${VERSION}.dmg" + if [ ! -f "$DMG" ]; then + echo "::error::Expected DMG not found at $DMG" + ls -la dmg-assets/ + exit 1 + fi + if [ ! -f "${DMG}.sha256" ]; then + echo "::error::Checksum file not found at ${DMG}.sha256" + exit 1 + fi + SHA=$(awk '{print $1}' "${DMG}.sha256") + if [ -z "$SHA" ]; then + echo "::error::Empty SHA256 parsed from ${DMG}.sha256" + exit 1 + fi + echo "sha256=$SHA" >> $GITHUB_OUTPUT + echo "DMG SHA256: $SHA" + + - name: Generate GitHub App token for homebrew-tap + if: steps.version.outputs.is_prerelease == 'false' + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.HOMEBREW_TAP_APP_CLIENT_ID }} + private-key: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} + owner: dataiku + repositories: homebrew-tap + + - name: Checkout homebrew-tap + if: steps.version.outputs.is_prerelease == 'false' + uses: actions/checkout@v6 + with: + repository: dataiku/homebrew-tap + token: ${{ steps.app-token.outputs.token }} + path: homebrew-tap + + - name: Render cask and push + if: steps.version.outputs.is_prerelease == 'false' + env: + VERSION: ${{ steps.version.outputs.version }} + SHA256: ${{ steps.cask.outputs.sha256 }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + set -euo pipefail + SRC=packaging/homebrew/Casks/kiji-privacy-proxy.rb + DST=homebrew-tap/Casks/kiji-privacy-proxy.rb + mkdir -p homebrew-tap/Casks + + # Guard against template drift: refuse to render if the placeholders + # we depend on are no longer present. + if ! grep -q '^ version "0\.0\.0"$' "$SRC"; then + echo "::error::Template $SRC no longer contains placeholder: version \"0.0.0\"" + exit 1 + fi + if ! grep -qE '^ sha256 "0{64}"$' "$SRC"; then + echo "::error::Template $SRC no longer contains placeholder sha256" + exit 1 + fi + + sed \ + -e "s|^ version \"0\\.0\\.0\"$| version \"${VERSION}\"|" \ + -e "s|^ sha256 \"0\\{64\\}\"$| sha256 \"${SHA256}\"|" \ + "$SRC" > "$DST" + + echo "Rendered cask:" + cat "$DST" + + cd homebrew-tap + git config user.name "${APP_SLUG}[bot]" + git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" + + if git diff --quiet -- Casks/kiji-privacy-proxy.rb; then + echo "::notice::Cask already at ${VERSION}, nothing to commit." + exit 0 + fi + + git add Casks/kiji-privacy-proxy.rb + git commit -m "kiji-privacy-proxy ${VERSION}" + git push origin HEAD:main From 730cd3924015430d0e2d2f30a504ebc927f469a7 Mon Sep 17 00:00:00 2001 From: Hannes Hapke Date: Wed, 27 May 2026 20:19:04 -0700 Subject: [PATCH 3/7] added requirement descriptions --- .github/workflows/REQUIREMENTS.md | 242 ++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 .github/workflows/REQUIREMENTS.md diff --git a/.github/workflows/REQUIREMENTS.md b/.github/workflows/REQUIREMENTS.md new file mode 100644 index 00000000..4dfb0950 --- /dev/null +++ b/.github/workflows/REQUIREMENTS.md @@ -0,0 +1,242 @@ +# GitHub Actions & Repository Requirements + +Reference for everything a fork or new clone of this repo needs configured on the GitHub side before the workflows in this directory will run correctly. + +Source of truth: the workflow YAML files in `.github/workflows/`. If you change a workflow, update this doc. + +--- + +## 1. Repository Settings + +### Workflow permissions +**Settings → Actions → General → Workflow permissions** + +- **"Read and write permissions"** — required so `changesets.yml`, `release.yml`, and `cla.yml` can push branches, tags, and commits using `GITHUB_TOKEN`. +- **"Allow GitHub Actions to create and approve pull requests"** — **required** for `changesets.yml` to open the Version PR. + +### Branch protection (recommended) +**Settings → Branches → Branch protection rules → `main`** + +Required status checks (from `lint-and-test.yml` and `semantic-pr.yml`): +- `Python Lint` +- `Go Lint` +- `Go Tests` +- `Frontend Lint & Type Check` +- `Workflow Lint (actionlint)` +- `Validate PR title` +- `Check PR scope` +- `CLA Assistant` (from `cla.yml`) + +Other recommended settings: +- Require pull request reviews before merging +- Require linear history (changesets workflow assumes a clean main) +- Do **not** allow force pushes to `main` + +### Labels +The following labels must exist (create under **Issues → Labels**): + +| Label | Used by | Purpose | +|-------|---------|---------| +| `release` | `changesets.yml`, `release.yml` | Applied to Version PRs; merging a PR with this label triggers the build/release pipeline | +| `dependencies` | `dependabot.yml` | Auto-applied to Dependabot PRs | +| `github-actions` | `dependabot.yml` | Auto-applied to Action-version bumps | +| `javascript` | `dependabot.yml` | Auto-applied to npm bumps | +| `go` | `dependabot.yml` | Auto-applied to Go module bumps | + +--- + +## 2. Environments + +### `DMG Build Environment` +**Settings → Environments → New environment → `DMG Build Environment`** + +Used **only** by the `build-dmg` job in `release.yml`. Name is case-sensitive. + +**Recommended environment protection rules:** +- Required reviewers (maintainers) — secrets stay sealed until a maintainer approves the deployment. This is what makes it safe to expose signing secrets on the `pull_request` (merged-release-PR) trigger. +- Restrict to `main` branch and tags matching `v*`. + +**Secrets (required):** + +| Secret | Purpose | How to obtain | +|--------|---------|---------------| +| `CSC_LINK` | Base64-encoded `.p12` Apple Developer ID Application certificate | `base64 -i certificate.p12 \| pbcopy` | +| `CSC_KEY_PASSWORD` | Password used when exporting the `.p12` from Keychain | Set at export time | +| `APPLE_API_KEY` | Contents of the App Store Connect `AuthKey_XXXX.p8` file | App Store Connect → Users and Access → Integrations → App Store Connect API | +| `APPLE_API_KEY_ID` | The 10-char Key ID for the `.p8` above | Shown next to the key in App Store Connect | +| `APPLE_API_ISSUER` | The Issuer ID (UUID) for your App Store Connect API account | Same page as Key ID | + +All five are checked up-front by the "Verify signing secrets are available" step in `release.yml`; the job fails fast if any are missing. + +> **Note on notarization:** `release.yml` enables notarization via the App Store Connect API key (`APPLE_API_*`). The older `APPLE_ID` / `APPLE_APP_SPECIFIC_PASSWORD` / `APPLE_TEAM_ID` approach mentioned in the workflows README is no longer used. + +--- + +## 3. Repository Variables and Secrets + +`GITHUB_TOKEN` is provided automatically by GitHub Actions and is sufficient for most workflows. The Homebrew tap publishing job additionally needs a scoped GitHub App credential. + +### Variables (Settings → Secrets and variables → Actions → Variables) + +| Variable | Used by | Purpose | +|----------|---------|---------| +| `HOMEBREW_TAP_APP_CLIENT_ID` | `release.yml` (`publish-homebrew-tap`) | Client ID of the GitHub App authorized to push to `dataiku/homebrew-tap` | + +### Secrets (Settings → Secrets and variables → Actions → Secrets) + +| Secret | Used by | Purpose | +|--------|---------|---------| +| `HOMEBREW_TAP_APP_PRIVATE_KEY` | `release.yml` (`publish-homebrew-tap`) | PEM private key (begins with `-----BEGIN RSA PRIVATE KEY-----`) downloaded from the GitHub App settings page | + +**Optional:** +- `SIGNING_PRIVATE_KEY` — referenced by `sign-model.yml` if it is added back to the repo (the workflow is documented in the workflows README but not currently present here). + +See [§12 Homebrew Tap Publishing](#12-homebrew-tap-publishing) for the App setup that these values plug into. No PATs are required — the App scope replaces what a PAT would have done. + +--- + +## 4. Git LFS + +`release.yml` (build-dmg, build-linux) and any workflow that touches `model/quantized/model.onnx` **require Git LFS** to be enabled on the repository. + +- **Settings → General → Features → Git LFS** must be on. +- Checkouts use `actions/checkout@v6` with `lfs: true` and explicitly run `git lfs pull` before building. +- A size-check step fails the build if `model/quantized/model.onnx` is still a pointer file (<1000 bytes). +- LFS bandwidth/storage quotas apply — forks must enable LFS billing or the builds will fail with rate-limit errors. + +--- + +## 5. Workflow Inventory + +| Workflow | Triggers | Permissions | Environment | Secrets used | Runner(s) | +|----------|----------|-------------|-------------|--------------|-----------| +| `changesets.yml` | `push: main` | `contents: write`, `pull-requests: write` | — | `GITHUB_TOKEN` | `ubuntu-latest` | +| `release.yml` (build-dmg) | tag `v*`, `workflow_dispatch`, merged PR with `release` label | `contents: read` | `DMG Build Environment` | `CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_API_KEY`, `APPLE_API_KEY_ID`, `APPLE_API_ISSUER` | `macos-latest` | +| `release.yml` (build-linux) | same as above | `contents: read` | — | — | `ubuntu-latest` (container: `almalinux:9`) | +| `release.yml` (build-chrome) | same as above | `contents: read` | — | — | `ubuntu-latest` | +| `release.yml` (create-release) | same as above (needs all 3 builds) | `contents: write` | — | `GITHUB_TOKEN` | `ubuntu-latest` | +| `release.yml` (publish-homebrew-tap) | same as above (needs `create-release`); skips alpha/beta/rc | `contents: read` | — | `HOMEBREW_TAP_APP_PRIVATE_KEY`, `vars.HOMEBREW_TAP_APP_CLIENT_ID` | `ubuntu-latest` | +| `lint-and-test.yml` | `push`/`pull_request` on `main`/`develop` | default | — | — | `ubuntu-latest` (×5 jobs) | +| `semantic-pr.yml` | `pull_request_target`: opened/edited/synchronize | `pull-requests: write`, `contents: read` | — | `GITHUB_TOKEN` | `ubuntu-latest` | +| `cleanup-artifacts.yml` | daily 02:00 UTC, `workflow_dispatch` | `actions: write` | — | — | `ubuntu-latest` | +| `cla.yml` | `issue_comment: created`, `pull_request_target` | `actions: write`, `contents: write`, `pull-requests: write`, `statuses: write` | — | `GITHUB_TOKEN` | `ubuntu-latest` | + +> `pull_request_target` (used by `semantic-pr.yml` and `cla.yml`) runs in the context of the base repo with access to secrets. Be careful when editing — these workflows must not check out and execute untrusted PR code. + +--- + +## 6. Runner & Toolchain Requirements + +The workflows assume these toolchain versions are available (some are installed by the workflow itself, others come from the runner image): + +| Tool | Version | Source | +|------|---------|--------| +| Go | from `go.mod` | `actions/setup-go@v6` with `go-version-file: go.mod` | +| Python | 3.13 | `actions/setup-python@v6` | +| Node.js | 20 | `actions/setup-node@v6` | +| Rust | stable | `dtolnay/rust-toolchain` (SHA pinned) | +| uv | latest (cached) | `astral-sh/setup-uv` (SHA pinned) | +| `golangci-lint` | from `.tool-versions` | `golangci/golangci-lint-action` (SHA pinned) | +| `actionlint` | 1.7.12 (SHA-pinned download script) | inline curl | + +The Linux build runs inside an **AlmaLinux 9 container** to ensure a low GLIBC requirement for the released binary. The job reports the minimum required GLIBC in the build summary. + +--- + +## 7. Concurrency & Triggers + +- `changesets.yml` uses `concurrency: ${{ github.workflow }}-${{ github.ref }}` so only one Version PR run is active per branch. +- `release.yml` has no concurrency group — multiple tag pushes will run in parallel; the `create-release` job uses `gh release create` which will fail (not overwrite) if a tag's release already exists. +- The Version PR opened by `changesets.yml` is created using `GITHUB_TOKEN`, so `pull_request`-triggered workflows (lint-and-test, semantic-pr) **will not run on it**. This is intentional — the diff is mechanical — but it means lint/test coverage on that PR is implicit. + +--- + +## 8. Dependabot + +Configured in `.github/dependabot.yml`. Weekly updates on Mondays at 09:00 for: + +- `github-actions` (root) — grouped into a single PR; uses `ci` commit prefix +- `npm` (`/src/frontend`) — grouped by `production` / `development`; **ignores major version bumps** +- `npm` (root) — for changesets / tooling +- `gomod` (root) — grouped into a single PR + +For Dependabot PRs to pass the `semantic-pr.yml` check, the PR title prefix (`ci`, `deps`, `deps-dev`) must be in the allowed types list. The allowed list in `semantic-pr.yml` includes: `feat`, `fix`, `docs`, `style`, `chore`, `refactor`, `test`, `ci`, `perf`, `deps`. **`deps-dev` is not in the allowed list** — if Dependabot ever opens a PR with that prefix the semantic check will fail. Either add `deps-dev` to the allowed types or change the `prefix-development` in `dependabot.yml`. + +--- + +## 9. PR-scope Check + +`semantic-pr.yml` runs `.github/scripts/classify-pr-files.sh`, which categorizes changed files into: `ci`, `test`, `docs`, `chore`, `code`. A PR touching **3 or more** of those categories **fails the check**. Files matching no rule fall into `other` and are ignored for the count. + +Classification rules live in the script — update both the script and this doc together if you add new top-level directories. + +--- + +## 10. External Action Pinning + +Third-party actions are pinned to a full commit SHA with the version as a trailing comment. When updating (manually or via Dependabot), update both: + +- `astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0` +- `dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable` +- `golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0` +- `amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1` +- `marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4` +- `cla-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1` + +First-party `actions/*` are pinned to major version tags (`@v6`, `@v7`, etc.). + +--- + +## 11. CLA Assistant + +`cla.yml` writes signatures to the `cla-signatures` branch in this repo (`path-to-signatures: signatures/v1/cla.json`). That branch: + +- Must **not** be protected (the action needs to push to it). +- Will be created automatically on the first signature. +- Bots (`*[bot]`) are allowlisted and skip the CLA check. + +The CLA document itself is pinned by commit SHA in the workflow — updating the CLA wording requires updating the `path-to-document` URL with the new commit SHA so prior signatures stay valid against the version they signed. + +--- + +## 12. Homebrew Tap Publishing + +`release.yml`'s `publish-homebrew-tap` job pushes the rendered cask to `dataiku/homebrew-tap` on every stable release. It skips `-alpha`/`-beta`/`-rc` versions so the tap only ships stable. Authentication is via a GitHub App so the credential is scoped to a single repo, rotatable, and not tied to a user account. + +### GitHub App setup + +1. Create (or reuse) a GitHub App owned by the `dataiku` org. +2. **Repository permissions:** `Contents: Read and write`. No other scopes required. +3. **Install** the App on `dataiku/homebrew-tap` only. Do not install it on `dataiku/kiji-proxy`. The token minted in the workflow is scoped to the tap repo via the `owner` + `repositories` inputs to `actions/create-github-app-token@v2`. +4. Generate a private key on the App settings page and store the full PEM (including the `-----BEGIN/END-----` lines) as the `HOMEBREW_TAP_APP_PRIVATE_KEY` repository secret on `dataiku/kiji-proxy`. +5. Copy the App's **Client ID** from the App settings page and store it as the `HOMEBREW_TAP_APP_CLIENT_ID` repository variable. The Client ID is used in preference to the numeric App ID since the latter is being phased out for new App auth surfaces; the action's `app-id` input accepts either. + +### Tap-repo prerequisites + +- Default branch is `main`. +- A `Casks/` directory exists at the repo root (cask target path: `Casks/kiji-privacy-proxy.rb`). +- Branch protection on `main`, if enabled, must either include the App in bypass actors or be relaxed enough for direct pushes by the bot. If you want to keep strict protection, change the final `git push origin HEAD:main` step in `release.yml` to open a PR using the same App token instead. + +### Cask template + +The cask source of truth lives in this repo at `packaging/homebrew/Casks/kiji-privacy-proxy.rb`, with placeholder `version "0.0.0"` and a 64-zero `sha256`. The workflow: + +- refuses to render if either placeholder line is missing (template-drift guard); +- substitutes only those two lines and writes the result to the tap; +- commits as `${app-slug}[bot]` with message `kiji-privacy-proxy ${VERSION}`. + +Edit the template in-repo to change cask metadata (description, zap paths, dependencies, etc.) — the workflow rewrites only the version and sha256 on each release. + +--- + +## 13. Quick Setup Checklist (for a fork) + +- [ ] Enable Actions (Settings → Actions → General) +- [ ] Set workflow permissions to read/write and allow PR creation +- [ ] Enable Git LFS and confirm bandwidth quota +- [ ] Create labels: `release`, `dependencies`, `github-actions`, `javascript`, `go` +- [ ] Create `DMG Build Environment` with the 5 Apple secrets and maintainer-approval rule (only if you need signed macOS builds) +- [ ] Configure branch protection on `main` with the status checks listed above +- [ ] Ensure the `cla-signatures` branch is not protected +- [ ] (Optional) Adjust `dependabot.yml` schedule/timezone to your team +- [ ] (For Homebrew distribution) Create a GitHub App with `Contents: read & write`, install it on your fork's tap repo, then set the `HOMEBREW_TAP_APP_CLIENT_ID` variable and `HOMEBREW_TAP_APP_PRIVATE_KEY` secret on the source repo. Update `actions/create-github-app-token` inputs (`owner`, `repositories`) and the `git push` target if your tap lives elsewhere. From 9b22d54a12c9506d69f0c8adeaa39e9f5136fe6b Mon Sep 17 00:00:00 2001 From: Hannes Hapke Date: Thu, 28 May 2026 11:28:26 -0700 Subject: [PATCH 4/7] added test workflow --- .github/workflows/test-homebrew-tap-auth.yml | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 .github/workflows/test-homebrew-tap-auth.yml diff --git a/.github/workflows/test-homebrew-tap-auth.yml b/.github/workflows/test-homebrew-tap-auth.yml new file mode 100644 index 00000000..a4263da1 --- /dev/null +++ b/.github/workflows/test-homebrew-tap-auth.yml @@ -0,0 +1,87 @@ +name: Test Homebrew Tap Auth + +# One-off smoke test. Verifies that the GitHub App referenced by +# vars.HOMEBREW_TAP_APP_CLIENT_ID + secrets.HOMEBREW_TAP_APP_PRIVATE_KEY can +# mint a token scoped to dataiku/homebrew-tap and both push to and delete +# from it. Safe to delete this workflow file once the publish-homebrew-tap +# job in release.yml is proven end-to-end. + +on: + workflow_dispatch: + inputs: + cleanup: + description: "Delete the test branch from the tap after pushing" + required: false + default: true + type: boolean + +jobs: + smoke-test: + name: Verify App can push to dataiku/homebrew-tap + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Mint App installation token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.HOMEBREW_TAP_APP_CLIENT_ID }} + private-key: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} + owner: dataiku + repositories: homebrew-tap + + - name: Read tap metadata via App token + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -euo pipefail + echo "App slug: ${{ steps.app-token.outputs.app-slug }}" + echo "Installation id: ${{ steps.app-token.outputs.installation-id }}" + echo + gh api repos/dataiku/homebrew-tap \ + --jq '{full_name, default_branch, visibility, permissions}' + + - name: Checkout tap + uses: actions/checkout@v6 + with: + repository: dataiku/homebrew-tap + token: ${{ steps.app-token.outputs.token }} + path: homebrew-tap + + - name: Push marker commit to throwaway branch + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BRANCH: test-auth-${{ github.run_id }} + run: | + set -euo pipefail + cd homebrew-tap + git config user.name "${APP_SLUG}[bot]" + git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" + + git checkout -b "${BRANCH}" + mkdir -p .smoke-tests + cat > ".smoke-tests/${{ github.run_id }}.txt" < Date: Fri, 29 May 2026 09:35:05 -0700 Subject: [PATCH 5/7] added env to release --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 901c62d5..1f3a9d41 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -696,6 +696,8 @@ jobs: runs-on: ubuntu-latest + environment: "Homebrew Release" + permissions: contents: read From a578956493ccbb6e95bc37d8bda59adbd5cc30f8 Mon Sep 17 00:00:00 2001 From: Hannes Hapke Date: Fri, 29 May 2026 09:35:26 -0700 Subject: [PATCH 6/7] removed test workflow --- .github/workflows/test-homebrew-tap-auth.yml | 95 -------------------- 1 file changed, 95 deletions(-) delete mode 100644 .github/workflows/test-homebrew-tap-auth.yml diff --git a/.github/workflows/test-homebrew-tap-auth.yml b/.github/workflows/test-homebrew-tap-auth.yml deleted file mode 100644 index 2d5a8340..00000000 --- a/.github/workflows/test-homebrew-tap-auth.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Test Homebrew Tap Auth - -# One-off smoke test. Verifies that the GitHub App referenced by -# vars.HOMEBREW_TAP_APP_CLIENT_ID + secrets.HOMEBREW_TAP_APP_PRIVATE_KEY can -# mint a token scoped to dataiku/homebrew-tap and both push to and delete -# from it. Safe to delete this workflow file once the publish-homebrew-tap -# job in release.yml is proven end-to-end. - -on: - workflow_dispatch: - inputs: - cleanup: - description: "Delete the test branch from the tap after pushing" - required: false - default: true - type: boolean - -jobs: - smoke-test: - name: Verify App can push to dataiku/homebrew-tap - runs-on: ubuntu-latest - environment: "Homebrew Release" - permissions: - contents: read - - steps: - - name: Debug vars resolution - env: - CLIENT_ID: ${{ vars.HOMEBREW_TAP_APP_CLIENT_ID }} - APP_ID: ${{ vars.HOMEBREW_TAP_APP_ID }} - run: | - echo "CLIENT_ID length: ${#CLIENT_ID}" - echo "APP_ID value: ${APP_ID}" - - name: Mint App installation token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.HOMEBREW_TAP_APP_CLIENT_ID }} - private-key: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} - owner: dataiku - repositories: homebrew-tap - - - name: Read tap metadata via App token - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - set -euo pipefail - echo "App slug: ${{ steps.app-token.outputs.app-slug }}" - echo "Installation id: ${{ steps.app-token.outputs.installation-id }}" - echo - gh api repos/dataiku/homebrew-tap \ - --jq '{full_name, default_branch, visibility, permissions}' - - - name: Checkout tap - uses: actions/checkout@v6 - with: - repository: dataiku/homebrew-tap - token: ${{ steps.app-token.outputs.token }} - path: homebrew-tap - - - name: Push marker commit to throwaway branch - env: - APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - BRANCH: test-auth-${{ github.run_id }} - run: | - set -euo pipefail - cd homebrew-tap - git config user.name "${APP_SLUG}[bot]" - git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" - - git checkout -b "${BRANCH}" - mkdir -p .smoke-tests - cat > ".smoke-tests/${{ github.run_id }}.txt" < Date: Fri, 29 May 2026 09:42:55 -0700 Subject: [PATCH 7/7] updated requirements --- .github/workflows/REQUIREMENTS.md | 54 ++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/.github/workflows/REQUIREMENTS.md b/.github/workflows/REQUIREMENTS.md index 4dfb0950..1738adc8 100644 --- a/.github/workflows/REQUIREMENTS.md +++ b/.github/workflows/REQUIREMENTS.md @@ -70,28 +70,41 @@ All five are checked up-front by the "Verify signing secrets are available" step > **Note on notarization:** `release.yml` enables notarization via the App Store Connect API key (`APPLE_API_*`). The older `APPLE_ID` / `APPLE_APP_SPECIFIC_PASSWORD` / `APPLE_TEAM_ID` approach mentioned in the workflows README is no longer used. ---- +### `Homebrew Release` +**Settings → Environments → New environment → `Homebrew Release`** -## 3. Repository Variables and Secrets +Used by the `publish-homebrew-tap` job in `release.yml` — production cask publish on every stable release. + +Name is case-sensitive. The job declares `environment: "Homebrew Release"`; the GitHub App credentials are env-scoped (not repo-scoped) so they cannot be extracted by a workflow run that does not opt into this environment. -`GITHUB_TOKEN` is provided automatically by GitHub Actions and is sufficient for most workflows. The Homebrew tap publishing job additionally needs a scoped GitHub App credential. +**Recommended environment protection rules:** +- Restrict deployment branches to `main` and tags matching `v*` — prevents an attacker who opens a PR from extracting the App key by running a workflow on their branch. +- Required reviewers (optional) — adds a manual gate before each cask publish. + +**Variables (required):** -### Variables (Settings → Secrets and variables → Actions → Variables) +| Variable | Purpose | +|----------|---------| +| `HOMEBREW_TAP_APP_CLIENT_ID` | Client ID of the GitHub App authorized to push to `dataiku/homebrew-tap`. The numeric App ID also works in `actions/create-github-app-token@v2`'s `app-id` input, but Client ID is the forward-looking value. | -| Variable | Used by | Purpose | -|----------|---------|---------| -| `HOMEBREW_TAP_APP_CLIENT_ID` | `release.yml` (`publish-homebrew-tap`) | Client ID of the GitHub App authorized to push to `dataiku/homebrew-tap` | +**Secrets (required):** -### Secrets (Settings → Secrets and variables → Actions → Secrets) +| Secret | Purpose | +|--------|---------| +| `HOMEBREW_TAP_APP_PRIVATE_KEY` | PEM private key (begins with `-----BEGIN RSA PRIVATE KEY-----`) downloaded from the GitHub App settings page. | + +See [§12 Homebrew Tap Publishing](#12-homebrew-tap-publishing) for the App setup and tap-repo prerequisites these values plug into. + +--- + +## 3. Repository Variables and Secrets -| Secret | Used by | Purpose | -|--------|---------|---------| -| `HOMEBREW_TAP_APP_PRIVATE_KEY` | `release.yml` (`publish-homebrew-tap`) | PEM private key (begins with `-----BEGIN RSA PRIVATE KEY-----`) downloaded from the GitHub App settings page | +`GITHUB_TOKEN` is provided automatically by GitHub Actions and is sufficient for the workflows that don't declare an `environment:`. **Optional:** - `SIGNING_PRIVATE_KEY` — referenced by `sign-model.yml` if it is added back to the repo (the workflow is documented in the workflows README but not currently present here). -See [§12 Homebrew Tap Publishing](#12-homebrew-tap-publishing) for the App setup that these values plug into. No PATs are required — the App scope replaces what a PAT would have done. +All other credentials live in environments — see §2. No PATs are required anywhere. --- @@ -115,7 +128,7 @@ See [§12 Homebrew Tap Publishing](#12-homebrew-tap-publishing) for the App setu | `release.yml` (build-linux) | same as above | `contents: read` | — | — | `ubuntu-latest` (container: `almalinux:9`) | | `release.yml` (build-chrome) | same as above | `contents: read` | — | — | `ubuntu-latest` | | `release.yml` (create-release) | same as above (needs all 3 builds) | `contents: write` | — | `GITHUB_TOKEN` | `ubuntu-latest` | -| `release.yml` (publish-homebrew-tap) | same as above (needs `create-release`); skips alpha/beta/rc | `contents: read` | — | `HOMEBREW_TAP_APP_PRIVATE_KEY`, `vars.HOMEBREW_TAP_APP_CLIENT_ID` | `ubuntu-latest` | +| `release.yml` (publish-homebrew-tap) | same as above (needs `create-release`); skips alpha/beta/rc | `contents: read` | `Homebrew Release` | `HOMEBREW_TAP_APP_PRIVATE_KEY`, `vars.HOMEBREW_TAP_APP_CLIENT_ID` (env-scoped) | `ubuntu-latest` | | `lint-and-test.yml` | `push`/`pull_request` on `main`/`develop` | default | — | — | `ubuntu-latest` (×5 jobs) | | `semantic-pr.yml` | `pull_request_target`: opened/edited/synchronize | `pull-requests: write`, `contents: read` | — | `GITHUB_TOKEN` | `ubuntu-latest` | | `cleanup-artifacts.yml` | daily 02:00 UTC, `workflow_dispatch` | `actions: write` | — | — | `ubuntu-latest` | @@ -208,13 +221,14 @@ The CLA document itself is pinned by commit SHA in the workflow — updating the 1. Create (or reuse) a GitHub App owned by the `dataiku` org. 2. **Repository permissions:** `Contents: Read and write`. No other scopes required. 3. **Install** the App on `dataiku/homebrew-tap` only. Do not install it on `dataiku/kiji-proxy`. The token minted in the workflow is scoped to the tap repo via the `owner` + `repositories` inputs to `actions/create-github-app-token@v2`. -4. Generate a private key on the App settings page and store the full PEM (including the `-----BEGIN/END-----` lines) as the `HOMEBREW_TAP_APP_PRIVATE_KEY` repository secret on `dataiku/kiji-proxy`. -5. Copy the App's **Client ID** from the App settings page and store it as the `HOMEBREW_TAP_APP_CLIENT_ID` repository variable. The Client ID is used in preference to the numeric App ID since the latter is being phased out for new App auth surfaces; the action's `app-id` input accepts either. +4. Generate a private key on the App settings page and store the full PEM (including the `-----BEGIN/END-----` lines) as `HOMEBREW_TAP_APP_PRIVATE_KEY` on the `Homebrew Release` environment in `dataiku/kiji-proxy` (see [§2 Homebrew Release](#homebrew-release)) — **not** as a repo-level secret, so the credential is only resolvable by jobs that explicitly opt into the environment. +5. Copy the App's **Client ID** from the App settings page and store it as `HOMEBREW_TAP_APP_CLIENT_ID` on the same environment. The Client ID is used in preference to the numeric App ID since the latter is being phased out for new App auth surfaces; the action's `app-id` input accepts either. ### Tap-repo prerequisites -- Default branch is `main`. -- A `Casks/` directory exists at the repo root (cask target path: `Casks/kiji-privacy-proxy.rb`). +- Repo must be initialized — at least one commit on `main`. A fresh empty repo will fail `actions/checkout` with `couldn't find remote ref refs/heads/main`. +- Default branch is `main`. If you use a different default, update both the checkout target and the final `git push origin HEAD:main` in `release.yml` accordingly. +- A `Casks/` directory exists at the repo root (cask target path: `Casks/kiji-privacy-proxy.rb`). The publish job creates the directory if missing, but pre-creating it keeps the first run noise-free. - Branch protection on `main`, if enabled, must either include the App in bypass actors or be relaxed enough for direct pushes by the bot. If you want to keep strict protection, change the final `git push origin HEAD:main` step in `release.yml` to open a PR using the same App token instead. ### Cask template @@ -239,4 +253,8 @@ Edit the template in-repo to change cask metadata (description, zap paths, depen - [ ] Configure branch protection on `main` with the status checks listed above - [ ] Ensure the `cla-signatures` branch is not protected - [ ] (Optional) Adjust `dependabot.yml` schedule/timezone to your team -- [ ] (For Homebrew distribution) Create a GitHub App with `Contents: read & write`, install it on your fork's tap repo, then set the `HOMEBREW_TAP_APP_CLIENT_ID` variable and `HOMEBREW_TAP_APP_PRIVATE_KEY` secret on the source repo. Update `actions/create-github-app-token` inputs (`owner`, `repositories`) and the `git push` target if your tap lives elsewhere. +- [ ] (For Homebrew distribution) + - Create a GitHub App with `Contents: read & write` and install it on your fork's tap repo only. + - Create the `Homebrew Release` environment and add `HOMEBREW_TAP_APP_CLIENT_ID` (variable) and `HOMEBREW_TAP_APP_PRIVATE_KEY` (secret) under it — not at the repo level. + - Initialize the tap repo with a `main` branch and a `Casks/` directory. + - Update `actions/create-github-app-token` inputs (`owner`, `repositories`) and the `git push` target in `release.yml` if your tap lives somewhere other than `dataiku/homebrew-tap`.