-
Notifications
You must be signed in to change notification settings - Fork 39
162 lines (150 loc) · 7.27 KB
/
Copy pathmake_usb.yml
File metadata and controls
162 lines (150 loc) · 7.27 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
name: Add USB Image to Release
on:
# No `release: published` trigger, deliberately -- see the concurrency comment
# below. Both release workflows invoke this as a reusable workflow, so a
# release-event trigger would be a second, simultaneous entry point rather
# than the only one.
workflow_call:
inputs:
tag_name:
required: true
type: string
# Lets the image be rebuilt for a release that already exists, without
# cutting a new one -- useful when the build is changed, or when a release
# predates the workflow being wired up.
workflow_dispatch:
inputs:
tag_name:
description: Existing release tag to build the USB image for
required: true
type: string
permissions:
contents: write
# One USB build per release tag.
#
# This used to be guaranteed by something invisible: the release was created
# with GITHUB_TOKEN, and a release created that way deliberately does not
# re-trigger workflows, so the `release: published` trigger and the
# `workflow_call` path could never both fire. That protection ended when the
# release step moved to the FOG App token -- an App token re-triggers workflows
# exactly like a PAT does, which would have started two runs building and
# uploading the same asset to the same tag at once.
#
# So the `release: published` trigger is gone and `workflow_call` is the only
# automatic entry point. Both release workflows already call this explicitly and
# gate on its result, so nothing was lost; `workflow_dispatch` still covers
# rebuilding the image for a tag that already exists. This group stays as the
# backstop for the dispatch path racing a release.
concurrency:
group: make-usb-${{ inputs.tag_name }}
cancel-in-progress: false
jobs:
add-usb-image:
# Pinned rather than ubuntu-latest, matching the two release workflows.
# `latest` moves under you between runs, which is exactly the kind of
# surprise a release path should not carry.
runs-on: ubuntu-24.04
# A successful run takes well under a minute; this only catches a hang,
# which would otherwise sit for the 6 hour default.
timeout-minutes: 30
steps:
# Same FOG App identity the fog-workflows repo uses, scoped to just this
# repo, so the uploaded USB asset is attributed to the FOG bot rather than
# to github-actions[bot]. Reaching this job through `workflow_call` means
# the private key only arrives if the caller passes `secrets: inherit` --
# both release workflows do.
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ vars.FOG_WORKFLOWS_APPID }}
private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }}
owner: FOGProject
repositories: "fos"
- name: Checkout repository
uses: actions/checkout@v6
# Both remaining triggers supply tag_name as an input; the release-event
# fallback that used to be here went with the `release: published` trigger.
- name: Set tag name
run: echo "TAG_NAME=${{ inputs.tag_name }}" >> "$GITHUB_ENV"
- name: Create USB Image
run: |
# -y and an update first: the runner has no tty, so an unattended
# apt-get would hit the confirm prompt, read EOF and abort.
sudo apt-get update
sudo apt-get install -y grub-efi-amd64 parted kpartx
sudo ./create-usb-image.sh "https://github.com/${{ github.repository }}/releases/download/${{ env.TAG_NAME }}"
# Every other published artifact ships a .sha256 beside it -- build.sh
# writes one for each kernel and init, and the release job verifies the
# whole set with `sha256sum -c`. The USB image was the exception, and it
# is the one asset a user writes straight to physical media, so it is the
# one where a silently truncated or corrupted download matters most.
#
# Generated from inside /tmp so the file records a bare filename, the same
# way build.sh does it. `sha256sum -c` then works in whatever directory
# someone downloads the pair into; an absolute path would only verify on
# a machine that happened to put it in /tmp.
- name: Checksum USB image
run: |
cd /tmp
sha256sum fos-usb.img > fos-usb.img.sha256
cat fos-usb.img.sha256
- name: Release
uses: softprops/action-gh-release@v3
with:
token: ${{ steps.app-token.outputs.token }}
tag_name: ${{ env.TAG_NAME }}
files: |
/tmp/fos-usb.img
/tmp/fos-usb.img.sha256
# Verify rather than trust, because a partial upload here fails silently.
#
# On 2026-08-06 this step uploaded fos-usb.img.sha256 fine and then died
# with `other side closed` partway through fos-usb.img itself. The release
# was left advertising a checksum for a file that was not present -- and
# nothing downstream noticed, because the release page renders happily
# with a missing asset and every other job in the run was green. The only
# user-visible symptom is `sha256sum -c` failing on a pair someone
# downloaded, long after the fact.
#
# A truncated transfer is a transport failure, which is precisely the kind
# that succeeds on a second attempt, so re-upload rather than just report.
# Sizes are compared instead of checksums deliberately: the failure being
# guarded against is a short or absent object, GitHub stores what it
# received, and re-hashing a 128 MiB asset over the network on every
# release buys nothing against that.
- name: Verify uploaded assets, re-uploading any that are missing or short
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# +e explicitly: Actions runs `run:` blocks under `bash -e`, which would
# abort this step the moment a `gh release view` call failed -- i.e. on
# exactly the transient API failure the retry loop exists to survive.
# Errors are handled per-command below instead.
set +e
set -uo pipefail
files=(/tmp/fos-usb.img /tmp/fos-usb.img.sha256)
for attempt in 1 2 3; do
incomplete=()
for f in "${files[@]}"; do
name=$(basename "$f")
want=$(stat -c%s "$f")
got=$(gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" \
--json assets -q ".assets[]|select(.name==\"$name\")|.size" 2>/dev/null)
if [[ "$got" != "$want" ]]; then
echo "::warning::$name is ${got:-absent} on release $TAG_NAME, expected $want bytes"
incomplete+=("$f")
fi
done
if [[ ${#incomplete[@]} -eq 0 ]]; then
echo "All USB assets present on $TAG_NAME at their expected sizes."
exit 0
fi
if [[ $attempt -lt 3 ]]; then
echo "Re-uploading ${#incomplete[@]} asset(s) (attempt $attempt of 3)..."
gh release upload "$TAG_NAME" "${incomplete[@]}" \
--repo "$GITHUB_REPOSITORY" --clobber || true
sleep $((attempt * 10))
fi
done
echo "::error::USB assets still missing or truncated on $TAG_NAME after 3 attempts -- the release is incomplete."
exit 1