g1t/.g1t/workflows/runner-base.yml

87 lines3,599 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1# Rebuilds the base image of g1t's sandboxes (services/runner/base/Dockerfile:
2# the OS, toolchains and the Claude Code CLI), pushes it to Cloudflare's
3# registry, and opens a pull request that records it in
4# services/runner/base.json. Merging that pull request is what rolls it out:
5# the next deploy builds the runner's image on it, in seconds.
6#
7# Weekly, for security updates and new stable toolchains; when the base's
8# folder changes on main (a change that forgot to rebuild it); and by hand.
9#
10# It needs Docker, which g1t's own sandboxes do not have, so it runs on a
11# self-hosted runner with the `docker` label. Until one is registered, run
12# the same thing by hand on a machine with Docker:
13#
14# node scripts/deploy.mjs build-base
15#
16# and commit services/runner/base.json. docs/DEPLOYING.md explains both.
17name: Runner base image
18
19on:
20 schedule:
21 - cron: "17 6 * * 1"
22 push:
23 branches: [main]
24 paths:
25 - services/runner/base/**
26 workflow_dispatch:
27 inputs:
28 no_cache:
29 description: "Build every layer again, ignoring the previous base"
30 type: boolean
31 default: false
32
33concurrency:
34 group: runner-base
35 cancel-in-progress: false
36
37env:
38 CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
39 WRANGLER_SEND_METRICS: "false"
40
41jobs:
42 build:
43 name: Build and push the base
44 runs-on: [self-hosted, docker]
45 environment: production
46 timeout-minutes: 60
47 steps:
48 - uses: actions/checkout@v5
49 - name: Install Wrangler
50 run: npm ci --workspaces=false --no-audit --no-fund
51 - name: Build and push
52 env:
53 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
54 CI: "true"
55 NO_CACHE: ${{ inputs.no_cache }}
56 run: |
57 args=()
58 if [ "$NO_CACHE" = "true" ]; then args+=(--no-cache); fi
59 node scripts/deploy.mjs build-base "${args[@]}"
60 # The runner's own image on the new base, so the deploy after the
61 # merge finds it in the registry instead of building it.
62 - name: Build and push the runner's image on it
63 env:
64 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
65 CI: "true"
66 run: node scripts/deploy.mjs image
67 - name: Open a pull request with base.json
68 env:
69 G1T_TOKEN: ${{ github.token }}
70 REPO: ${{ github.repository }}
71 run: |
72 if git diff --quiet -- services/runner/base.json; then
73 echo "The base is unchanged."
74 exit 0
75 fi
76 branch="runner-base/$(date -u +%Y%m%d-%H%M)"
77 git config user.name "g1t"
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent78 git config user.email "g1t@users.noreply.g1t.sh"
Fast pages, required checks on the branch, self-hosted runners, honest incidents79 git checkout -b "$branch"
80 git add services/runner/base.json
81 git commit -m "A new base image for g1t's sandboxes"
82 git push origin "$branch"
83 tag=$(node -e 'console.log(require("./services/runner/base.json").image.split(":").pop())')
84 body=$(node -e 'const b=require("./services/runner/base.json"); console.log("Built and pushed by the Runner base image workflow.\n\n| | |\n| --- | --- |\n" + Object.entries(b.versions).map(([k,v]) => `| ${k} | ${v} |`).join("\n") + `\n| size | ${(b.size_bytes/1e9).toFixed(2)} GB unpacked |`)')
85 curl -fsS -X POST "https://api.g1t.sh/repos/$REPO/pulls" \
86 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
87 -d "$(node -e 'console.log(JSON.stringify({ title: `Runner base image ${process.argv[1]}`, branch: process.argv[2], body: process.argv[3] }))' "$tag" "$branch" "$body")"