ci(release): open the packaging PR with a release GitHub App token #86
No files matched your search
@@ -24,14 +24,17 @@
|
||||
# published stable installer, so the job runs once the WINGET_PACKAGE_ID variable
|
||||
# is set (docs/RELEASING.md) and fails if the token is missing from then on. The
|
||||
# tokens live in the `release` environment, which only v* tags can use:
|
||||
# DEPS_PAT fine-grained token: contents + pull requests (write) on this
|
||||
# repository, so the packaging PR runs the required checks
|
||||
# RELEASE_APP_PRIVATE_KEY private key of the release GitHub App (contents +
|
||||
# pull requests write on this repository only); the job mints a
|
||||
# short-lived token from it, so the packaging PR's commit is
|
||||
# signed by GitHub and the PR runs the required checks
|
||||
# CHOCO_API_KEY chocolatey.org API key
|
||||
# WINGET_TOKEN fine-grained token that can fork and open pull requests on
|
||||
# public repositories (microsoft/winget-pkgs)
|
||||
# NUGET_API_KEY nuget.org key with push rights for gclo.Engine (NUGET_PUBLISH)
|
||||
# plus the repository variable WINGET_PACKAGE_ID (KofTwentyTwo.gclo, set after the
|
||||
# one-time manual `wingetcreate new`).
|
||||
# plus the repository variables RELEASE_APP_CLIENT_ID (the release App's client id)
|
||||
# and WINGET_PACKAGE_ID (KofTwentyTwo.gclo, set after the one-time manual
|
||||
# `wingetcreate new`).
|
||||
name: Release
|
||||
|
||||
on:
|
||||
@@ -136,13 +139,14 @@ jobs:
|
||||
- name: Require the publishing secrets
|
||||
shell: pwsh
|
||||
env:
|
||||
DEPS_PAT: ${{ secrets.DEPS_PAT }}
|
||||
RELEASE_APP_CLIENT_ID: ${{ vars.RELEASE_APP_CLIENT_ID }}
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
|
||||
run: |
|
||||
$missing = @('DEPS_PAT', 'CHOCO_API_KEY') | Where-Object { [string]::IsNullOrEmpty((Get-Item -Path "env:$_" -ErrorAction SilentlyContinue).Value) }
|
||||
$missing = @('RELEASE_APP_CLIENT_ID', 'RELEASE_APP_PRIVATE_KEY', 'CHOCO_API_KEY') | Where-Object { [string]::IsNullOrEmpty((Get-Item -Path "env:$_" -ErrorAction SilentlyContinue).Value) }
|
||||
if ($missing)
|
||||
{
|
||||
Write-Output "::error::Missing in the release environment: $($missing -join ', '). Add them (docs/RELEASING.md) and re-run this job."
|
||||
Write-Output "::error::Missing in the release environment (or repository variables): $($missing -join ', '). Add them (docs/RELEASING.md) and re-run this job."
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -173,19 +177,28 @@ jobs:
|
||||
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
|
||||
run: choco push (Get-ChildItem artifacts/chocolatey/*.nupkg | Select-Object -First 1).FullName --source https://push.chocolatey.org/ --api-key $env:CHOCO_API_KEY
|
||||
|
||||
# protect-main requires signed commits and a DCO sign-off. The action only
|
||||
# signs with a bot token (a PAT is explicitly unsupported), so the PR is
|
||||
# opened with a short-lived token of the release GitHub App: GitHub signs
|
||||
# the commit, its author is the App's [bot] user (exempt from the DCO
|
||||
# check), and the PR still triggers the required checks (#81, #85).
|
||||
- name: Mint a release App token
|
||||
id: app
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Open a PR with the updated package manifests
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.DEPS_PAT }}
|
||||
token: ${{ steps.app.outputs.token }}
|
||||
sign-commits: true
|
||||
base: main
|
||||
branch: packaging/v${{ needs.release.outputs.version }}
|
||||
delete-branch: true
|
||||
# The pr / dco gate wants a Signed-off-by whose email matches the commit
|
||||
# author, so author and committer are the same identity (the actor who
|
||||
# pushed the tag) and the action signs off as that committer (#81).
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
committer: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: true
|
||||
commit-message: 'build(packaging): point the Scoop and Chocolatey manifests at v${{ needs.release.outputs.version }}'
|
||||
title: 'build(packaging): point the Scoop and Chocolatey manifests at v${{ needs.release.outputs.version }}'
|
||||
body: |
|
||||
|
||||
+11
-3
@@ -65,12 +65,20 @@ its first submission cannot be automated; see below.
|
||||
| Job | Runs for | Needs | Publishes |
|
||||
| --- | -------- | ----- | --------- |
|
||||
| `release` (shared workflow) | every tag | nothing beyond `GITHUB_TOKEN` | the GitHub Release: Setup, portable, full/delta packages, feed, CLI zip, SBOMs, `SHA256SUMS`, provenance and SBOM attestations |
|
||||
| `packages` | stable only | `DEPS_PAT` (fine-grained token: contents and pull requests, write, this repository, ≤ 90 days), `CHOCO_API_KEY` (chocolatey.org) | the Chocolatey package pushed to chocolatey.org, and a `build(packaging): …` pull request against `main` with `bucket/gclo.json` and `packaging/chocolatey` pointed at the release |
|
||||
| `packages` | stable only | `RELEASE_APP_PRIVATE_KEY` (private key of the release GitHub App, see below) plus the repository variable `RELEASE_APP_CLIENT_ID`, `CHOCO_API_KEY` (chocolatey.org) | the Chocolatey package pushed to chocolatey.org, and a `build(packaging): …` pull request against `main` with `bucket/gclo.json` and `packaging/chocolatey` pointed at the release |
|
||||
| `winget` | stable only, once the repository variable `WINGET_PACKAGE_ID` is set (`KofTwentyTwo.gclo`, after the one-time manual submission below) | `WINGET_TOKEN` (fine-grained token that can fork and open pull requests on public repositories, ≤ 90 days); missing → the job fails | a manifest update PR on [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs) |
|
||||
| `nuget` | only when the repository variable `NUGET_PUBLISH` is `true` | `NUGET_API_KEY` (nuget.org, push rights for `gclo.Engine`) | `gclo.Engine` on nuget.org |
|
||||
|
||||
Why `DEPS_PAT`: a pull request opened with the default `GITHUB_TOKEN` triggers no
|
||||
status checks, so the packaging PR could never satisfy `main`'s required checks.
|
||||
Why a GitHub App: `main` requires signed commits, a DCO sign-off, and the full set
|
||||
of status checks. A pull request opened with the default `GITHUB_TOKEN` triggers
|
||||
no checks, and the action that opens the PR cannot sign commits with a personal
|
||||
access token. With a short-lived token minted from the App, GitHub signs the
|
||||
commit, its author is the App's `[bot]` user (exempt from the DCO check), and the
|
||||
checks run. Create the App once (the same pattern the standards repository uses):
|
||||
GitHub App installed **only on this repository**, permissions Contents and Pull
|
||||
requests **write**, no ruleset bypass. Store its client id as the Actions variable
|
||||
`RELEASE_APP_CLIENT_ID` and its private key (`.pem`) as `RELEASE_APP_PRIVATE_KEY`
|
||||
in the `release` environment; never as a repository secret or a committed file.
|
||||
|
||||
## First winget submission (one-time, manual)
|
||||
|
||||
|
||||
@@ -47,7 +47,9 @@ and hash; `PackagingManifestTests` keeps both manifests valid and in agreement.
|
||||
- Stable releases are consumable via `scoop bucket add gclo <repo>` with no external
|
||||
account; prereleases never touch package managers.
|
||||
- The generated manifest pull request targets `main` and goes through the normal
|
||||
gates (with `DEPS_PAT` it also gets status checks).
|
||||
gates: it is opened with a short-lived release GitHub App token, so its commit
|
||||
is signed by GitHub, exempt from the DCO check as a bot, and runs the required
|
||||
status checks (#85).
|
||||
- Security: a new distribution channel; both channels verify the asset hash
|
||||
(threat model T2), and the hash they pin is the one `SHA256SUMS` attests.
|
||||
- Follow-up: `CHOCO_API_KEY` and the first Chocolatey moderation (#7).
|
||||
@@ -111,3 +111,4 @@ flowchart LR
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-10-09 | v1.0.1 | James Maes (@KofTwentyTwo) | Initial model, covering the path-recovery, WSL clone, vault scoping, package-manager, and SBOM changes of 1.0.1 |
|
||||
| 2026-10-09 | v1.0.1 | James Maes (@KofTwentyTwo) | T11 updated: releases build in the shared standards workflow; publishing tokens moved to the `release` environment (#76) |
|
||||
| 2026-10-10 | v1.0.2 | James Maes (@KofTwentyTwo) | T11: the packaging PR is opened with a short-lived release GitHub App token (contents + pull requests on this repository, no bypass) instead of a long-lived PAT; the commit is GitHub-signed (#85) |
|
||||
Reference in new issue
Block a user