import { join } from "jsr:@std/path@1"; import { parseCredentials } from "./credentials.ts"; import type { BotProfile, ProfileFailure } from "./profiles.ts"; const FIRST_PROFILE_UID = 20_000; export type RuntimeProfile = Pick & { index: number; uid: number; gid: number; home: string; tokenFile: string | null; }; export type RuntimeManifest = { runtimeDir: string; manifestPath: string; reviewerFile: string; outcomeFile: string; profiles: RuntimeProfile[]; }; export type RuntimeOutcome = | { ok: true; body: string; reviewPath: string; anchorsPath: string; profile: Pick; } | { ok: false; failures: ProfileFailure[] }; async function writePrivate( path: string, value: string, uid: number, gid: number, ): Promise { await Deno.writeTextFile(path, value, { mode: 0o600 }); await Deno.chmod(path, 0o600); await Deno.chown(path, uid, gid); } export async function prepareRuntime( actionPath: string, runtimeRoot: string, masterKey: string, ): Promise { const credentials = await parseCredentials( await Deno.readTextFile(join(actionPath, "profiles.yml")), masterKey, ); const runtimeDir = await Deno.makeTempDir({ dir: runtimeRoot, prefix: "bot-agents-", }); try { await Deno.chmod(runtimeDir, 0o711); const reviewerFile = join(runtimeDir, "reviewer-token"); await writePrivate(reviewerFile, credentials.reviewerToken, 0, 0); const profiles: RuntimeProfile[] = []; for (const [index, profile] of credentials.profiles.entries()) { const uid = FIRST_PROFILE_UID + index; const home = join(runtimeDir, `profile-${index}`); await Deno.mkdir(home, { mode: 0o700 }); await Deno.chmod(home, 0o700); await Deno.chown(home, uid, uid); const tokenFile = profile.token ? join(home, "token") : null; if (tokenFile !== null) { await writePrivate(tokenFile, profile.token!, uid, uid); } profiles.push({ index, type: profile.type, model: profile.model, uid, gid: uid, home, tokenFile, }); } const manifestPath = join(runtimeDir, "manifest.json"); const manifest: RuntimeManifest = { runtimeDir, manifestPath, reviewerFile, outcomeFile: join(runtimeDir, "outcome.json"), profiles, }; await writePrivate(manifestPath, JSON.stringify(manifest), 0, 0); return manifest; } catch (error) { await Deno.remove(runtimeDir, { recursive: true }).catch(() => undefined); throw error; } } export async function readRuntimeManifest( path: string, ): Promise { return JSON.parse(await Deno.readTextFile(path)); }