update action
NixOS CI / Validate flake (push) Has been cancelled
NixOS CI / Build ${{ matrix.host }} (push) Has been cancelled
NixOS CI / Report main status (push) Has been cancelled

This commit is contained in:
2026-07-24 11:44:14 +09:00
parent 873223cb78
commit 4487c38f0b
4 changed files with 369 additions and 21 deletions
@@ -0,0 +1,260 @@
name: Update Flake Input
description: Update one GitHub-backed Nix flake input and create a pull request
inputs:
input-name:
description: Name of the flake input to update
required: true
github-token:
description: Token used to query GitHub, push the update branch, and manage the pull request
required: true
base-branch:
description: Branch targeted by the pull request
required: false
default: main
minimum-release-age-days:
description: Minimum age of the target commit in days
required: false
default: "3"
skip-delay:
description: Update to the latest revision without applying the minimum age
required: false
default: "false"
auto-merge:
description: Enable squash auto-merge on the pull request
required: false
default: "true"
pr-labels:
description: Comma-separated labels to add when they already exist in the repository
required: false
default: dependencies,automated
outputs:
updated:
description: Whether flake.lock changed
value: ${{ steps.update.outputs.updated }}
current-version:
description: Previous locked revision
value: ${{ steps.update.outputs.current_version }}
new-version:
description: New locked revision
value: ${{ steps.update.outputs.new_version }}
pr-url:
description: URL of the created or updated pull request
value: ${{ steps.pull-request.outputs.pr_url }}
runs:
using: composite
steps:
- name: Update flake input
id: update
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
INPUT_NAME: ${{ inputs.input-name }}
MINIMUM_RELEASE_AGE_DAYS: ${{ inputs.minimum-release-age-days }}
SKIP_DELAY: ${{ inputs.skip-delay }}
run: |
set -euo pipefail
if [[ ! "$MINIMUM_RELEASE_AGE_DAYS" =~ ^[0-9]+$ ]]; then
echo "::error::minimum-release-age-days must be a non-negative integer"
exit 1
fi
node_key="$(
jq -er --arg input "$INPUT_NAME" '
.nodes.root.inputs[$input]
| if type == "array" then .[0] else . end
' flake.lock
)"
input_type="$(jq -r --arg node "$node_key" '.nodes[$node].locked.type // ""' flake.lock)"
input_owner="$(jq -r --arg node "$node_key" '.nodes[$node].locked.owner // ""' flake.lock)"
input_repo="$(jq -r --arg node "$node_key" '.nodes[$node].locked.repo // ""' flake.lock)"
input_ref="$(jq -r --arg node "$node_key" '.nodes[$node].original.ref // ""' flake.lock)"
current_rev="$(jq -er --arg node "$node_key" '.nodes[$node].locked.rev' flake.lock)"
if [ "$input_type" != "github" ] || [ -z "$input_owner" ] || [ -z "$input_repo" ]; then
echo "::error::${INPUT_NAME} is not a GitHub-backed flake input"
exit 1
fi
echo "Input: $INPUT_NAME"
echo "Repository: ${input_owner}/${input_repo}"
echo "Current revision: $current_rev"
if [ "$SKIP_DELAY" = "true" ]; then
nix flake update "$INPUT_NAME"
else
cutoff="$(date --utc --date="${MINIMUM_RELEASE_AGE_DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ)"
api_args=(
--method GET
"repos/${input_owner}/${input_repo}/commits"
-f "until=$cutoff"
-f per_page=1
)
if [ -n "$input_ref" ]; then
api_args+=(-f "sha=$input_ref")
fi
echo "Selecting the newest commit no later than $cutoff"
target_data="$(gh api "${api_args[@]}" --jq '.[0] | {sha: .sha, date: .commit.committer.date}')"
target_rev="$(jq -er '.sha' <<< "$target_data")"
target_date="$(jq -er '.date' <<< "$target_data")"
if [ "$target_rev" = "$current_rev" ]; then
echo "The input is already at the newest eligible revision"
{
echo "updated=false"
echo "current_version=$current_rev"
echo "new_version=$current_rev"
} >> "$GITHUB_OUTPUT"
exit 0
fi
current_date="$(
gh api "repos/${input_owner}/${input_repo}/commits/${current_rev}" \
--jq '.commit.committer.date'
)"
current_timestamp="$(date --date="$current_date" +%s)"
target_timestamp="$(date --date="$target_date" +%s)"
if [ "$target_timestamp" -lt "$current_timestamp" ]; then
echo "The newest eligible revision is older than the current revision; skipping"
{
echo "updated=false"
echo "current_version=$current_rev"
echo "new_version=$current_rev"
} >> "$GITHUB_OUTPUT"
exit 0
fi
nix flake update "$INPUT_NAME" \
--override-input "$INPUT_NAME" "github:${input_owner}/${input_repo}/${target_rev}"
fi
if git diff --quiet -- flake.lock; then
echo "No lock file changes were produced"
{
echo "updated=false"
echo "current_version=$current_rev"
echo "new_version=$current_rev"
} >> "$GITHUB_OUTPUT"
exit 0
fi
new_node_key="$(
jq -er --arg input "$INPUT_NAME" '
.nodes.root.inputs[$input]
| if type == "array" then .[0] else . end
' flake.lock
)"
new_rev="$(jq -er --arg node "$new_node_key" '.nodes[$node].locked.rev' flake.lock)"
echo "New revision: $new_rev"
{
echo "updated=true"
echo "current_version=$current_rev"
echo "new_version=$new_rev"
echo "input_owner=$input_owner"
echo "input_repo=$input_repo"
} >> "$GITHUB_OUTPUT"
- name: Create or update pull request
id: pull-request
if: steps.update.outputs.updated == 'true'
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
INPUT_NAME: ${{ inputs.input-name }}
BASE_BRANCH: ${{ inputs.base-branch }}
CURRENT_REV: ${{ steps.update.outputs.current_version }}
NEW_REV: ${{ steps.update.outputs.new_version }}
INPUT_OWNER: ${{ steps.update.outputs.input_owner }}
INPUT_REPO: ${{ steps.update.outputs.input_repo }}
MINIMUM_RELEASE_AGE_DAYS: ${{ inputs.minimum-release-age-days }}
SKIP_DELAY: ${{ inputs.skip-delay }}
AUTO_MERGE: ${{ inputs.auto-merge }}
PR_LABELS: ${{ inputs.pr-labels }}
run: |
set -euo pipefail
branch_suffix="$(tr -c 'A-Za-z0-9._-' '-' <<< "$INPUT_NAME" | sed 's/-$//')"
branch="update-flake-${branch_suffix}"
current_short="${CURRENT_REV:0:8}"
new_short="${NEW_REV:0:8}"
title="chore(nix): update ${INPUT_NAME} to ${new_short}"
if [ "$SKIP_DELAY" = "true" ]; then
age_note="The minimum release age check was skipped for this manually requested update."
else
age_note="The target commit is at least ${MINIMUM_RELEASE_AGE_DAYS} days old."
fi
body="$(
printf '%s\n' \
"Automated update of the \`${INPUT_NAME}\` flake input." \
"" \
"- Previous revision: [\`${current_short}\`](https://github.com/${INPUT_OWNER}/${INPUT_REPO}/commit/${CURRENT_REV})" \
"- New revision: [\`${new_short}\`](https://github.com/${INPUT_OWNER}/${INPUT_REPO}/commit/${NEW_REV})" \
"- Changes: [compare](https://github.com/${INPUT_OWNER}/${INPUT_REPO}/compare/${CURRENT_REV}...${NEW_REV})" \
"" \
"$age_note"
)"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add flake.lock
git switch -C "$branch"
git commit -m "$title"
git fetch origin "refs/heads/${branch}:refs/remotes/origin/${branch}" || true
git push --force-with-lease origin "HEAD:refs/heads/${branch}"
label_args=()
available_labels="$(gh label list --limit 100 --json name --jq '.[].name')"
IFS=',' read -ra requested_labels <<< "$PR_LABELS"
for label in "${requested_labels[@]}"; do
label="$(xargs <<< "$label")"
if [ -n "$label" ] && grep -Fxq "$label" <<< "$available_labels"; then
label_args+=(--add-label "$label")
elif [ -n "$label" ]; then
echo "::warning::Skipping missing pull request label: $label"
fi
done
pr_number="$(
gh pr list \
--state open \
--head "$branch" \
--json number \
--jq '.[0].number // empty'
)"
if [ -n "$pr_number" ]; then
gh pr edit "$pr_number" \
--title "$title" \
--body "$body" \
"${label_args[@]}"
else
gh pr create \
--base "$BASE_BRANCH" \
--head "$branch" \
--title "$title" \
--body "$body"
pr_number="$(
gh pr list \
--state open \
--head "$branch" \
--json number \
--jq '.[0].number'
)"
if [ "${#label_args[@]}" -gt 0 ]; then
gh pr edit "$pr_number" "${label_args[@]}"
fi
fi
if [ "$AUTO_MERGE" = "true" ]; then
gh pr merge "$pr_number" --auto --squash ||
echo "::warning::Auto-merge could not be enabled; check the repository merge settings"
fi
pr_url="$(gh pr view "$pr_number" --json url --jq '.url')"
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"
echo "Pull request: $pr_url"
+1 -1
View File
@@ -11,7 +11,7 @@ concurrency:
cancel-in-progress: false
jobs:
renovate:
name: Update Nix flake inputs
name: Update GitHub Actions dependencies
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
+106
View File
@@ -0,0 +1,106 @@
name: Update Flake Inputs
on:
schedule:
# Every day at 03:30 JST (18:30 UTC on the previous day).
- cron: "30 18 * * *"
workflow_dispatch:
inputs:
input:
description: Update only this flake input (empty updates all inputs)
required: false
type: string
skip-delay:
description: Update to the latest revision without the three-day delay
required: false
default: false
type: boolean
auto-merge:
description: Enable auto-merge after required checks pass
required: false
default: true
type: boolean
permissions:
contents: write
pull-requests: write
concurrency:
group: update-flake-inputs
cancel-in-progress: false
jobs:
discover:
name: Discover flake inputs
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.inputs.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build update matrix
id: inputs
env:
REQUESTED_INPUT: ${{ inputs.input }}
run: |
set -euo pipefail
github_inputs="$(
jq -c '
. as $lock
| [
$lock.nodes.root.inputs
| to_entries[]
| .key as $name
| (
.value
| if type == "array" then .[0] else . end
) as $node
| select($lock.nodes[$node].locked.type == "github")
| $name
]
| sort
' flake.lock
)"
if [ -n "$REQUESTED_INPUT" ]; then
if ! jq -e --arg input "$REQUESTED_INPUT" 'index($input) != null' <<< "$github_inputs" >/dev/null; then
echo "::error::Unknown or unsupported flake input: $REQUESTED_INPUT"
exit 1
fi
matrix="$(jq -cn --arg input "$REQUESTED_INPUT" '{input: [$input]}')"
else
matrix="$(jq -cn --argjson inputs "$github_inputs" '{input: $inputs}')"
fi
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "Update matrix: $matrix"
update:
name: Update ${{ matrix.input }}
needs: discover
if: ${{ needs.discover.outputs.matrix != '{"input":[]}' }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 4
matrix: ${{ fromJSON(needs.discover.outputs.matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
token: ${{ secrets.RENOVATE_TOKEN }}
- name: Install Nix
uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
with:
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
access-tokens = github.com=${{ secrets.RENOVATE_TOKEN }}
- name: Update input
uses: ./.github/actions/update-flake-input
with:
input-name: ${{ matrix.input }}
github-token: ${{ secrets.RENOVATE_TOKEN }}
skip-delay: ${{ github.event_name == 'workflow_dispatch' && inputs.skip-delay }}
auto-merge: ${{ github.event_name != 'workflow_dispatch' || inputs.auto-merge }}
+2 -20
View File
@@ -1,31 +1,13 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
"enabledManagers": ["github-actions", "nix"],
"enabledManagers": ["github-actions"],
"prHourlyLimit": 0,
"nix": {
"enabled": true
},
"lockFileMaintenance": {
"enabled": true,
"schedule": ["at any time"]
},
"prCreation": "immediate",
"semanticCommits": "enabled",
"semanticCommitType": "chore",
"semanticCommitScope": "deps",
"packageRules": [
{
"description": "Group Nix flake input updates",
"matchManagers": ["nix"],
"groupName": "Nix flake inputs",
"groupSlug": "nix-flake-inputs"
}
]
"semanticCommitScope": "deps"
}