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 incidents | 1 | # 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 | # | |
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 10 | # It runs on a self-hosted runner with the `docker` label. g1t's own |
| 11 | # machines have Docker now, but this build's own downloads (Docker's apt | |
| 12 | # repository over HTTPS) do not yet trust a guarded job's egress | |
| 13 | # certificate, so it stays where the network is open. Until a runner is | |
| 14 | # registered, run the same thing by hand on a machine with Docker: | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 15 | # |
| 16 | # node scripts/deploy.mjs build-base | |
| 17 | # | |
| 18 | # and commit services/runner/base.json. docs/DEPLOYING.md explains both. | |
| Runner base workflow: skipped until a self-hosted runner exists | 19 | # Once a runner is registered, set the repository variable |
| 20 | # RUNNER_BASE_SELF_HOSTED to "true" to turn this workflow's job on. | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 21 | name: Runner base image |
| 22 | ||
| 23 | on: | |
| 24 | schedule: | |
| 25 | - cron: "17 6 * * 1" | |
| 26 | push: | |
| 27 | branches: [main] | |
| 28 | paths: | |
| 29 | - services/runner/base/** | |
| 30 | workflow_dispatch: | |
| 31 | inputs: | |
| 32 | no_cache: | |
| 33 | description: "Build every layer again, ignoring the previous base" | |
| 34 | type: boolean | |
| 35 | default: false | |
| 36 | ||
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 37 | # Its token pushes the branch with base.json and opens the pull request. |
| 38 | # What a job's token does starts no workflows, so that pull request's | |
| 39 | # checks start when someone pushes to it or runs CI by hand. | |
| 40 | permissions: | |
| 41 | contents: write | |
| 42 | pull-requests: write | |
| 43 | ||
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 44 | concurrency: |
| 45 | group: runner-base | |
| 46 | cancel-in-progress: false | |
| 47 | ||
| 48 | env: | |
| 49 | CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| 50 | WRANGLER_SEND_METRICS: "false" | |
| 51 | ||
| 52 | jobs: | |
| 53 | build: | |
| 54 | name: Build and push the base | |
| 55 | runs-on: [self-hosted, docker] | |
| Runner base workflow: skipped until a self-hosted runner exists | 56 | # Skipped until a runner with these labels is registered and the |
| 57 | # repository variable RUNNER_BASE_SELF_HOSTED is "true": without one, | |
| 58 | # each run waited in the queue for a day and a half and then failed. | |
| 59 | if: ${{ vars.RUNNER_BASE_SELF_HOSTED == 'true' }} | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 60 | environment: production |
| 61 | timeout-minutes: 60 | |
| 62 | steps: | |
| 63 | - uses: actions/checkout@v5 | |
| 64 | - name: Install Wrangler | |
| 65 | run: npm ci --workspaces=false --no-audit --no-fund | |
| 66 | - name: Build and push | |
| 67 | env: | |
| 68 | CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| 69 | CI: "true" | |
| 70 | NO_CACHE: ${{ inputs.no_cache }} | |
| 71 | run: | | |
| 72 | args=() | |
| 73 | if [ "$NO_CACHE" = "true" ]; then args+=(--no-cache); fi | |
| 74 | node scripts/deploy.mjs build-base "${args[@]}" | |
| 75 | # The runner's own image on the new base, so the deploy after the | |
| 76 | # merge finds it in the registry instead of building it. | |
| 77 | - name: Build and push the runner's image on it | |
| 78 | env: | |
| 79 | CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| 80 | CI: "true" | |
| 81 | run: node scripts/deploy.mjs image | |
| 82 | - name: Open a pull request with base.json | |
| 83 | env: | |
| 84 | G1T_TOKEN: ${{ github.token }} | |
| 85 | REPO: ${{ github.repository }} | |
| 86 | run: | | |
| 87 | if git diff --quiet -- services/runner/base.json; then | |
| 88 | echo "The base is unchanged." | |
| 89 | exit 0 | |
| 90 | fi | |
| 91 | branch="runner-base/$(date -u +%Y%m%d-%H%M)" | |
| 92 | 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-agent | 93 | git config user.email "g1t@users.noreply.g1t.sh" |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 94 | git checkout -b "$branch" |
| 95 | git add services/runner/base.json | |
| 96 | git commit -m "A new base image for g1t's sandboxes" | |
| 97 | git push origin "$branch" | |
| 98 | tag=$(node -e 'console.log(require("./services/runner/base.json").image.split(":").pop())') | |
| 99 | 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 |`)') | |
| 100 | curl -fsS -X POST "https://api.g1t.sh/repos/$REPO/pulls" \ | |
| 101 | -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \ | |
| 102 | -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")" |
This file's history is long; its oldest lines are credited to the oldest commit read.