#!/usr/bin/env node /** * @file Detached runner for one background `git clone` * (`server/lib/clone-jobs.js`). The dashboard restarts on every update, and a * git whose stderr was a pipe into it died of SIGPIPE on its next progress * line — so the clone runs here instead, in its own session/process group, * and outlives the server (the unit is `KillMode=process`). It reads its * spec (`{cmd, argv, cwd, timeoutMs}` — exactly the command `worktree.git()` * would run, built by `gitCommand`; never a secret, which travels in this * process's env as it would in git's) from the 0600 file it is given and * deletes it, runs the command with stderr appended straight to * `/stderr.log` (a file, never a pipe: nothing can SIGPIPE it), and * writes `/exit.json` `{code, signal, timedOut, finishedAt}` when it * ends. A cancel SIGTERMs this whole group: git dies (sudo relays it to a * member's git) and this runner stays to record how it ended. * @author Nguyễn Ngọc Trí Vĩ */ require("../server/lib/windows-hide").install(); // before child_process is read const fs = require("fs"); const path = require("path"); const { spawn } = require("child_process"); const specPath = process.argv[2]; const dir = path.dirname(specPath); let spec; try { spec = JSON.parse(fs.readFileSync(specPath, "utf8")); } finally { fs.rmSync(specPath, { force: true }); } function writeExit(fields) { const file = path.join(dir, "exit.json"); const tmp = `${file}.tmp`; fs.writeFileSync(tmp, JSON.stringify({ ...fields, finishedAt: new Date().toISOString() }), { mode: 0o600, }); fs.renameSync(tmp, file); } // Stay to record git's end: a cancel signals the whole group, this included. process.on("SIGTERM", () => {}); process.on("SIGHUP", () => {}); process.on("SIGINT", () => {}); const log = fs.openSync(path.join(dir, "stderr.log"), "a", 0o600); let timedOut = false; let child; try { child = spawn(spec.cmd, spec.argv, { cwd: spec.cwd, env: process.env, stdio: ["ignore", "ignore", log], windowsHide: true, }); } catch (err) { writeExit({ code: null, signal: null, timedOut: false, error: err.message }); process.exit(0); } const timer = spec.timeoutMs > 0 ? setTimeout(() => { timedOut = true; child.kill("SIGTERM"); }, spec.timeoutMs) : null; child.on("error", (err) => { if (timer) clearTimeout(timer); writeExit({ code: null, signal: null, timedOut, error: err.message }); process.exit(0); }); child.on("exit", (code, signal) => { if (timer) clearTimeout(timer); writeExit({ code, signal, timedOut }); process.exit(0); });