Install git and gh in the sandbox supervisor image (#216)
The GitHub broker runs git and gh for agent computers, but the supervisor image is node:bookworm-slim, which has neither. A containerized supervisor therefore failed every broker call. Install both, and when the supervisor runs as root, run git as the workspace owner so clones stay writable by the computer. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a0bdf6ddc8
commit
a27cdf733c
@ -1,4 +1,7 @@
|
||||
FROM node:22-bookworm-slim
|
||||
# The GitHub broker runs git and gh on behalf of computers.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates gh git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
|
||||
COPY patches patches
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { chownSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { signGithubBrokerRunToken } from "@rakazo/core/node/github-broker-token";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
@ -5,6 +8,7 @@ import {
|
||||
type GithubBrokerSession,
|
||||
githubBrokerSessionsForTests,
|
||||
githubHttpsRemote,
|
||||
gitProcessIdentity,
|
||||
hostGitInvocation,
|
||||
hostPathFor,
|
||||
} from "./github-broker.js";
|
||||
@ -98,3 +102,24 @@ describe("github broker remotes", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("github broker host tools", () => {
|
||||
it("ships git and gh in the supervisor image", () => {
|
||||
const dockerfile = readFileSync(path.join(import.meta.dirname, "../Dockerfile"), "utf8");
|
||||
expect(dockerfile).toMatch(/apt-get install[^\n]* gh git/);
|
||||
});
|
||||
|
||||
it("runs git as the workspace owner only from a root supervisor", () => {
|
||||
const workspace = mkdtempSync(path.join(os.tmpdir(), "broker-owner-"));
|
||||
try {
|
||||
expect(gitProcessIdentity(workspace, 1000)).toBeUndefined();
|
||||
expect(gitProcessIdentity(path.join(workspace, "missing"), 0)).toBeUndefined();
|
||||
if (process.getuid?.() === 0) {
|
||||
chownSync(workspace, 1000, 1000);
|
||||
expect(gitProcessIdentity(workspace, 0)).toEqual({ uid: 1000, gid: 1000 });
|
||||
}
|
||||
} finally {
|
||||
rmSync(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
evaluateGitShellPolicy,
|
||||
@ -79,6 +80,20 @@ export function hostGitInvocation(
|
||||
return { argv: ["/usr/bin/git", ...GIT_CREDENTIAL_ARGS, ...mapped], cwd };
|
||||
}
|
||||
|
||||
/** A root supervisor runs git as the workspace owner, so clones stay writable by the computer. */
|
||||
export function gitProcessIdentity(
|
||||
workspacePath: string,
|
||||
uid = process.getuid?.(),
|
||||
): { uid: number; gid: number } | undefined {
|
||||
if (uid !== 0) return undefined;
|
||||
try {
|
||||
const stat = statSync(workspacePath);
|
||||
return stat.uid === 0 ? undefined : { uid: stat.uid, gid: stat.gid };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const sessions = new Map<string, GithubBrokerSession>();
|
||||
|
||||
export function githubBrokerSessionsForTests() {
|
||||
@ -112,6 +127,7 @@ async function runHostGit(
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(argv[0]!, argv.slice(1), {
|
||||
cwd,
|
||||
...gitProcessIdentity(session.workspacePath),
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: session.workspacePath,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user