#!/usr/bin/env bun
/**
* gitfor self-hosted runner.
*
* GITFOR_URL=https://forge.example.com GITFOR_RUNNER_TOKEN=gitfor_runner_... bun runner/gitfor-runner.ts
*
* Polls the forge for queued jobs whose `runs-on` labels match, executes the generated job script
* with bash, streams logs back and uploads artifacts. Requires bash, git, tar and curl on the host.
*
* Security: steps run with the runner's uid. On Linux, set `kernel.yama.ptrace_scope` to 1 or higher so a step
* cannot read the job script's memory (`/proc/$PPID/mem`) and recover the log-marker nonce; run the runner
* under a dedicated, unprivileged account.
*/
import { spawn } from "node:child_process";
import { mkdir, rm, stat, writeFile, readFile } from "node:fs/promises";
import { hostname, tmpdir } from "node:os";
import { join } from "node:path";
type ClaimedJob = {
id: string;
name: string;
repository: string;
timeoutMinutes: number;
script: string;
markerNonce: string;
env: Record<string, string>;
};
const SERVER = requireEnv("GITFOR_URL").replace(/\/$/, "");
const TOKEN = requireEnv("GITFOR_RUNNER_TOKEN");
const LABELS = (process.env.GITFOR_RUNNER_LABELS ?? "")
.split(",")
.map((label) => label.trim())
.filter(Boolean);
const WORK_ROOT = process.env.GITFOR_RUNNER_WORKDIR ?? join(tmpdir(), "gitfor-runner");
const IDLE_POLL_MS = 5000;
const ERROR_BACKOFF_MS = 15_000;
const LOG_FLUSH_MS = 1000;
const MAX_LOG_CHUNK = 512 * 1024;
const ARTIFACT_NAME = /^[A-Za-z0-9._-]+$/;