-
Notifications
You must be signed in to change notification settings - Fork 0
322 lines (312 loc) · 15.2 KB
/
Copy pathci.yml
File metadata and controls
322 lines (312 loc) · 15.2 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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
name: ci
on:
push:
pull_request:
schedule:
- cron: "17 9 * * 1" # Mondays: make sure every upstream still resolves and verifies
workflow_dispatch:
permissions:
contents: read
# GitHub shows at most 10 annotations of each level per step, so results are
# grouped into one annotation per level (lines joined with %0A).
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: Unit + end-to-end tests
run: python -m unittest discover -s tests -v
- name: Lint
run: |
# Also flags syntax that Python 3.11, the oldest the project runs on, doesn't accept (ruff.toml)
pip install --disable-pip-version-check -q --require-hashes -r tests/requirements.txt
ruff check --output-format github .
- name: Shellcheck
run: |
# gcc format → annotations on the exact line
shellcheck -x -f gcc install.sh refresh.sh theme.sh check.sh scripts/common.sh scripts/release-notes.sh scripts/release.sh pe/vm/build-vm.sh \
linux/build.sh linux/HelixBoot.sh.in \
| sed -E 's/^([^:]+):([0-9]+):([0-9]+): (error|warning|note): (.*)$/::\4 file=\1,line=\2,col=\3::\5/; s/^::note /::notice /'
exit "${PIPESTATUS[0]}"
- name: Build HelixBoot.sh
run: linux/build.sh dist/HelixBoot.sh
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: HelixBoot-linux
path: dist/HelixBoot.sh
boot:
# Boots the Ventoy menu in QEMU with every theme, on UEFI and on BIOS, and checks each came
# up with its own background and a drawn menu. The screenshots are kept as an artifact.
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: QEMU, firmware and disk tools
run: |
sudo apt-get update -q
sudo apt-get install -y -q qemu-system-x86 ovmf mtools dosfstools
# Let the runner's user use KVM: without it the same boots take several times as long
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
pip install --disable-pip-version-check --require-hashes -r tests/requirements.txt
- name: Boot every theme
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HELIX_CACHE: ${{ runner.temp }}/helix
NO_COLOR: "1"
run: |
./helix fetch ventoy
python tests/boot/boot_menu.py --firmware both --out boot-shots
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: boot-menu-screenshots
path: boot-shots
upstream:
# Catches upstreams that renamed their files or moved (asset regex no longer matches).
runs-on: ubuntu-latest
outputs:
failed: ${{ steps.resolve.outputs.failed }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: Resolve latest releases
id: resolve
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# A site that is down fails the weekly run and a run started by hand; on a push it is a warning:
# an outage somewhere else says nothing about the commit.
OUTAGE: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' && '--outage-ok' || '' }}
run: |
set +e
./helix check --json $OUTAGE > upstream.json
rc=$?
python - <<'EOF'
import json, os
d = json.load(open("upstream.json"))
good, bad, down = [], [], []
lenient = bool(os.environ.get("OUTAGE"))
rows = ["| tool | status | latest | file |", "|---|---|---|---|"]
for k, v in sorted(d.items()):
if "error" in v and v.get("outage") and lenient:
down.append(f"{k}: {v['error']}")
rows.append(f"| {k} | ⚠️ site down | | {v['error'][:120]} |")
elif "error" in v:
bad.append(f"{k}: {v['error']}")
rows.append(f"| {k} | ❌ | | {v['error'][:120]} |")
else:
good.append(f"{k}: {v.get('latest') or '—'} {v.get('file') or ''}")
rows.append(f"| {k} | ✅ | {v.get('latest') or '—'} | {v.get('file') or ''} |")
enc = lambda xs: "%0A".join(x.replace("%", "%25") for x in xs)
if good: print(f"::notice title=resolved ({len(good)})::{enc(good)}")
if down: print(f"::warning title=site down ({len(down)})::{enc(down)}")
if bad: print(f"::error title=failed ({len(bad)})::{enc(bad)}")
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
f.write("\n".join(rows) + "\n")
# For the issue the weekly run opens. What a site answered is in these lines, so each is kept
# to one line of plain characters and the block ends at a marker nobody else can know.
import re, secrets
end = "END_" + secrets.token_hex(16)
plain = [re.sub(r"[^A-Za-z0-9 .,:;/_()'=?&%+@#-]", " ", b)[:300] for b in bad[:40]]
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write(f"failed<<{end}\n" + "\n".join(plain) + f"\n{end}\n")
EOF
exit $rc
fetch:
# Real downloads + checksum verification of everything (~4 GB).
runs-on: ubuntu-latest
needs: upstream
if: always()
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: Fetch and verify
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HELIX_CACHE: ${{ runner.temp }}/helix
NO_COLOR: "1"
# A site that is down fails the weekly run and a run started by hand; on a push it is a warning:
# an outage somewhere else says nothing about the commit.
OUTAGE: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' && '--outage-ok' || '' }}
run: |
set +e
./helix fetch $OUTAGE > fetch.log 2>&1
rc=$?
cat fetch.log
enc() { sed ':a;N;$!ba;s/%/%25/g;s/\n/%0A/g'; }
ok=$(grep '^✓' fetch.log | enc); warn=$(grep '^!' fetch.log | enc); err=$(grep -A2 '^✗' fetch.log | enc)
[[ -n $ok ]] && echo "::notice title=verified::$ok"
[[ -n $warn ]] && echo "::warning title=warnings::$warn"
[[ -n $err ]] && echo "::error title=failed::$err"
echo "::notice title=cache::$(du -sh "$HELIX_CACHE"/* 2>/dev/null | enc)"
# Second run must be a no-op (proves "up to date" detection works upstream-side too)
./helix fetch $OUTAGE > again.log 2>&1
echo "::notice title=second run::$(grep -c 'up to date' again.log) up to date, $(grep -c 'downloading' again.log) re-downloaded"
exit $rc
report:
# The weekly run is where a tool whose site has moved or gone shows up (on a push, a site that
# is down is only a warning). A red scheduled run is easy to miss, so it opens an issue, or
# adds to the one already open.
needs: [test, boot, upstream, fetch, mac, mac-boot]
if: failure() && github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Open or update the issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RESULTS: ${{ toJSON(needs) }}
TOOLS: ${{ needs.upstream.outputs.failed }}
RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
title="The weekly check failed"
{
echo "The weekly run of every test, download and boot failed on $(date -u +%F): $RUN"
echo
echo "**Failed jobs:** $(jq -r '[to_entries[] | select(.value.result == "failure") | .key] | join(", ")' <<<"$RESULTS")"
if [[ -n $TOOLS ]]; then
printf '\n**Tools that could not be resolved upstream** (a site that moved, renamed its files or is gone):\n\n```\n%s\n```\n' "$TOOLS"
fi
printf '\nA site that was only down for the day fixes itself: re-run the failed jobs, and close this when the run is green.\n'
} > body.md
open=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open --search "\"$title\" in:title" --json number --jq '.[0].number // empty')
if [[ -n $open ]]; then
gh issue comment "$open" --repo "$GITHUB_REPOSITORY" --body-file body.md
else
gh issue create --repo "$GITHUB_REPOSITORY" --title "$title" --body-file body.md
fi
mac:
# The tools for a Mac, on a real Mac: each download opens, and what is inside is whole,
# signed by its developer and let through by Gatekeeper.
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
# Updating a stick from a Mac, for real: a disk image partitioned like a Ventoy stick (a data
# partition and VTOYEFI) stands in for one, found and written through diskutil.
- name: Update a stick from this Mac
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HELIX_CACHE: ${{ runner.temp }}/helix-small
NO_COLOR: "1"
run: |
set -euo pipefail
mkfile -n 700m "$RUNNER_TEMP/stick.img"
dev=$(hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount "$RUNNER_TEMP/stick.img" | awk 'NR==1 { print $1 }')
disk=${dev#/dev/}
diskutil partitionDisk "$dev" MBR ExFAT HELIXTEST 640M "MS-DOS FAT16" VTOYEFI R
mkdir -p /Volumes/VTOYEFI/grub
printf '%s\n' 'function legacy_iso_memdisk {' '}' 'function uefi_iso_memdisk {' '}' \
'set VTOY_HELP_CMD="a"' 'set VTOY_LANG_CMD="b"' '#clear all input key before show main menu' 'vt_clear_key' \
> /Volumes/VTOYEFI/grub/grub.cfg
diskutil unmount /Volumes/VTOYEFI
diskutil info "$dev" | grep -E 'Internal|Protocol|Virtual' || true
python helix fetch ventoy supergrub2 memtest86plus
python mac/helix-mac list
python mac/helix-mac update --disk "$disk" --yes --no-fetch
test -f /Volumes/HELIXTEST/ventoy/ventoy.json
test -f /Volumes/HELIXTEST/.helix-boot/keys-hook
grep -q key_power.png /Volumes/HELIXTEST/ventoy/theme/theme.txt
find /Volumes/HELIXTEST/ISO -type f | sed 's/^/on the stick: /'
diskutil mount "${disk}s2"
grep -q 'helix-boot splash: begin' /Volumes/VTOYEFI/grub/grub.cfg
diskutil unmount /Volumes/VTOYEFI
python mac/helix-mac check --disk "$disk"
python mac/helix-mac update --disk "$disk" --yes --no-fetch | tee again.log
grep -q 'already up to date' again.log
hdiutil detach "$dev" || { sleep 10; hdiutil detach -force "$dev"; } # macOS holds a disk a moment
# A new stick made on this Mac (experimental), on a second disk image. The image is kept:
# the job below boots it, to see that a disk written here really starts on a PC.
- name: Make a new stick on this Mac
env:
HELIX_CACHE: ${{ runner.temp }}/helix-small
NO_COLOR: "1"
run: |
set -euo pipefail
mkfile -n 700m "$RUNNER_TEMP/new-stick.img"
dev=$(hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount "$RUNNER_TEMP/new-stick.img" | awk 'NR==1 { print $1 }')
disk=${dev#/dev/}
if python mac/helix-mac install --disk "$disk" --erase "$disk" --no-fetch; then
echo "a disk image was erased without --allow-disk-image"; exit 1
fi
python mac/helix-mac install --disk "$disk" --erase "$disk" --no-fetch --allow-disk-image
diskutil list "$dev"
test -f /Volumes/HelixBoot/ventoy/ventoy.json
test -f /Volumes/HelixBoot/.helix-boot/keys-hook
python mac/helix-mac check --disk "$disk"
python mac/helix-mac update --disk "$disk" --yes --no-fetch | tee again.log
grep -q 'already up to date' again.log
hdiutil detach "$dev" || { sleep 10; hdiutil detach -force "$dev"; } # macOS holds a disk a moment
gzip -1 "$RUNNER_TEMP/new-stick.img"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: stick-made-on-a-mac
path: ${{ runner.temp }}/new-stick.img.gz
retention-days: 3
- name: Fetch the Mac tools
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HELIX_CACHE: ${{ runner.temp }}/helix
NO_COLOR: "1"
run: python helix fetch $(python tests/mac/check_tools.py --names)
- name: Open each one as macOS would
env:
HELIX_CACHE: ${{ runner.temp }}/helix
NO_COLOR: "1"
run: python tests/mac/check_tools.py
mac-boot:
# The stick the Mac job made, started in a virtual PC: the menu has to come up, on UEFI and BIOS.
needs: mac
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: stick-made-on-a-mac
path: ${{ runner.temp }}
- name: Boot it
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq qemu-system-x86 ovmf >/dev/null
pip install --disable-pip-version-check -q --require-hashes -r tests/requirements.txt
sudo chmod 666 /dev/kvm 2>/dev/null || true
gunzip "$RUNNER_TEMP/new-stick.img.gz"
python tests/boot/boot_menu.py --image "$RUNNER_TEMP/new-stick.img" --firmware both --out boot-shots
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: mac-stick-boot-screenshots
path: boot-shots
if-no-files-found: ignore