Repository navigation
203 lines (186 loc) · 8.58 KB
/
Copy pathnixos-pr-build.yml
File metadata and controls
203 lines (186 loc) · 8.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
name: Build PiFinder NixOS (testable PRs)
# Builds testable NixOS PRs — including those from contributor forks — and
# publishes the result so devices can install it.
#
# `pull_request_target` runs in the BASE repo's trusted context, so the job has
# the real ATTIC_TOKEN and a read-write GITHUB_TOKEN even for fork PRs. The
# contributor's code is checked out explicitly (head SHA) and built. This is
# only reached after a maintainer applies the `testable` label — that label is
# the security boundary: it runs contributor code with the cache push token, so
# review the diff before labeling, and re-review on each new push to a labeled
# PR.
#
# Build strategy: the free GitHub-hosted ubuntu-24.04-arm runner builds first.
# Its cores are faster than the Pi5, and the Attic cache (cache.pifinder.io) is
# a substituter, so anything already built — including the patched kernel once
# it has been built once — is downloaded, not recompiled. Each successful build
# is pushed back to Attic, so the first build after a kernel/source change is
# the only slow one; every build after it is fast. The self-hosted Pi5 is a
# last-resort fallback that only runs if the hosted build fails.
on:
pull_request_target:
types: [labeled, synchronize, opened]
concurrency:
group: nixos-build-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
# Primary: free GitHub-hosted arm64 runner (native aarch64, no QEMU).
build-hosted:
if: contains(github.event.pull_request.labels.*.name, 'testable')
runs-on: ubuntu-24.04-arm
# Generous: only a kernel/source change compiles from scratch (~1 h on this
# 4-core runner); everything else substitutes from Attic in minutes.
timeout-minutes: 150
outputs:
store_path: ${{ steps.push.outputs.store_path }}
steps:
# Build the contributor's code. persist-credentials:false so the
# GITHUB_TOKEN is not left in the fork checkout's git config.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
persist-credentials: false
# Fork PRs: pull_request_target checkout of the head requires opt-in
# since actions/checkout began refusing it. The head SHA is pinned
# explicitly and only reached after the `testable` label, which is the
# review gate for running contributor code.
allow-unsafe-pr-checkout: true
# The shared CI action and scripts come from the base branch, which is
# trusted code, and never from the PR head.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
path: .ci
sparse-checkout: .github
persist-credentials: false
- uses: ./.ci/.github/actions/setup-nix
with:
attic-token: ${{ secrets.ATTIC_TOKEN }}
- name: Build NixOS system and on-device dev shell closures
run: |
nix build .#nixosConfigurations.pifinder.config.system.build.toplevel \
-L --no-link
nix build .#devShells.aarch64-linux.default -L --no-link
- name: Push to Attic
id: push
env:
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
run: |
STORE_PATH=$(nix build .#nixosConfigurations.pifinder.config.system.build.toplevel \
--json | jq -r '.[].outputs.out')
echo "store_path=$STORE_PATH" >> "$GITHUB_OUTPUT"
if [ -n "$ATTIC_TOKEN" ]; then
attic push pifinder:pifinder "$STORE_PATH"
.ci/.github/scripts/push_dev_shell.sh aarch64-linux
else
echo "No ATTIC_TOKEN — skipping push; build is verify-only"
fi
# Last-resort fallback: self-hosted Pi5. Only runs if the hosted build failed
# (e.g. hosted-runner outage or capacity). The Pi5 is slow for a from-scratch
# kernel, hence the longer timeout.
build-pi5:
needs: build-hosted
if: |
always() &&
contains(github.event.pull_request.labels.*.name, 'testable') &&
needs.build-hosted.result == 'failure'
runs-on: [self-hosted, aarch64]
timeout-minutes: 240
outputs:
store_path: ${{ steps.push.outputs.store_path }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
persist-credentials: false
# Fork PRs: pull_request_target checkout of the head requires opt-in
# since actions/checkout began refusing it. The head SHA is pinned
# explicitly and only reached after the `testable` label, which is the
# review gate for running contributor code.
allow-unsafe-pr-checkout: true
# The shared CI action and scripts come from the base branch, which is
# trusted code, and never from the PR head.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
path: .ci
sparse-checkout: .github
persist-credentials: false
- uses: ./.ci/.github/actions/setup-nix
with:
attic-token: ${{ secrets.ATTIC_TOKEN }}
self-hosted: "true"
- name: Build NixOS system and on-device dev shell closures
run: |
nix build .#nixosConfigurations.pifinder.config.system.build.toplevel \
-L --no-link
nix build .#devShells.aarch64-linux.default -L --no-link
- name: Push to Attic
id: push
run: |
STORE_PATH=$(nix build .#nixosConfigurations.pifinder.config.system.build.toplevel \
--json | jq -r '.[].outputs.out')
attic push pifinder:pifinder "$STORE_PATH"
.ci/.github/scripts/push_dev_shell.sh aarch64-linux
echo "store_path=$STORE_PATH" >> "$GITHUB_OUTPUT"
# Stamp the PR's build into the metadata-only nixos-manifest branch. Runs the
# TRUSTED scripts from the base branch (default checkout), never the fork's,
# since this step holds the write token.
update-manifest:
needs: [build-hosted, build-pi5]
if: |
always() &&
(needs.build-hosted.result == 'success' || needs.build-pi5.result == 'success')
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: manifest-write
cancel-in-progress: false
steps:
# Check out the PR's BASE branch explicitly. A bare checkout under
# pull_request_target lands on the repository's default branch, which
# may carry this workflow (so the event fires) without carrying the
# scripts below -- the job then dies after a successful build, leaving
# an image in Attic that no device can reach because the manifest was
# never written. The base branch is the branch this workflow file itself
# came from, so the scripts are always beside it, and it stays
# maintainer-controlled: never check out the PR head here, which would
# run contributor code with contents:write and the Attic token.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: Update generated manifest branch
env:
STORE_PATH: ${{ needs.build-hosted.outputs.store_path || needs.build-pi5.outputs.store_path }}
GH_REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
PR_NUMBER="$(jq -r '.pull_request.number // ""' "$GITHUB_EVENT_PATH")"
PR_TITLE="$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH")"
PR_BODY="$(jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH")"
HEAD_REPO="$(jq -r '.pull_request.head.repo.full_name // ""' "$GITHUB_EVENT_PATH")"
HEAD_REF="$(jq -r '.pull_request.head.ref // ""' "$GITHUB_EVENT_PATH")"
HEAD_SHA="$(jq -r '.pull_request.head.sha // ""' "$GITHUB_EVENT_PATH")"
SHORT_SHA="${HEAD_SHA:0:7}"
VERSION="PR#${PR_NUMBER}-${SHORT_SHA}"
bash .github/scripts/publish_manifest.sh \
"chore: update build manifest [skip ci]" \
python3 .github/scripts/update_manifest.py build \
--manifest @MANIFEST@ \
--repository "$GH_REPOSITORY" \
--ref-name "$HEAD_REF" \
--sha "$HEAD_SHA" \
--store-path "$STORE_PATH" \
--version "$VERSION" \
--pr-number "$PR_NUMBER" \
--pr-title "$PR_TITLE" \
--pr-body "$PR_BODY" \
--head-repo "$HEAD_REPO" \
--head-ref "$HEAD_REF" \
--head-sha "$HEAD_SHA"