This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

.github.update-pkgs: let PRs run ci themselves and merge via auto-merge

Branches, PRs and auto-merge now use the UPDATE_PKGS_TOKEN repo secret
instead of GITHUB_TOKEN, so the normal pull_request ci.yml run (with the
farm build) shows up on each PR and GitHub merges it with a merge commit
once the ruleset is met. The serial verify job (rebase, dispatch ci.yml,
wait, merge) and the final ci dispatch on master are gone: the auto-merge
counts as the token's, so the push to master triggers ci.yml itself.

UPDATE_PKGS_TOKEN: fine-grained PAT (or GitHub App token) for this repo
with Contents: read/write, Pull requests: read/write and Workflows:
read/write (force-pushing an update branch rebased over master commits that
touched .github/workflows is refused without it). The repo needs "Allow
auto-merge" enabled. The workflow fails early if the secret is missing.

+44 -137
+2 -2
AGENTS.md
··· 94 94 95 95 `--rebase` keeps the individual commits on master. Only the user (repo admin) 96 96 bypasses the ruleset. The `ci` check includes a farm build of all machines 97 - (farm.yml, called from ci.yml). The update-pkgs bot merges its PRs through 98 - the ruleset as well, after dispatching ci.yml on them. 97 + (farm.yml, called from ci.yml). The update-pkgs bot's PRs go through 98 + the ruleset as well: they run ci.yml like any PR and merge via auto-merge. 99 99 100 100 ## Machine Facts and Secrets via vars 101 101
-2
.github/workflows/ci.yml
··· 4 4 push: 5 5 branches: [master] 6 6 pull_request: 7 - # update-pkgs runs it on its PR branches: pushes and PRs made with 8 - # GITHUB_TOKEN don't trigger workflows, and master requires the ci check. 9 7 workflow_dispatch: 10 8 11 9 # A newer run for the same ref replaces a running one, its farm included.
+42 -133
.github/workflows/update-pkgs.yml
··· 1 1 name: update-pkgs 2 2 3 - # Daily package and flake.lock updates. Nothing reaches master without a full 4 - # farm build: 3 + # Daily package and flake.lock updates, one PR per package: 5 4 # 6 - # 1. update (one job per package): run its passthru.updateScript through 7 - # tools/update-pkgs, build the package on its platform (darwin-only 8 - # packages on macOS), push update/<pkg> and open or refresh its PR. 9 - # lock: `nix flake update`, pushed as update/flake-lock with its own PR. 10 - # 2. verify: for every open update/* PR, one after another (flake.lock 11 - # first), rebase it onto the current master, run ci.yml on that commit 12 - # (it builds all machines with the farm) and merge the PR with a merge 13 - # commit only if it succeeded (master's ruleset requires its ci check). A 14 - # commit that already has a finished run is not checked again, so a 15 - # failing PR stays open until its branch or master changes. 5 + # - update (one job per package): run its passthru.updateScript through 6 + # tools/update-pkgs, build the package on its platform (darwin-only 7 + # packages on macOS), push update/<pkg> on top of the current master and 8 + # open its PR. 9 + # - lock: `nix flake update`, pushed as update/flake-lock with its own PR. 16 10 # 17 - # One PR at a time: the farm builds one commit at a time anyway, and each PR 18 - # is rebased onto the master it gets merged into. 11 + # A branch whose tree already equals the new commit is left alone, so a 12 + # failing PR isn't built again until upstream or master changes. Otherwise 13 + # it is replaced by the new commit, which keeps open PRs rebased onto master. 19 14 # 20 - # Needs "Allow GitHub Actions to create and approve pull requests" in the repo 21 - # settings. Merges made with GITHUB_TOKEN don't trigger push workflows, so the 22 - # ci run (farm build) of master after merging is dispatched here as well. 15 + # Pushes, PRs and auto-merge use the UPDATE_PKGS_TOKEN secret (fine-grained 16 + # PAT or GitHub App token with contents, pull requests and workflows 17 + # read/write). Unlike GITHUB_TOKEN, its pushes run ci.yml on the PR, and 18 + # GitHub merges the PR with a merge commit once master's ruleset (the ci 19 + # check) is met. That merge counts as the token's, so ci.yml runs on master 20 + # as well. Needs "Allow auto-merge" in the repo settings. 23 21 24 22 on: 25 23 schedule: ··· 30 28 description: Packages to update, space separated (empty for all) 31 29 default: "" 32 30 automerge: 33 - description: Merge PRs whose farm build succeeded 31 + description: Enable auto-merge on the PRs 34 32 type: boolean 35 33 default: true 36 34 37 35 permissions: 38 - contents: write 39 - pull-requests: write 40 - actions: write # dispatch and watch ci runs 36 + contents: read 41 37 42 38 concurrency: 43 39 group: update-pkgs ··· 54 50 outputs: 55 51 matrix: ${{ steps.list.outputs.matrix }} 56 52 steps: 53 + - env: 54 + TOKEN: ${{ secrets.UPDATE_PKGS_TOKEN }} 55 + run: | 56 + if [ -z "$TOKEN" ]; then 57 + echo "::error::secret UPDATE_PKGS_TOKEN is not set (fine-grained PAT or GitHub App token with contents, pull requests and workflows read/write)" 58 + exit 1 59 + fi 57 60 - uses: actions/checkout@v4 58 61 - uses: cachix/install-nix-action@v31 59 62 - id: list ··· 91 94 - uses: actions/checkout@v4 92 95 with: 93 96 fetch-depth: 0 97 + token: ${{ secrets.UPDATE_PKGS_TOKEN }} 94 98 - uses: cachix/install-nix-action@v31 95 99 - env: 96 100 PKG: ${{ matrix.pkg }} 97 101 SYSTEM: ${{ matrix.system }} 98 - GH_TOKEN: ${{ github.token }} 102 + GH_TOKEN: ${{ secrets.UPDATE_PKGS_TOKEN }} 99 103 # nix-update and the update scripts query the GitHub API 100 104 GITHUB_TOKEN: ${{ github.token }} 105 + AUTOMERGE: ${{ github.event_name == 'schedule' || inputs.automerge }} 101 106 run: | 102 107 version() { nix eval --raw ".#packages.$SYSTEM.$PKG.version" 2>/dev/null || echo unknown; } 103 108 old=$(version) ··· 126 131 git add -A 127 132 git commit -m "$msg" 128 133 129 - # Same change as the branch already carries (e.g. a PR whose farm 130 - # build failed and upstream hasn't moved): keep its commit, so 131 - # verify doesn't build it again. 132 - if git fetch -q origin "$branch" 2>/dev/null && 133 - git diff --quiet FETCH_HEAD HEAD -- "$prefix/$PKG"; then 134 - echo "$PKG: $branch already has this update" 134 + if git fetch -q origin "$branch" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then 135 + echo "$PKG: $branch already has this update on this master" 135 136 else 136 137 git push --force origin "$branch" 137 138 fi 138 139 139 140 if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then 140 141 gh pr create --base master --head "$branch" --title "$msg" \ 141 - --body "Automated update by the update-pkgs workflow (\`nix run .#update-pkgs -- $PKG\`), package built on $SYSTEM. Merged automatically once the farm has built all machines from this branch." 142 + --body "Automated update by the update-pkgs workflow (\`nix run .#update-pkgs -- $PKG\`), package built on $SYSTEM. Merged automatically once ci (including the farm build of all machines) passed." 143 + fi 144 + if [ "$AUTOMERGE" = true ]; then 145 + gh pr merge "$branch" --auto --merge --delete-branch 142 146 fi 143 147 144 148 lock: 149 + needs: list 145 150 # whole runs only, not manual runs for selected packages 146 151 if: inputs.packages == '' 147 152 runs-on: ubuntu-latest ··· 149 154 - uses: actions/checkout@v4 150 155 with: 151 156 fetch-depth: 0 157 + token: ${{ secrets.UPDATE_PKGS_TOKEN }} 152 158 - uses: cachix/install-nix-action@v31 153 159 - env: 154 - GH_TOKEN: ${{ github.token }} 160 + GH_TOKEN: ${{ secrets.UPDATE_PKGS_TOKEN }} 155 161 GITHUB_TOKEN: ${{ github.token }} 162 + AUTOMERGE: ${{ github.event_name == 'schedule' || inputs.automerge }} 156 163 run: | 157 164 export NIX_CONFIG="$NIX_CONFIG"$'\n'"access-tokens = github.com=$GITHUB_TOKEN" 158 165 nix flake update 2>&1 | tee "$RUNNER_TEMP/update.log" ··· 168 175 git switch -c "$branch" 169 176 git commit -m "$msg" -- flake.lock 170 177 171 - # Same lock as the branch already carries: keep its commit, so 172 - # verify doesn't build it again. 173 - if git fetch -q origin "$branch" 2>/dev/null && 174 - git diff --quiet FETCH_HEAD HEAD -- flake.lock; then 175 - echo "flake.lock: $branch already has this update" 178 + if git fetch -q origin "$branch" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then 179 + echo "flake.lock: $branch already has this update on this master" 176 180 else 177 181 git push --force origin "$branch" 178 182 fi 179 183 180 184 { 181 - echo "Automated \`nix flake update\` by the update-pkgs workflow. Merged automatically once the farm has built all machines from this branch." 185 + echo "Automated \`nix flake update\` by the update-pkgs workflow. Merged automatically once ci (including the farm build of all machines) passed." 182 186 echo 183 187 echo '```' 184 188 sed -n '/^• /,$p' "$RUNNER_TEMP/update.log" ··· 190 194 else 191 195 gh pr edit "$pr" --body-file "$RUNNER_TEMP/body.md" 192 196 fi 193 - 194 - verify: 195 - needs: [update, lock] 196 - if: always() && needs.update.result != 'cancelled' && needs.lock.result != 'cancelled' 197 - runs-on: ubuntu-latest 198 - timeout-minutes: 350 199 - steps: 200 - - uses: actions/checkout@v4 201 - with: 202 - fetch-depth: 0 203 - - env: 204 - GH_TOKEN: ${{ github.token }} 205 - AUTOMERGE: ${{ github.event_name == 'schedule' || inputs.automerge }} 206 - run: | 207 - repo=$GITHUB_REPOSITORY 208 - merged=0 209 - git config user.name "github-actions[bot]" 210 - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 211 - 212 - # Dispatched run of a workflow for a head commit: "<id> <status> 213 - # <conclusion>" or empty. Cancelled runs don't count: a newer run 214 - # for the branch replaced them. 215 - dispatched_run() { 216 - gh run list -R "$repo" -w "$1" -b "$2" -e workflow_dispatch -L 20 \ 217 - --json databaseId,headSha,status,conclusion \ 218 - --jq "[.[] | select(.headSha == \"$3\" and .conclusion != \"cancelled\")][0] // empty 219 - | \"\(.databaseId) \(.status) \(.conclusion)\"" 220 - } 221 - 222 - # Dispatch a workflow on a branch unless its head commit already 223 - # has a run, wait for it and print its conclusion, or "nostart". 224 - run_workflow() { 225 - local workflow=$1 branch=$2 sha=$3 run id 226 - run=$(dispatched_run "$workflow" "$branch" "$sha") 227 - if [ -z "$run" ]; then 228 - gh workflow run "$workflow" -R "$repo" --ref "$branch" >&2 229 - for _ in $(seq 60); do 230 - sleep 10 231 - run=$(dispatched_run "$workflow" "$branch" "$sha") 232 - [ -n "$run" ] && break 233 - done 234 - fi 235 - id=${run%% *} 236 - if [ -z "$id" ]; then 237 - echo nostart 238 - return 239 - fi 240 - echo "$workflow run $id on $sha" >&2 241 - gh run watch "$id" -R "$repo" --interval 60 >/dev/null 2>&1 </dev/null || true 242 - gh run view "$id" -R "$repo" --json conclusion --jq .conclusion 243 - } 244 - 245 - # Rebase a PR branch onto master and run ci.yml (with the farm 246 - # build) on that commit. Prints "<conclusion> <sha>"; conclusion is 247 - # the ci run's, or "conflict" / "nostart". 248 - verify_pr() { 249 - local branch=$1 sha conclusion 250 - git fetch -q origin master "$branch" 251 - git switch -q -C "$branch" "origin/$branch" 252 - if ! git rebase -q origin/master >&2; then 253 - git rebase --abort 254 - echo "conflict -" 255 - return 256 - fi 257 - sha=$(git rev-parse HEAD) 258 - if [ "$sha" != "$(git rev-parse "origin/$branch")" ]; then 259 - git push -q --force-with-lease="$branch:origin/$branch" origin "$branch" >&2 260 - fi 261 - conclusion=$(run_workflow ci.yml "$branch" "$sha") 262 - echo "$conclusion $sha" 263 - } 264 - 265 - # flake.lock first: every package PR is rebased onto the master it 266 - # gets merged into right before its build, so after the lock merge 267 - # the packages are built once, against the new lock, and the final 268 - # ci run on master only hits the cache. 269 - prs=$(gh pr list -R "$repo" --state open --base master --limit 100 \ 270 - --json number,headRefName \ 271 - --jq '[.[] | select(.headRefName | startswith("update/"))] 272 - | sort_by(.headRefName != "update/flake-lock") | .[] 273 - | "\(.number) \(.headRefName)"') 274 - 275 - while read -r pr branch <&3; do 276 - [ -n "$pr" ] || continue 277 - echo "::group::#$pr $branch" 278 - read -r conclusion sha < <(verify_pr "$branch") 279 - echo "#$pr: $conclusion" 280 - if [ "$conclusion" = success ] && [ "$AUTOMERGE" = true ]; then 281 - gh pr merge "$pr" -R "$repo" --merge --delete-branch --match-head-commit "$sha" 282 - merged=$((merged + 1)) 283 - fi 284 - echo "::endgroup::" 285 - done 3<<<"$prs" 286 - 287 - echo "merged update PRs: $merged" 288 - if [ "$merged" -gt 0 ]; then 289 - gh workflow run ci.yml -R "$repo" --ref master 197 + if [ "$AUTOMERGE" = true ]; then 198 + gh pr merge "$branch" --auto --merge --delete-branch 290 199 fi