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
233 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.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1#!/usr/bin/env node
2// Releases of the self-hosted runner, `g1t-runner` (crates/runner): built
3// for every platform, checksummed, signed, and published to the
4// g1t-downloads R2 bucket that g1t.sh serves at /downloads/runner/
5// (apps/web/app/routes/downloads-runner.ts).
6//
7// node scripts/runner-release.mjs keygen # once: the release key
8// node scripts/runner-release.mjs build [--targets linux-x64,windows-x64]
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.9// node scripts/runner-release.mjs manifest # again, with the sandbox's images
Fast pages, required checks on the branch, self-hosted runners, honest incidents10// node scripts/runner-release.mjs sign # latest.json + .sig
11// node scripts/runner-release.mjs verify # checks the signature
12// node scripts/runner-release.mjs publish [--dry-run]
13//
14// What a release is, at runner/<version>/ in the bucket:
15//
16// g1t-runner-linux-x64, -linux-arm64, -macos-x64, -macos-arm64,
17// -windows-x64.exe the binaries
18// SHA256SUMS `sha256 name`, one line each
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.19// manifest.json { version, published_at, agent_image, files: { platform: { name, sha256 } },
20// sandbox: { runner_image, files: { "wsl-x64": { name, sha256, size }, … } } }
21// g1t-sandbox-* the sandbox's images (scripts/sandbox-image.mjs), when built
Fast pages, required checks on the branch, self-hosted runners, honest incidents22//
23// and at runner/: latest.json (the newest release's manifest) and
24// latest.json.sig, its Ed25519 signature in base64. A runner trusts a
25// release only when the signature checks out against the public key built
26// into it (G1T_RUNNER_RELEASE_KEY at build time) and every file's SHA-256
27// is the manifest's (crates/runner/src/selfhosted/update.rs).
28//
29// Environment:
30// RUNNER_RELEASE_KEY the private key (keygen prints it): a g1t Actions secret
31// G1T_RUNNER_RELEASE_KEY the public key, built into the binaries
32// RUNNER_AGENT_IMAGE the image agent work runs in, named in the manifest
33//
34// Every target is cross-compiled with cargo-zigbuild (`cargo install
35// cargo-zigbuild`, and zig on PATH), so one Linux machine builds them all.
36
37import { spawnSync } from "node:child_process";
38import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify } from "node:crypto";
39import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
40import { dirname, join } from "node:path";
41import { fileURLToPath } from "node:url";
42
43const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
44export const TARGETS = {
45 "linux-x64": { triple: "x86_64-unknown-linux-musl", file: "g1t-runner-linux-x64" },
46 "linux-arm64": { triple: "aarch64-unknown-linux-musl", file: "g1t-runner-linux-arm64" },
47 "macos-x64": { triple: "x86_64-apple-darwin", file: "g1t-runner-macos-x64" },
48 "macos-arm64": { triple: "aarch64-apple-darwin", file: "g1t-runner-macos-arm64" },
49 "windows-x64": { triple: "x86_64-pc-windows-gnu", file: "g1t-runner-windows-x64.exe", exe: true },
50};
51const BUCKET = "g1t-downloads";
52
53export function version() {
54 const toml = readFileSync(join(ROOT, "crates/runner/Cargo.toml"), "utf8");
55 const found = /^version\s*=\s*"([^"]+)"/m.exec(toml);
56 if (!found) throw new Error("crates/runner/Cargo.toml has no version");
57 return found[1];
58}
59
60const outDir = (v = version()) => join(ROOT, "target", "runner-release", v);
61
62/** The 32 raw bytes of an Ed25519 public key, in base64, as the runner takes it. */
63export function rawPublicKey(publicKey) {
64 const der = publicKey.export({ type: "spki", format: "der" });
65 return der.subarray(der.length - 32).toString("base64");
66}
67
68export function keygen() {
69 const { privateKey, publicKey } = generateKeyPairSync("ed25519");
70 return {
71 private: privateKey.export({ type: "pkcs8", format: "der" }).toString("base64"),
72 public: rawPublicKey(publicKey),
73 };
74}
75
76function privateKey(text = process.env.RUNNER_RELEASE_KEY) {
77 if (!text) throw new Error("RUNNER_RELEASE_KEY is not set");
78 return createPrivateKey({ key: Buffer.from(text, "base64"), format: "der", type: "pkcs8" });
79}
80
81/** Signs `bytes`: the signature in base64. */
82export function signBytes(bytes, key) {
83 return sign(null, bytes, key).toString("base64");
84}
85
86/** Whether `signature` is the key's over `bytes`, given the raw public key in base64. */
87export function verifyBytes(bytes, signature, publicRaw) {
88 const der = Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(publicRaw, "base64")]);
89 const key = createPublicKey({ key: der, format: "der", type: "spki" });
90 return verify(null, bytes, key, Buffer.from(signature, "base64"));
91}
92
93export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
94
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.95/**
96 * The manifest of the files in a release folder. Its `sandbox` section, the
97 * images `g1t-runner sandbox` runs and their SHA-256, comes from
98 * sandbox.json there when scripts/sandbox-image.mjs made one, and is signed
99 * with the rest.
100 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents101export function manifest(dir, v, agentImage = process.env.RUNNER_AGENT_IMAGE || null) {
102 const files = {};
103 for (const [platform, target] of Object.entries(TARGETS)) {
104 const path = join(dir, target.file);
105 if (existsSync(path)) files[platform] = { name: target.file, sha256: sha256(readFileSync(path)) };
106 }
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.107 const m = { version: v, published_at: new Date().toISOString(), agent_image: agentImage, files };
108 const sandboxPath = join(dir, "sandbox.json");
109 if (existsSync(sandboxPath)) {
110 const sandbox = JSON.parse(readFileSync(sandboxPath, "utf8"));
111 for (const file of Object.values(sandbox.files ?? {})) {
112 if (!existsSync(join(dir, file.name))) throw new Error(`sandbox.json names ${file.name}, which is not in ${dir}`);
113 }
114 m.sandbox = sandbox;
115 }
116 return m;
117}
118
119/** Writes manifest.json and SHA256SUMS for what is in the folder now. */
120function writeManifest(dir, v) {
121 const m = manifest(dir, v);
122 writeFileSync(join(dir, "manifest.json"), `${JSON.stringify(m, null, 2)}\n`);
123 const all = [...Object.values(m.files), ...Object.values(m.sandbox?.files ?? {})];
124 writeFileSync(join(dir, "SHA256SUMS"), all.map((f) => `${f.sha256} ${f.name}`).join("\n") + "\n");
125 return m;
Fast pages, required checks on the branch, self-hosted runners, honest incidents126}
127
128function run(command, args, options = {}) {
129 const done = spawnSync(command, args, { stdio: "inherit", cwd: ROOT, shell: process.platform === "win32", ...options });
130 if (done.status !== 0) throw new Error(`${command} ${args.join(" ")} failed`);
131}
132
133function build(targets) {
134 const v = version();
135 const dir = outDir(v);
136 mkdirSync(dir, { recursive: true });
137 if (!process.env.G1T_RUNNER_RELEASE_KEY) console.error("warning: G1T_RUNNER_RELEASE_KEY is not set; these builds will not update themselves");
138 for (const platform of targets) {
139 const target = TARGETS[platform];
140 if (!target) throw new Error(`unknown target ${platform}; one of ${Object.keys(TARGETS).join(", ")}`);
141 console.error(`building ${platform} (${target.triple})`);
142 run("rustup", ["target", "add", target.triple]);
143 run("cargo", ["zigbuild", "--release", "--locked", "--package", "g1t-runner", "--target", target.triple]);
144 const built = join(ROOT, "target", target.triple, "release", target.exe ? "g1t-runner.exe" : "g1t-runner");
145 copyFileSync(built, join(dir, target.file));
146 }
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.147 const m = writeManifest(dir, v);
Fast pages, required checks on the branch, self-hosted runners, honest incidents148 console.log(`built ${Object.keys(m.files).length} binaries of ${v} into ${dir}`);
149}
150
151function signRelease() {
152 const v = version();
153 const dir = outDir(v);
154 const bytes = readFileSync(join(dir, "manifest.json"));
155 writeFileSync(join(dir, "latest.json"), bytes);
156 writeFileSync(join(dir, "latest.json.sig"), signBytes(bytes, privateKey()));
157 console.log(`signed ${v}`);
158}
159
160function verifyRelease() {
161 const dir = outDir();
162 const publicRaw = process.env.G1T_RUNNER_RELEASE_KEY;
163 if (!publicRaw) throw new Error("G1T_RUNNER_RELEASE_KEY is not set");
164 const ok = verifyBytes(readFileSync(join(dir, "latest.json")), readFileSync(join(dir, "latest.json.sig"), "utf8"), publicRaw);
165 if (!ok) throw new Error("latest.json's signature is not the release key's");
166 const m = JSON.parse(readFileSync(join(dir, "latest.json"), "utf8"));
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.167 const all = [...Object.values(m.files), ...Object.values(m.sandbox?.files ?? {})];
168 for (const file of all) {
Fast pages, required checks on the branch, self-hosted runners, honest incidents169 if (sha256(readFileSync(join(dir, file.name))) !== file.sha256) throw new Error(`${file.name}'s SHA-256 is not the manifest's`);
170 }
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.171 console.log(`verified ${m.version}: ${all.length} files${m.sandbox ? `, the sandbox's runner image ${m.sandbox.runner_image ?? "(none)"}` : ""}`);
Fast pages, required checks on the branch, self-hosted runners, honest incidents172}
173
174function publish(dryRun) {
175 const v = version();
176 const dir = outDir(v);
177 const put = (key, file, type) => {
178 const args = ["wrangler", "r2", "object", "put", `${BUCKET}/${key}`, "--file", file, "--remote", "--content-type", type];
179 if (dryRun) console.log(`would put ${key}`);
180 else run("npx", args, { cwd: join(ROOT, "apps/web") });
181 };
182 // The version's files first; latest.json last, so no runner is pointed
183 // at files that are not there yet.
184 for (const name of readdirSync(dir)) {
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.185 // sandbox.json is in manifest.json already.
186 if (name.startsWith("latest.json") || name === "sandbox.json") continue;
Fast pages, required checks on the branch, self-hosted runners, honest incidents187 put(`runner/${v}/${name}`, join(dir, name), name.endsWith(".json") ? "application/json" : "application/octet-stream");
188 }
189 put("runner/latest.json", join(dir, "latest.json"), "application/json");
190 put("runner/latest.json.sig", join(dir, "latest.json.sig"), "text/plain");
191}
192
193if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/runner-release.mjs")) {
194 const [command, ...rest] = process.argv.slice(2);
195 const option = (name) => {
196 const at = rest.indexOf(`--${name}`);
197 return at >= 0 ? rest[at + 1] : undefined;
198 };
199 try {
200 switch (command) {
201 case "keygen": {
202 const keys = keygen();
203 console.log(`RUNNER_RELEASE_KEY=${keys.private}\nG1T_RUNNER_RELEASE_KEY=${keys.public}`);
204 console.error("Keep RUNNER_RELEASE_KEY secret (a g1t Actions secret); G1T_RUNNER_RELEASE_KEY is public and goes into every build.");
205 break;
206 }
207 case "build":
208 build((option("targets") ?? Object.keys(TARGETS).join(",")).split(","));
209 break;
Self-hosted runners on a person's own computer are sandboxes that nothing a job runs can get out of. `g1t-runner sandbox up|down|status|update [--json]` gives a Windows, macOS or Linux computer up to two capabilities, each a runner of its own: Linux jobs in a Linux VM the runner owns, where the runner's image runs under Sysbox and each job is a fresh container inside it (nested-container, three walls from the computer); and jobs native to Windows, each in a Windows Sandbox of its own, discarded after, with one read-only folder holding the harness (a new `vm` isolation level, also `g1t-runner register --native`). On Windows the VM is a WSL 2 distribution imported from the release's signed root filesystem: its wsl.conf turns off drives, Windows programs and Windows' PATH, and the supervisor inside unmounts what WSL shares with every distribution anyway (/mnt/wsl, where Docker Desktop keeps its sockets to Windows, WSLg, the GPU driver folders), refuses to run while anything of the computer's is reachable, and drops containers' traffic to the computer's address but for DNS. It needs no administrator once WSL is installed, and works on Windows 11 Home. On macOS the VM runs under vfkit and on Linux without Sysbox under QEMU with KVM, with no shared folders and nothing forwarded in (built and tested as commands, not yet run on those machines); on Linux with Sysbox no VM is needed. `status --json` reports the state, versions, isolation, the current job and a sentence when something is wrong, and each capability's availability with its cause and fix, for the desktop app. `update` drains the runner, swaps in the newest signed image and starts again without registering again. The sandbox's images are built from deploy/sandbox/Dockerfile by scripts/sandbox-image.mjs and listed with their SHA-256 in the release's signed manifest, beside the runner image's digest. The release workflow now builds its images on g1t's own machines (g1t-4core) rather than waiting forever for a self-hosted runner, registering QEMU for arm64 where the machine allows it and building amd64 alone with a warning where it does not, and signs and publishes once the images are built. The Runners page shows each runner's isolation (Sandboxed, VM per job, Shared Docker, Unsandboxed), which the API now returns as `isolation`. The Self-hosted runners guide has a new "Sandbox on your computer" section, with what it cannot stop.210 case "manifest": {
211 // Again, once the sandbox's images have joined the binaries.
212 const m = writeManifest(outDir(), version());
213 console.log(`manifest of ${m.version}: ${Object.keys(m.files).length} binaries, ${Object.keys(m.sandbox?.files ?? {}).length} sandbox images`);
214 break;
215 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents216 case "sign":
217 signRelease();
218 break;
219 case "verify":
220 verifyRelease();
221 break;
222 case "publish":
223 publish(rest.includes("--dry-run"));
224 break;
225 default:
226 console.error("usage: node scripts/runner-release.mjs keygen|build|sign|verify|publish");
227 process.exit(2);
228 }
229 } catch (error) {
230 console.error(String(error.message ?? error));
231 process.exit(1);
232 }
233}