From e809009bc83f3ea57231953df2f53431ffe100c8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Sep 2026 14:27:17 +0000 Subject: [PATCH 1/4] ci: update dependency ubuntu to v26 Update ubuntu from 24.04 to 26.04 --- .github/workflows/build.yml | 12 ++++++------ .github/workflows/lint.yml | 8 ++++---- .github/workflows/release.yml | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8683be0..7a6027a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,7 +56,7 @@ jobs: # add it to the filter's `if:` so `image` is never left empty (empty == # silent skip, the very failure mode this job exists to prevent). changes: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 outputs: image: ${{ steps.filter.outputs.image || steps.force.outputs.image }} steps: @@ -183,7 +183,7 @@ jobs: # PRs never push, so there is nothing to merge. needs: build if: github.event_name != 'pull_request' - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 permissions: contents: read packages: write @@ -250,7 +250,7 @@ jobs: test: name: Functional Testing - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 permissions: contents: read packages: read @@ -422,7 +422,7 @@ jobs: scan: name: Security Scan - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: merge if: github.event_name != 'pull_request' permissions: @@ -472,7 +472,7 @@ jobs: promote: name: Tag and Promote Image - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: [merge, test, scan] if: | github.event_name != 'pull_request' && @@ -563,7 +563,7 @@ jobs: ci-pass: needs: [changes, build, merge, test, scan, promote] if: always() - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: All required jobs passed or were appropriately skipped env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6c17a89..3454b2a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ permissions: jobs: hadolint: name: Dockerfile Linting - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -44,7 +44,7 @@ jobs: shellcheck: name: Shell Script Linting - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -56,7 +56,7 @@ jobs: yaml-lint: name: YAML Linting - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -68,7 +68,7 @@ jobs: action-lint: name: GitHub Actions Linting - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c26c4c..d57667f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ permissions: jobs: release: name: Release Please - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Generate GitHub App token id: app-token From 0036ab87a3698a7c4b8931f5205d984cafda2ce3 Mon Sep 17 00:00:00 2001 From: owine Date: Sat, 19 Sep 2026 09:28:13 -0500 Subject: [PATCH 2/4] ci: migrate native arm64/amd64 matrix runners to ubuntu-26.04 Renovate's runner-image bump updates literal `runs-on:` values only; it does not traverse strategy.matrix.include[], so the two `runner:` labels feeding the native per-arch build job were left on ubuntu-24.04 (confirmed in doc-scanner #209). Left alone this ships a split-brain build that stays green, since both images work and nothing flags the mismatch. Bump both legs to ubuntu-26.04 / ubuntu-26.04-arm and extend the existing native-runner comment to record that these labels are invisible to Renovate and must be updated by hand alongside its PRs. --- .github/workflows/build.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a6027a..946ac40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,16 +98,23 @@ jobs: # compile, which dominated wall-clock under the old single-job # multi-platform build. amd64 builds on the standard x86 runner, arm64 on # GitHub's free public-repo arm runner, and both legs run in parallel. + # + # The `runner:` labels in the matrix below are INVISIBLE to Renovate: it + # extracts runner labels from literal `runs-on:` values only and does not + # traverse strategy.matrix.include[] (confirmed in doc-scanner #209). A + # Renovate runner-image bump will silently update every other job in this + # repo and leave these two behind, producing a split-brain build that + # still passes CI. Update these by hand alongside any such PR. runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - platform: linux/amd64 - runner: ubuntu-24.04 + runner: ubuntu-26.04 arch: amd64 - platform: linux/arm64 - runner: ubuntu-24.04-arm + runner: ubuntu-26.04-arm arch: arm64 permissions: contents: read From 76d82bde6925b131af9b1f471d141f09728f57c5 Mon Sep 17 00:00:00 2001 From: owine Date: Sat, 19 Sep 2026 09:30:58 -0500 Subject: [PATCH 3/4] ci: make matrix runner labels visible to Renovate The built-in github-actions manager extracts runner labels from literal `runs-on:` values only and does not traverse strategy.matrix.include[] (confirmed in doc-scanner #209), so the native per-arch build legs were unmanaged -- the previous commit had to bump them by hand. Add a customManager keyed on the `runner:` lines. It captures only the numeric version, so the arm64 leg's `-arm` suffix falls outside the match and survives the replace; that is also why a single matchString covers both legs. depName `ubuntu` and docker versioning match what the built-in manager emits, so these deps group into the same branch and PR as the `runs-on:` ones instead of opening a second. Rewrite the load-bearing native-runner comment to point at the manager rather than instruct a hand-edit, and to record that the `runner: ubuntu-.` shape is what the manager matches on. Validated with renovate-config-validator (renovate@41). --- .github/renovate.json | 14 ++++++++++++++ .github/workflows/build.yml | 15 +++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 83465c8..0d88549 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -29,6 +29,20 @@ "datasourceTemplate": "github-releases", "depNameTemplate": "networkupstools/nut", "extractVersionTemplate": "^v(?.*)$" + }, + { + "customType": "regex", + "description": "Make the build matrix's `runner:` labels visible to Renovate. The built-in github-actions manager extracts runner labels from literal `runs-on:` values ONLY -- it does not traverse strategy.matrix.include[] (confirmed in doc-scanner #209). Without this manager a runner-image bump updates every other job in the repo and silently leaves the native per-arch build legs behind, shipping a split-brain build that still passes CI because both images work. The pattern captures only the numeric version, so the `-arm` suffix on the arm64 leg falls outside the match and survives the replace untouched; that is also why one matchString covers both legs. depName `ubuntu` and docker versioning match what the built-in manager emits, so these deps group into the same branch/PR as the `runs-on:` ones rather than opening a second.", + "managerFilePatterns": [ + "/^\\.github\\/workflows\\/.+\\.ya?ml$/" + ], + "matchStrings": [ + "runner: ubuntu-(?\\d+\\.\\d+)" + ], + "autoReplaceStringTemplate": "runner: ubuntu-{{{newValue}}}", + "datasourceTemplate": "github-runners", + "depNameTemplate": "ubuntu", + "versioningTemplate": "docker" } ], "packageRules": [ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 946ac40..c29b1aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,12 +99,15 @@ jobs: # multi-platform build. amd64 builds on the standard x86 runner, arm64 on # GitHub's free public-repo arm runner, and both legs run in parallel. # - # The `runner:` labels in the matrix below are INVISIBLE to Renovate: it - # extracts runner labels from literal `runs-on:` values only and does not - # traverse strategy.matrix.include[] (confirmed in doc-scanner #209). A - # Renovate runner-image bump will silently update every other job in this - # repo and leave these two behind, producing a split-brain build that - # still passes CI. Update these by hand alongside any such PR. + # Renovate's built-in github-actions manager does NOT see the `runner:` + # labels below: it extracts runner labels from literal `runs-on:` values + # only and does not traverse strategy.matrix.include[] (confirmed in + # doc-scanner #209). Left unmanaged, a runner-image bump would update + # every other job in this repo and leave these two behind -- a + # split-brain build that still passes CI, since both images work. The + # `ubuntu` customManager in .github/renovate.json covers these two lines + # so they bump in the same PR. Keep the `runner: ubuntu-.` + # shape: that manager matches on it. runs-on: ${{ matrix.runner }} strategy: fail-fast: false From fc719fb430567f170d320a5f4c07392ebe9f0733 Mon Sep 17 00:00:00 2001 From: owine Date: Sat, 19 Sep 2026 09:33:49 -0500 Subject: [PATCH 4/4] ci: teach actionlint the ubuntu-26.04 runner labels actionlint validates runner labels against a list baked into its binary. Ubuntu 26.04 went GA 2026-09-17, after the latest actionlint release (v1.7.12, 2026-03-30), so it rejected all 13 labels as unknown. Declare them via .github/actionlint.yaml, the escape hatch actionlint's own error message points to. This is the first fleet repo to hit it: the other migrated repos do not run actionlint. Unlike the Renovate side, this cannot live in shared config -- actionlint discovers its config by path (.github/actionlint.yaml). Also drop the local `ubuntu` customManager added in the previous commit; that belongs in the shared preset (owine/renovate-config), so the matrix comment now points there instead. Note actionlint DOES expand strategy.matrix.include[] -- it flagged 8 labels in build.yml, the 6 `runs-on:` values plus both matrix legs. It catches precisely the gap Renovate's extractor misses. --- .github/actionlint.yaml | 14 ++++++++++++++ .github/renovate.json | 14 -------------- .github/workflows/build.yml | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 .github/actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000..a25155a --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,14 @@ +--- +# actionlint validates `runs-on:` against a runner-label list baked into its +# binary. Ubuntu 26.04 went GA 2026-09-17, after the latest actionlint release +# (v1.7.12, 2026-03-30), so every ubuntu-26.04 label in this repo is reported +# as unknown. Declaring the labels here is the escape hatch actionlint's own +# error message points to. +# +# Remove this file once actionlint ships ubuntu-26.04 in rule_runner_label.go; +# keeping it after that only costs the version-compatibility checks actionlint +# does for known images. +self-hosted-runner: + labels: + - ubuntu-26.04 + - ubuntu-26.04-arm diff --git a/.github/renovate.json b/.github/renovate.json index 0d88549..83465c8 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -29,20 +29,6 @@ "datasourceTemplate": "github-releases", "depNameTemplate": "networkupstools/nut", "extractVersionTemplate": "^v(?.*)$" - }, - { - "customType": "regex", - "description": "Make the build matrix's `runner:` labels visible to Renovate. The built-in github-actions manager extracts runner labels from literal `runs-on:` values ONLY -- it does not traverse strategy.matrix.include[] (confirmed in doc-scanner #209). Without this manager a runner-image bump updates every other job in the repo and silently leaves the native per-arch build legs behind, shipping a split-brain build that still passes CI because both images work. The pattern captures only the numeric version, so the `-arm` suffix on the arm64 leg falls outside the match and survives the replace untouched; that is also why one matchString covers both legs. depName `ubuntu` and docker versioning match what the built-in manager emits, so these deps group into the same branch/PR as the `runs-on:` ones rather than opening a second.", - "managerFilePatterns": [ - "/^\\.github\\/workflows\\/.+\\.ya?ml$/" - ], - "matchStrings": [ - "runner: ubuntu-(?\\d+\\.\\d+)" - ], - "autoReplaceStringTemplate": "runner: ubuntu-{{{newValue}}}", - "datasourceTemplate": "github-runners", - "depNameTemplate": "ubuntu", - "versioningTemplate": "docker" } ], "packageRules": [ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c29b1aa..a04b367 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,9 +105,9 @@ jobs: # doc-scanner #209). Left unmanaged, a runner-image bump would update # every other job in this repo and leave these two behind -- a # split-brain build that still passes CI, since both images work. The - # `ubuntu` customManager in .github/renovate.json covers these two lines - # so they bump in the same PR. Keep the `runner: ubuntu-.` - # shape: that manager matches on it. + # `ubuntu` customManager in the shared preset (owine/renovate-config) + # covers these two lines so they bump in the same PR. Keep the + # `runner: ubuntu-.` shape: that manager matches on it. runs-on: ${{ matrix.runner }} strategy: fail-fast: false