Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
260 linesCodeBlameRaw
1# Releases the self-hosted runner, g1t-runner (crates/runner): builds it for
2# Linux, macOS and Windows on x64 and arm64, pushes its container image,
3# builds the images `g1t-runner sandbox` runs on people's own computers,
4# signs the release (binaries and sandbox images in one manifest), and
5# publishes it to g1t.sh/downloads/runner/ (the g1t-downloads R2 bucket).
6# Runners already out there update themselves to it.
7#
8# A release is a tag `runner-v<version>`, where the version is the one in
9# crates/runner/Cargo.toml; or run it by hand. scripts/runner-release.mjs
10# and scripts/sandbox-image.mjs do the work, and the first's header says how
11# to make the release key the first time; CONTRIBUTING.md ("Deploying")
12# says how a release is made.
13#
14# The image jobs run on g1t's own machines (g1t-4core), whose jobs have a
15# Docker Engine of their own (crates/runner/src/docker/engine.rs). arm64
16# images need QEMU's emulators registered with the kernel; each image job
17# tries that, and when the machine does not allow it, builds amd64 alone
18# and says so in a warning rather than failing the release.
19name: Runner release
20
21on:
22 push:
23 tags: ["runner-v*"]
24 workflow_dispatch:
25
26permissions:
27 contents: read
28
29concurrency:
30 group: runner-release
31 cancel-in-progress: false
32
33env:
34 CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
35 WRANGLER_SEND_METRICS: "false"
36
37jobs:
38 binaries:
39 # One job per target, side by side: each cross-compile takes about six
40 # minutes, and one after another they were most of a release's wait.
41 name: Build ${{ matrix.target }}
42 runs-on: ubuntu-latest
43 timeout-minutes: 60
44 strategy:
45 fail-fast: true
46 matrix:
47 target: [linux-x64, linux-arm64, windows-x64]
48 steps:
49 - uses: actions/checkout@v5
50 - name: The tag names this version
51 if: startsWith(github.ref, 'refs/tags/runner-v')
52 run: |
53 version="$(sed -n 's/^version = "\(.*\)"/\1/p' crates/runner/Cargo.toml | head -1)"
54 [ "runner-v$version" = "${GITHUB_REF_NAME}" ] || { echo "::error::The tag is ${GITHUB_REF_NAME}, but crates/runner is $version"; exit 1; }
55 # zig and cargo-zigbuild, kept between releases: installing them took
56 # four minutes of every one. Change the key to take newer versions.
57 - name: Cache zig and cargo-zigbuild
58 id: zig
59 uses: actions/cache@v4
60 with:
61 path: |
62 ~/.local/lib/python3*/site-packages/ziglang
63 ~/.local/bin
64 ~/.cargo/bin/cargo-zigbuild
65 key: zig-0.13.0-zigbuild-${{ runner.os }}-1
66 - name: Install zig and cargo-zigbuild
67 if: steps.zig.outputs.cache-hit != 'true'
68 run: |
69 # The runner's Debian marks its Python as externally managed (PEP 668); zig goes under ~/.local anyway.
70 pip install --user --break-system-packages ziglang==0.13.0
71 cargo install --locked cargo-zigbuild
72 - name: zig on the PATH
73 run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
74 # Each target's build, kept between releases; a release after a
75 # Cargo.lock change starts from the last one's rather than from nothing.
76 - uses: actions/cache@v4
77 with:
78 path: |
79 ~/.cargo/registry
80 target
81 key: runner-release-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
82 restore-keys: runner-release-${{ matrix.target }}-
83 # Crates whose inputs did not change come back from the Actions cache
84 # instead of being compiled (scripts/sccache.sh); without it, the job
85 # builds as before.
86 - name: sccache
87 run: bash scripts/sccache.sh install
88 # The cache above brings back the last release's target/runner-release
89 # too: left there, it would go out with this one's, and the jobs after
90 # would find two of each file.
91 - name: Only this release's files
92 run: rm -rf target/runner-release
93 - name: Build Linux and Windows
94 # The macOS targets need Apple's SDK to link (the Security and
95 # CoreFoundation frameworks), which zig does not carry, so they are
96 # left out until this builds in the rust-cross/cargo-zigbuild image
97 # as the CLI's release does (scripts/cli-release.mjs). A Mac runner
98 # keeps the version it has, since the manifest names no file for it.
99 env:
100 G1T_RUNNER_RELEASE_KEY: ${{ vars.RUNNER_RELEASE_PUBLIC_KEY }}
101 RUNNER_AGENT_IMAGE: ${{ vars.RUNNER_AGENT_IMAGE }}
102 run: node scripts/runner-release.mjs build --targets ${{ matrix.target }}
103 - name: sccache's hits
104 if: always()
105 run: bash scripts/sccache.sh stats
106 - uses: actions/upload-artifact@v4
107 with:
108 name: binaries-${{ matrix.target }}
109 path: target/runner-release/
110 overwrite: true
111
112 image:
113 name: Container image
114 needs: binaries
115 runs-on: g1t-4core
116 # Production's secrets, without a deployment: a release is not a deploy of
117 # g1t.sh, and would otherwise show on its card as one in progress.
118 environment:
119 name: production
120 deployment: false
121 timeout-minutes: 45
122 # Its token pushes the image to g1t's registry.
123 permissions:
124 contents: read
125 packages: write
126 outputs:
127 # The pushed image by digest, which the sandbox's manifest names.
128 runner_image: ${{ steps.push.outputs.runner_image }}
129 steps:
130 - uses: actions/checkout@v5
131 - uses: actions/download-artifact@v4
132 with:
133 pattern: binaries-*
134 merge-multiple: true
135 path: release
136 - name: QEMU for arm64, if this machine allows it
137 id: qemu
138 run: |
139 if docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null 2>&1 \
140 && [ "$(docker run --rm --platform linux/arm64 debian:bookworm-slim uname -m 2>/dev/null)" = aarch64 ]; then
141 echo "arches=amd64 arm64" >> "$GITHUB_OUTPUT"
142 else
143 echo "::warning::This machine cannot run arm64 programs (QEMU's emulators could not be registered), so the runner's image is built for amd64 alone this time."
144 echo "arches=amd64" >> "$GITHUB_OUTPUT"
145 fi
146 - name: Build and push
147 id: push
148 # To g1t's own registry, where flagon-io keeps it public.
149 env:
150 IMAGE: g1t.sh/flagon-io/g1t-runner
151 ARCHES: ${{ steps.qemu.outputs.arches }}
152 run: |
153 version="$(sed -n 's/^version = "\(.*\)"/\1/p' crates/runner/Cargo.toml | head -1)"
154 echo "${{ secrets.G1T_TOKEN }}" | docker login g1t.sh -u g1t --password-stdin
155 tags=()
156 for arch in $ARCHES; do
157 mkdir -p "context-$arch"
158 cp deploy/runner/Dockerfile "context-$arch/"
159 name="g1t-runner-linux-$([ "$arch" = amd64 ] && echo x64 || echo arm64)"
160 cp "release/$version/$name" "context-$arch/g1t-runner"
161 docker buildx build --platform "linux/$arch" -t "$IMAGE:$version-$arch" --load "context-$arch"
162 docker push "$IMAGE:$version-$arch"
163 tags+=("$IMAGE:$version-$arch")
164 done
165 docker buildx imagetools create -t "$IMAGE:$version" -t "$IMAGE:latest" "${tags[@]}"
166 digest="$(docker buildx imagetools inspect "$IMAGE:$version" --format '{{json .Manifest}}' | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).digest))')"
167 echo "runner_image=$IMAGE@$digest" >> "$GITHUB_OUTPUT"
168
169 sandbox:
170 name: Sandbox images
171 needs: [binaries, image]
172 runs-on: g1t-4core
173 timeout-minutes: 60
174 steps:
175 - uses: actions/checkout@v5
176 - uses: actions/download-artifact@v4
177 with:
178 pattern: binaries-*
179 merge-multiple: true
180 path: target/runner-release
181 - name: QEMU for arm64, if this machine allows it
182 id: qemu
183 run: |
184 if docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null 2>&1 \
185 && [ "$(docker run --rm --platform linux/arm64 debian:bookworm-slim uname -m 2>/dev/null)" = aarch64 ]; then
186 echo "arches=x64 arm64" >> "$GITHUB_OUTPUT"
187 else
188 echo "::warning::This machine cannot run arm64 programs, so the sandbox's images are built for x64 alone this time: an arm64 Mac or Linux machine keeps the sandbox image it has."
189 echo "arches=x64" >> "$GITHUB_OUTPUT"
190 fi
191 - name: Build the WSL root filesystem and the VM's disk
192 env:
193 ARCHES: ${{ steps.qemu.outputs.arches }}
194 run: |
195 version="$(sed -n 's/^version = "\(.*\)"/\1/p' crates/runner/Cargo.toml | head -1)"
196 dir="target/runner-release/$version/"
197 for arch in $ARCHES; do
198 node scripts/sandbox-image.mjs build --arch "$arch" --runner "${dir}g1t-runner-linux-$arch" --out "$dir"
199 done
200 - name: The manifest's sandbox section
201 env:
202 RUNNER_SANDBOX_IMAGE: ${{ needs.image.outputs.runner_image }}
203 run: |
204 version="$(sed -n 's/^version = "\(.*\)"/\1/p' crates/runner/Cargo.toml | head -1)"
205 node scripts/sandbox-image.mjs section --dir "target/runner-release/$version/"
206 - uses: actions/upload-artifact@v4
207 with:
208 name: sandbox
209 path: |
210 target/runner-release/*/g1t-sandbox-*
211 target/runner-release/*/sandbox.json
212 overwrite: true
213
214 publish:
215 name: Sign and publish
216 needs: [binaries, image, sandbox]
217 # The binaries go out even when an image job failed: the manifest then
218 # names no sandbox images, and computers keep the sandbox they have.
219 if: ${{ always() && needs.binaries.result == 'success' }}
220 runs-on: ubuntu-latest
221 environment:
222 name: production
223 deployment: false
224 timeout-minutes: 30
225 steps:
226 - uses: actions/checkout@v5
227 - uses: actions/download-artifact@v4
228 with:
229 pattern: binaries-*
230 merge-multiple: true
231 path: target/runner-release
232 - uses: actions/download-artifact@v4
233 if: ${{ needs.sandbox.result == 'success' }}
234 with:
235 name: sandbox
236 path: target/runner-release
237 - name: Sign
238 env:
239 RUNNER_RELEASE_KEY: ${{ secrets.RUNNER_RELEASE_KEY }}
240 G1T_RUNNER_RELEASE_KEY: ${{ vars.RUNNER_RELEASE_PUBLIC_KEY }}
241 RUNNER_AGENT_IMAGE: ${{ vars.RUNNER_AGENT_IMAGE }}
242 run: |
243 node scripts/runner-release.mjs manifest
244 node scripts/runner-release.mjs sign
245 node scripts/runner-release.mjs verify
246 # npm's downloads, kept between runs: an install reads them first
247 # and asks the registry only for what changed, so a dropped
248 # connection to it rarely stops the job.
249 - name: Cache npm's downloads
250 uses: actions/cache@v4
251 with:
252 path: ~/.npm
253 key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
254 restore-keys: npm-${{ runner.os }}-
255 - name: Install Wrangler
256 run: npm ci --prefer-offline --workspaces=false --no-audit --no-fund
257 - name: Publish to g1t.sh/downloads/runner
258 env:
259 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
260 run: node scripts/runner-release.mjs publish