Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
829c880
Build every DKMS module against every kernel's headers
emirb Oct 18, 2026
6db25c5
Run the DKMS check on changes to the check itself
emirb Oct 18, 2026
3473638
Check only what changes, and stop on any download, checksum or archiv…
emirb Oct 18, 2026
894f85d
Run the DKMS check on every pull request build and before every promo…
emirb Oct 18, 2026
5a57324
Build the Apple modules only against the Asahi kernel they run on
emirb Oct 18, 2026
dcc26b7
Check the DKMS packages Omarchy installs from Arch too
emirb Oct 18, 2026
f4f2647
Check a pull request only where it has to, and report on every one
emirb Oct 18, 2026
e233017
Close the ways a required DKMS status could pass without checking
emirb Oct 18, 2026
730ec08
Never pass a pull request whose packages the check could not see
emirb Oct 18, 2026
e3f6cd3
Check a pull request's kernels and DKMS modules inside its build
emirb Oct 18, 2026
a234ebe
Name the DKMS decision job so the result test can evaluate it
emirb Oct 18, 2026
23c10cf
Fan the DKMS check out, one leg per kernel and module
emirb Oct 18, 2026
ff1dc1b
Show the end of make.log when a DKMS build fails
emirb Oct 18, 2026
7986079
Unpack a DKMS package whole, not only its sources
emirb Oct 18, 2026
09de72f
Build DKMS modules with clang for a kernel built with clang
emirb Oct 18, 2026
1d976d8
Name only the kernel release when installing a kernel's headers
emirb Oct 18, 2026
0ac30d7
Leave linux-aurora out of the aarch64 nvidia-open-dkms legs
emirb Oct 18, 2026
596a06d
Report a known-broken DKMS package instead of failing on it
emirb Oct 18, 2026
bddfc39
Decide each DKMS leg's outcome on the host and summarise a run
emirb Oct 18, 2026
f537d0d
Show the DKMS check as one check on a pull request
emirb Oct 19, 2026
ea6907c
Refuse a dkms-request with a trailing newline, and packages that are …
emirb Oct 19, 2026
3e8f24b
Keep a pull request's package metadata from acting on the runner or t…
emirb Oct 19, 2026
9e68ee4
Name apple-ib-drv-dkms as macbook12-spi-driver-dkms's replacement
emirb Oct 19, 2026
56665ae
Plan no DKMS legs for a build that ships no packages
emirb Oct 19, 2026
fd35d7d
Key the DKMS check by commit, and refuse a fork that changes build-pr…
emirb Oct 19, 2026
3840406
Decide a DKMS build's provenance from the build run, and fail closed
emirb Oct 19, 2026
ae46ad9
Refuse package file names makepkg would not produce
emirb Oct 19, 2026
80fc61a
Accept a fork that branched before build-pr.yml existed
emirb Oct 19, 2026
d231581
Collect a reused artifact only from a build this check trusts
emirb Oct 19, 2026
6603382
Fail the DKMS matrix when a package cannot be read
emirb Oct 19, 2026
7e3ded5
Read every page of a reused artifact's candidates
emirb Oct 19, 2026
3816654
Put more untrusted artifacts in front than one page holds
emirb Oct 19, 2026
ce8df1a
List spark-hwmon-dkms in the DKMS registry
emirb Oct 29, 2026
bb1da56
Read each ARM channel's own Arch snapshot in the DKMS check
emirb Oct 29, 2026
8ee5f81
Keep the DKMS registry to the packages this repository publishes
emirb Nov 2, 2026
907a061
Check a promotion on the target channel as the promotion leaves it
emirb Nov 2, 2026
e069d8f
Merge remote-tracking branch 'origin/master' into ci/dkms-gate
emirb Nov 2, 2026
0b708e4
Fail a promotion's DKMS plan when the source database cannot be read
emirb Nov 2, 2026
1deb16f
Stop a promotion when the channels moved after its DKMS check
emirb Nov 2, 2026
df6bda7
Build DKMS modules only against Omarchy's own kernels
emirb Nov 2, 2026
5fb7b1b
Give the DKMS container the kernels and package base set outside it
emirb Nov 2, 2026
7f070d1
Test the kernel scope with linux-aarch64 instead of linux-n1x
emirb Nov 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.list.outputs.matrix }}
runners: ${{ steps.list.outputs.runners }}
count: ${{ steps.gate.outputs.count }}
trusted: ${{ steps.gate.outputs.trusted }}
vouch_status: ${{ steps.vouch.outputs.status }}
Expand Down Expand Up @@ -133,8 +134,18 @@ jobs:
{ echo "Reused existing build artifacts (${#reused[@]}):"; printf -- '- %s\n' "${reused[@]}"; } >> "$GITHUB_STEP_SUMMARY"
fi
fi
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "planned=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT"
# The runner of each entry moves to a map: GitHub names a matrix job
# after its values, and a runner list in every check name helps
# nobody. A base whose build-matrix predates runners has none.
runners=$(jq -c 'reduce (.include[] | select((.runner // "") != "")) as $e ({}; .[$e.package][$e.arch] = ($e.runner | fromjson))' <<<"$matrix")
matrix=$(jq -c '{include: [.include[] | del(.runner)]}' <<<"$matrix")
{
echo "matrix=$matrix"
echo "runners=$runners"
# The DKMS check needs every package that would publish, reused ones too.
echo "reused=$(printf '%s\n' "${reused[@]:-}" | jq -Rsc 'split("\n") | map(select(length > 0))')"
echo "planned=$(jq '.include | length' <<<"$matrix")"
} >> "$GITHUB_OUTPUT"
jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix"
# A PR whose diff against its base is empty changes nothing: its
# content already landed some other way (a sync PR beat it, or a
Expand Down Expand Up @@ -175,6 +186,28 @@ jobs:
fi
fi

# What this build ships, for the DKMS check (dkms-check.yml). It runs
# after this run completes, from master's copy, and reads this as data:
# whether the build ran, its matrix, and the artifacts it reused.
- name: Record what the DKMS check reads
if: github.event_name == 'pull_request'
env:
MATRIX: ${{ steps.list.outputs.matrix }}
REUSED: ${{ steps.list.outputs.reused }}
TRUSTED: ${{ steps.gate.outputs.trusted }}
run: |
jq -n --argjson matrix "$MATRIX" --argjson reused "$REUSED" --arg trusted "$TRUSTED" \
'{trusted: ($trusted == "true"), matrix: $matrix, reused: $reused}' >dkms-request.json
cat dkms-request.json
- if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: dkms-request
path: dkms-request.json
if-no-files-found: error
retention-days: 7
overwrite: true

build:
needs: changes
if: needs.changes.outputs.count != '0'
Expand All @@ -184,12 +217,14 @@ jobs:
# build it natively in 11 minutes, and linux-aurora in 30 (72 under QEMU).
# An aarch64 kernel goes to Namespace's Apple-backed runner instead:
# linux-omarchy-n1x took 1h45m on the 4 vCPU, and takes 24 minutes there. bin/build-matrix names the runner for each
# entry (package_runner); a base branch whose build-matrix predates that
# gives none, and gets the runners named here.
# The name GitHub made from the matrix before it carried the runner, so
# the checks on a pull request keep the names they had.
name: build (${{ matrix.package }}, ${{ matrix.arch }}, ${{ matrix.channels }}, ${{ matrix.publish_arches }})
runs-on: ${{ matrix.runner && fromJSON(matrix.runner) || (matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || fromJSON('["self-hosted","omarchy-builder"]')) }}
# entry (package_runner), and the changes job moves it to the runners
# map; a base branch whose build-matrix predates that gives none, and gets
# the runners named here.
# No name: GitHub's own, "build (package, arch, channels, publish_arches)",
# is the one these checks always had, and a build that is skipped shows as
# plain "build". A name with matrix expressions shows them unevaluated
# then, because GitHub evaluates nothing for a skipped job.
runs-on: ${{ fromJSON(needs.changes.outputs.runners || '{}')[matrix.package][matrix.arch] || (matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || fromJSON('["self-hosted","omarchy-builder"]')) }}
timeout-minutes: 180
strategy:
fail-fast: false
Expand Down
254 changes: 254 additions & 0 deletions .github/workflows/dkms-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
name: DKMS check

# Builds DKMS modules against kernel headers (dkms-matrix.yml, bin/check-dkms):
# - after every pull request build ("Build changed packages"), on what the
# build would publish: a new kernel against every module, Arch's included,
# and a new module against every kernel. The pull request gets one check,
# "DKMS check: 41/45 passed", whose summary has a row per kernel and module
# and links this run, where every leg is its own job;
# - daily, on every pair on edge that has not built at its versions yet:
# Arch updates the DKMS packages Omarchy installs from it (ci/dkms-packages
# repo=) on its own schedule;
# - by hand, for a whole channel, or for a channel as a promotion into it
# would leave it (rc with what differs on edge moved in):
# gh workflow run dkms-check.yml -R omacom/omarchy-pkgs -f channel=stable
# gh workflow run dkms-check.yml -R omacom/omarchy-pkgs -f channel=rc -f from=edge
# - by hand for a pull request, the same path as after its build:
# gh workflow run dkms-check.yml -R omacom/omarchy-pkgs -f pr=1250
# From a branch (--ref) it runs that branch's check, which is how a change
# to the check itself is proven before it merges; -f all=true checks every
# pair on the channel with the pull request's packages in place.
# promote.yml calls dkms-matrix.yml itself, on what a promotion moves.
#
# Why workflow_run and not the pull request's own workflow: every job of a
# pull_request run is a check on the pull request, about 45 of them for a
# kernel. This run's jobs attach to master's head instead, and the pull
# request gets the one check this workflow writes through the Checks API.
#
# workflow_run runs master's copy of this file with a token that could write,
# so: the only jobs that may write (checks: write, to open and close that one
# check) never check out the repository or download anything; they pass the
# event's commit and run ids and the matrix's verdict to the API through jq,
# as data. The matrix itself is read-only and runs master's tooling; the pull
# request's build is data until a leg unpacks it in a throwaway container.

on:
workflow_run:
workflows: [Build changed packages]
types: [completed]
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
schedule:
- cron: "37 5 * * *"
workflow_dispatch:
inputs:
channel:
description: Channel whose kernels and DKMS packages to check
type: choice
options: [edge, rc, stable]
default: edge
from:
description: "A promotion's source: check the channel with what differs there moved in (empty: everything)"
required: false
default: ""
from_repos:
description: Only the DKMS packages from Arch's repositories, against every kernel
type: boolean
default: false
pr:
description: "A pull request: check what its newest green build ships, and report it there as one check"
required: false
default: ""
head_sha:
description: "The commit whose build to check (default: the pull request's head)"
required: false
default: ""
all:
description: With pr or head_sha, every pair on the channel, the build's packages in place
type: boolean
default: false

permissions: {}

# One run per commit at a time: a newer build of the same commit (a label
# starts one) replaces the check in flight, which closes as cancelled. Keyed by
# commit, so a build of an older commit that finishes late, or one this
# workflow ignores (failed, cancelled, not a pull request's), never cancels the
# check of the pull request's head.
concurrency:
group: >-
dkms-check-${{ github.event_name == 'workflow_run'
&& (github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
&& github.event.workflow_run.path == '.github/workflows/build-pr.yml'
&& format('sha-{0}', github.event.workflow_run.head_sha) || format('ignored-{0}', github.run_id))
|| ((inputs.pr || inputs.head_sha) && format('pr-{0}-{1}', inputs.pr, inputs.head_sha))
|| format('{0}-{1}-{2}-{3}', github.event_name, github.ref, inputs.channel || 'edge', inputs.from || 'all') }}
cancel-in-progress: ${{ (github.event_name == 'workflow_run' || inputs.pr || inputs.head_sha) && true || false }}

jobs:
# A pull request's build: which commit and run, and the check, opened as in
# progress so the pull request shows it while the legs build. A commit the
# pull request has moved past gets no check. The build's dkms-request is
# only as good as the build-pr.yml that wrote it, and a fork's pull request
# runs its own copy: a build from a fork whose build-pr.yml differs from
# its merge base's is refused, never checked. Any API error fails this job
# before a check opens, so it fails closed.
target:
name: open the check
if: >-
(github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request'
&& github.event.workflow_run.conclusion == 'success'
&& github.event.workflow_run.path == '.github/workflows/build-pr.yml')
|| (github.event_name == 'workflow_dispatch' && (inputs.pr != '' || inputs.head_sha != ''))
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
checks: write
actions: read
pull-requests: read
outputs:
head_sha: ${{ steps.open.outputs.head_sha }}
build_run: ${{ steps.open.outputs.build_run }}
check_run: ${{ steps.open.outputs.check_run }}
refused: ${{ steps.open.outputs.refused }}
steps:
# No checkout: nothing from the repository, let alone a pull request,
# runs next to this token.
- name: Find the build and open its check
id: open
env:
GH_TOKEN: ${{ github.token }}
EVENT_SHA: ${{ github.event.workflow_run.head_sha }}
EVENT_RUN: ${{ github.event.workflow_run.id }}
HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
PR: ${{ inputs.pr }}
SHA: ${{ inputs.head_sha }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
pr=$PR refused=""
if [[ -n $EVENT_RUN ]]; then
sha=$EVENT_SHA run=$EVENT_RUN
# The event names no pull request for a fork's; find it by its
# head. One that has moved on to a newer commit gets no check.
pr=$(gh api -X GET "repos/$GITHUB_REPOSITORY/pulls" -f state=open -f head="$HEAD_OWNER:$HEAD_BRANCH" |
jq -r --arg sha "$sha" '[.[] | select(.head.sha == $sha)][0].number // empty')
if [[ -z $pr ]]; then
echo "no open pull request has $sha as its head; nothing to check"
exit 0
fi
else
sha=$SHA
if [[ -n $PR ]]; then
if [[ ! $PR =~ ^[0-9]+$ ]]; then
echo "::error::pr must be a pull request number"
exit 1
fi
[[ -n $sha ]] || sha=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR" --jq .head.sha)
fi
if [[ ! $sha =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::head_sha must be a full commit hash"
exit 1
fi
# The newest green pull request build of that commit.
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/build-pr.yml/runs?head_sha=$sha&event=pull_request&status=success&per_page=1" \
--jq '.workflow_runs[0].id // empty')
if [[ -z $run ]]; then
echo "::error::$sha has no green pull request build to check"
exit 1
fi
fi
# A fork's pull request builds with its own build-pr.yml, which
# writes the dkms-request this check reads. Whether this build's
# copy is the base's comes from the build run itself, in every mode.
build=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run")
if [[ $(jq -r '.head_sha' <<<"$build") != "$sha" || $(jq -r '.path' <<<"$build") != .github/workflows/build-pr.yml ]]; then
echo "::error::run $run is not the pull request build of $sha"
exit 1
fi
# build-pr.yml's blob at a commit, "missing" where it has none (a
# fork that branched before it existed); any other error stops here.
blob() {
local out
if out=$(gh api "repos/$GITHUB_REPOSITORY/contents/.github/workflows/build-pr.yml?ref=$1" --jq .sha 2>&1); then
echo "$out"
elif [[ $out == *"(HTTP 404)"* ]]; then
echo missing
else
echo "::error::cannot read build-pr.yml at $1" >&2
return 1
fi
}
if [[ $(jq -r '.head_repository.full_name // ""' <<<"$build") != "$GITHUB_REPOSITORY" ]]; then
base=$(gh api "repos/$GITHUB_REPOSITORY/compare/${DEFAULT_BRANCH}...$sha" --jq .merge_base_commit.sha)
ours=$(blob "$base")
theirs=$(blob "$sha")
if [[ ! $ours =~ ^([0-9a-f]{40}|missing)$ || $theirs != "$ours" ]]; then
refused="Not checked: this build comes from a fork whose .github/workflows/build-pr.yml differs from its merge base's, and that file writes what this check reads. Land the workflow change in a pull request of its own first."
fi
fi
check=$(jq -n --arg sha "$sha" --arg url "$URL" '{
name: "DKMS check", head_sha: $sha, status: "in_progress", details_url: $url,
output: {title: "Building DKMS modules against kernel headers", summary: "[Open the run, with every leg](\($url))"}
}' | gh api -X POST "repos/$GITHUB_REPOSITORY/check-runs" --input - --jq .id)
echo "check run $check on $sha, for build run $run${pr:+ of pull request #$pr}"
{
echo "head_sha=$sha"
echo "build_run=$run"
echo "check_run=$check"
echo "refused=$refused"
} >>"$GITHUB_OUTPUT"

dkms:
name: DKMS
needs: target
# After a build only for a pull request's open check; daily and by hand
# always.
if: >-
!cancelled() && ((needs.target.result == 'success' && needs.target.outputs.check_run != '' && needs.target.outputs.refused == '')
|| (github.event_name != 'workflow_run' && needs.target.result == 'skipped'))
permissions:
contents: read
actions: read
uses: ./.github/workflows/dkms-matrix.yml
with:
channel: ${{ inputs.channel || 'edge' }}
from: ${{ inputs.from || '' }}
from_repos: ${{ inputs.from_repos == true }}
all: ${{ inputs.all == true }}
build_run: ${{ needs.target.outputs.build_run || '' }}
head_sha: ${{ needs.target.outputs.head_sha || '' }}

# Closes the check with the matrix's verdict. A matrix that stopped before
# its summary fails the check (or cancels it), never passes it.
report:
name: close the check
needs: [target, dkms]
if: always() && needs.target.result == 'success' && needs.target.outputs.check_run != ''
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
checks: write
steps:
- name: Close the check
env:
GH_TOKEN: ${{ github.token }}
CHECK: ${{ needs.target.outputs.check_run }}
REFUSED: ${{ needs.target.outputs.refused }}
RESULT: ${{ needs.dkms.result }}
CONCLUSION: ${{ needs.dkms.outputs.conclusion }}
TITLE: ${{ needs.dkms.outputs.title }}
SUMMARY: ${{ needs.dkms.outputs.summary }}
URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
if [[ -n $REFUSED ]]; then
CONCLUSION=failure TITLE="Not checked" SUMMARY=$REFUSED
elif [[ $RESULT == cancelled ]]; then
CONCLUSION=cancelled TITLE="Cancelled" SUMMARY="A newer build's check replaced this one. [Open the run]($URL)"
elif [[ $CONCLUSION != success && $CONCLUSION != failure ]]; then
CONCLUSION=failure TITLE="The DKMS check did not finish ($RESULT)" SUMMARY="[Open the run]($URL)"
fi
jq -n --arg c "$CONCLUSION" --arg t "$TITLE" --arg s "$SUMMARY" --arg url "$URL" \
'{status: "completed", conclusion: $c, details_url: $url, output: {title: $t, summary: $s}}' |
gh api -X PATCH "repos/$GITHUB_REPOSITORY/check-runs/$CHECK" --input - --jq '"\(.conclusion): \(.output.title) \(.html_url)"'
Loading
Loading