name: update-pkgs # Daily package and flake.lock updates, one PR per package: # # - update (one job per package): run its passthru.updateScript through # tools/update-pkgs, build the package on its platform (darwin-only # packages on macOS), push update/ on top of the current master and # open its PR. # - lock: `nix flake update`, pushed as update/flake-lock with its own PR. # # A branch whose tree already equals the new commit is left alone, so a # failing PR isn't built again until upstream or master changes. Otherwise # it is replaced by the new commit, which keeps open PRs rebased onto master. # # Pushes, PRs and auto-merge use the UPDATE_PKGS_TOKEN secret (fine-grained # PAT or GitHub App token with contents, pull requests and workflows # read/write). Unlike GITHUB_TOKEN, its pushes run ci.yml on the PR, and # GitHub merges the PR with a merge commit once master's ruleset (the ci # check) is met. That merge counts as the token's, so ci.yml runs on master # as well. Needs "Allow auto-merge" in the repo settings. on: schedule: - cron: "0 3 * * *" # daily 03:00 UTC workflow_dispatch: inputs: packages: description: Packages to update, space separated (empty for all) default: "" automerge: description: Enable auto-merge on the PRs type: boolean default: true permissions: contents: read concurrency: group: update-pkgs cancel-in-progress: false env: NIX_CONFIG: | extra-substituters = https://cache.lassul.us extra-trusted-public-keys = cache.lassul.us-1:aOMEO5TU6eLvhyPi0jmgjR2sVlGjMq3rK0MeACjTcQ0= jobs: list: runs-on: ubuntu-latest outputs: matrix: ${{ steps.list.outputs.matrix }} steps: - env: TOKEN: ${{ secrets.UPDATE_PKGS_TOKEN }} run: | if [ -z "$TOKEN" ]; then echo "::error::secret UPDATE_PKGS_TOKEN is not set (fine-grained PAT or GitHub App token with contents, pull requests and workflows read/write)" exit 1 fi - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v31 - id: list env: PACKAGES: ${{ inputs.packages }} run: | names() { nix eval --json --impure --apply builtins.attrNames --expr \ "import ./tools/update-pkgs/scripts.nix { flake = toString ./.; host = \"$1\"; }" } # Linux packages update and build on ubuntu; the ones that only # exist for darwin on macOS. matrix=$(jq -cn --argjson linux "$(names x86_64-linux)" \ --argjson all "$(names aarch64-darwin)" --arg only "$PACKAGES" ' ($only | split(" ") | map(select(. != ""))) as $want | [ $all[] | select(($want | length) == 0 or (. as $p | $want | index($p))) | if (. as $p | $linux | index($p)) then { pkg: ., os: "ubuntu-latest", system: "x86_64-linux" } else { pkg: ., os: "macos-latest", system: "aarch64-darwin" } end ]') echo "matrix=$matrix" >> "$GITHUB_OUTPUT" echo "$matrix" | jq . update: needs: list if: needs.list.outputs.matrix != '[]' name: ${{ matrix.pkg }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: ${{ fromJSON(needs.list.outputs.matrix) }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.UPDATE_PKGS_TOKEN }} - uses: cachix/install-nix-action@v31 - env: PKG: ${{ matrix.pkg }} SYSTEM: ${{ matrix.system }} GH_TOKEN: ${{ secrets.UPDATE_PKGS_TOKEN }} # nix-update and the update scripts query the GitHub API GITHUB_TOKEN: ${{ github.token }} AUTOMERGE: ${{ github.event_name == 'schedule' || inputs.automerge }} run: | version() { nix eval --raw ".#packages.$SYSTEM.$PKG.version" 2>/dev/null || echo unknown; } old=$(version) nix run .#update-pkgs -- "$PKG" if [ -z "$(git status --porcelain)" ]; then echo "$PKG: no update" exit 0 fi nix build -L --no-link ".#packages.$SYSTEM.$PKG" new=$(version) prefix=pkgs [ -d "pkgs/$PKG" ] || prefix=tools if [ "$old" != "$new" ]; then msg="$prefix.$PKG: $old -> $new" else msg="$prefix.$PKG: update" fi branch="update/$PKG" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git switch -c "$branch" git add -A git commit -m "$msg" if git fetch -q origin "$branch" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then echo "$PKG: $branch already has this update on this master" else git push --force origin "$branch" fi if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then gh pr create --base master --head "$branch" --title "$msg" \ --body "Automated update by the update-pkgs workflow (\`nix run .#update-pkgs -- $PKG\`), package built on $SYSTEM. Merged automatically once ci (including the build of all machines) passed." fi if [ "$AUTOMERGE" = true ]; then gh pr merge "$branch" --auto --merge --delete-branch fi lock: needs: list # whole runs only, not manual runs for selected packages if: inputs.packages == '' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.UPDATE_PKGS_TOKEN }} - uses: cachix/install-nix-action@v31 - env: GH_TOKEN: ${{ secrets.UPDATE_PKGS_TOKEN }} GITHUB_TOKEN: ${{ github.token }} AUTOMERGE: ${{ github.event_name == 'schedule' || inputs.automerge }} run: | export NIX_CONFIG="$NIX_CONFIG"$'\n'"access-tokens = github.com=$GITHUB_TOKEN" nix flake update 2>&1 | tee "$RUNNER_TEMP/update.log" if git diff --quiet -- flake.lock; then echo "flake.lock: no update" exit 0 fi msg="flake.lock: update" branch="update/flake-lock" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git switch -c "$branch" git commit -m "$msg" -- flake.lock if git fetch -q origin "$branch" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then echo "flake.lock: $branch already has this update on this master" else git push --force origin "$branch" fi { echo "Automated \`nix flake update\` by the update-pkgs workflow. Merged automatically once ci (including the build of all machines) passed." echo echo '```' sed -n '/^• /,$p' "$RUNNER_TEMP/update.log" echo '```' } >"$RUNNER_TEMP/body.md" pr=$(gh pr list --head "$branch" --state open --json number --jq '.[].number') if [ -z "$pr" ]; then gh pr create --base master --head "$branch" --title "$msg" --body-file "$RUNNER_TEMP/body.md" else gh pr edit "$pr" --body-file "$RUNNER_TEMP/body.md" fi if [ "$AUTOMERGE" = true ]; then gh pr merge "$branch" --auto --merge --delete-branch fi