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
140 linesCodeBlameRaw

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.

The workspace cost alert compares only days that carry their value, not the days before it was kept1#!/usr/bin/env node
2// Builds the g1t runner (crates/runner) as one static Linux binary, the
3// only file the runner's image adds to its base (services/runner/Dockerfile).
4// Built outside the image, so a change to the runner is a Cargo build and
5// a one-file image, not a Docker build of Rust.
6//
7// The target is x86_64-unknown-linux-musl: statically linked, so it runs
8// on any x86-64 Linux, whatever its libc (the sandbox's Debian, or a
9// self-hosted runner's machine). Where it is built:
10//
11// - natively, on x86-64 Linux with the musl target and musl-gcc
12// (`rustup target add x86_64-unknown-linux-musl`, `apt-get install
13// musl-tools`), which a CI machine can have;
14// - otherwise in a small builder container (rust on Alpine, whose own
15// target is musl), with Docker volumes for Cargo's registry and target
16// directory, so a second build is incremental. This is how it builds
17// on Windows and macOS, where no musl cross-linker is at hand.
18//
19// node scripts/build-runner.mjs # -> target/runner-image/g1t-runner
20// node scripts/build-runner.mjs --docker # the builder container, wherever
21// node scripts/build-runner.mjs --native # this machine's Cargo, or fail
22
23import { spawnSync } from "node:child_process";
24import { copyFileSync, mkdirSync, readFileSync, statSync } from "node:fs";
25import { arch, platform } from "node:os";
26import { dirname, join } from "node:path";
27import { fileURLToPath } from "node:url";
28
29export const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
30export const TARGET = "x86_64-unknown-linux-musl";
31/** Where the binary is written: the thin image's whole build context. */
32export const OUT_DIR = join(ROOT, "target", "runner-image");
33export const BINARY = join(OUT_DIR, "g1t-runner");
34
35/**
36 * The builder container's image: Rust on Alpine, the same Rust release as
37 * rust-toolchain users get, plus musl's headers for crates with C in them
38 * (ring). Built once per Rust version and kept by Docker.
39 */
40export const BUILDER_RUST = "1.99";
41const BUILDER_IMAGE = `g1t-runner-builder:${BUILDER_RUST}`;
42const BUILDER_DOCKERFILE = `FROM rust:${BUILDER_RUST}-alpine\nRUN apk add --no-cache musl-dev\n`;
43/** Docker volumes the builder keeps between builds. */
44const VOLUMES = { registry: "g1t-runner-cargo-registry", git: "g1t-runner-cargo-git", target: "g1t-runner-target" };
45
46const run = (command, args, options = {}) => spawnSync(command, args, { stdio: "inherit", ...options });
47const quiet = (command, args) => spawnSync(command, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
48
49/** Whether this machine can build the musl binary itself. */
50export function canBuildNatively() {
51 if (platform() !== "linux" || arch() !== "x64") return false;
52 const targets = quiet("rustup", ["target", "list", "--installed"]);
53 if (targets.status !== 0 || !targets.stdout.split("\n").includes(TARGET)) return false;
54 return quiet("musl-gcc", ["--version"]).status === 0 || quiet("sh", ["-c", "command -v musl-gcc"]).status === 0;
55}
56
57function native() {
58 const built = run("cargo", ["build", "--release", "--locked", "--package", "g1t-runner", "--target", TARGET], {
59 cwd: ROOT,
60 env: { ...process.env, CC_x86_64_unknown_linux_musl: "musl-gcc" },
61 });
62 if (built.status !== 0) throw new Error("cargo build of the runner failed");
63 return join(ROOT, "target", TARGET, "release", "g1t-runner");
64}
65
66function inDocker() {
67 const have = quiet("docker", ["image", "inspect", BUILDER_IMAGE, "--format", "{{.Id}}"]);
68 if (have.status !== 0) {
69 console.error(`building ${BUILDER_IMAGE}`);
70 const made = run("docker", ["build", "--platform", "linux/amd64", "-t", BUILDER_IMAGE, "-"], { input: BUILDER_DOCKERFILE, stdio: ["pipe", "inherit", "inherit"] });
71 if (made.status !== 0) throw new Error(`could not build ${BUILDER_IMAGE}`);
72 }
73 mkdirSync(OUT_DIR, { recursive: true });
74 // The source is mounted read-only; Cargo writes only to its volumes, and
75 // the binary is copied out to target/runner-image.
76 const script = [
77 "set -e",
78 "cargo build --release --locked --package g1t-runner",
79 "cp /target/release/g1t-runner /out/g1t-runner",
80 ].join("\n");
81 const built = run("docker", [
82 "run", "--rm", "--platform", "linux/amd64",
83 "-v", `${ROOT}:/src:ro`,
84 "-v", `${OUT_DIR}:/out`,
85 "-v", `${VOLUMES.registry}:/usr/local/cargo/registry`,
86 "-v", `${VOLUMES.git}:/usr/local/cargo/git`,
87 "-v", `${VOLUMES.target}:/target`,
88 "-e", "CARGO_TARGET_DIR=/target",
89 "-e", "CARGO_TERM_COLOR=never",
90 "-w", "/src",
91 BUILDER_IMAGE, "sh", "-c", script,
92 ]);
93 if (built.status !== 0) throw new Error("cargo build of the runner in the builder container failed");
94 return BINARY;
95}
96
97/** Whether a file is a statically linked x86-64 ELF executable. */
98export function isStaticElf(path) {
99 const bytes = readFileSync(path);
100 if (bytes.length < 64 || bytes.readUInt32BE(0) !== 0x7f454c46) return false;
101 // 64-bit, little-endian, x86-64.
102 if (bytes[4] !== 2 || bytes[5] !== 1 || bytes.readUInt16LE(18) !== 0x3e) return false;
103 // No PT_INTERP program header: nothing to load it but the kernel.
104 const phoff = Number(bytes.readBigUInt64LE(32));
105 const phentsize = bytes.readUInt16LE(54);
106 const phnum = bytes.readUInt16LE(56);
107 for (let i = 0; i < phnum; i++) {
108 if (bytes.readUInt32LE(phoff + i * phentsize) === 3) return false;
109 }
110 return true;
111}
112
113/** Builds the binary; returns its path (always target/runner-image/g1t-runner). */
114export function buildRunner({ mode = "auto" } = {}) {
115 const started = Date.now();
116 const useNative = mode === "native" || (mode === "auto" && canBuildNatively());
117 if (mode === "native" && !canBuildNatively()) {
118 throw new Error(`no native musl build here: needs x86-64 Linux, rustup target ${TARGET} and musl-gcc`);
119 }
120 const built = useNative ? native() : inDocker();
121 if (built !== BINARY) {
122 mkdirSync(OUT_DIR, { recursive: true });
123 copyFileSync(built, BINARY);
124 }
125 if (!isStaticElf(BINARY)) throw new Error(`${BINARY} is not a static x86-64 Linux binary`);
126 const size = statSync(BINARY).size;
127 console.error(`g1t-runner: ${(size / 1048576).toFixed(1)} MB, static, built ${useNative ? "natively" : "in the builder container"} in ${((Date.now() - started) / 1000).toFixed(1)}s`);
128 return BINARY;
129}
130
131if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/build-runner.mjs")) {
132 const args = process.argv.slice(2);
133 const mode = args.includes("--docker") ? "docker" : args.includes("--native") ? "native" : "auto";
134 try {
135 console.log(buildRunner({ mode }));
136 } catch (error) {
137 console.error(String(error.message ?? error));
138 process.exit(1);
139 }
140}

This file's history is long; its oldest lines are credited to the oldest commit read.