Files
bun.sh/scripts/agent.mjs
2025-05-12 17:12:17 -07:00

259 lines
6.7 KiB
JavaScript
Executable File

#!/usr/bin/env node
// An agent that starts buildkite-agent and runs others services.
import { realpathSync } from "node:fs";
import { join } from "node:path";
import { parseArgs } from "node:util";
import {
getAbi,
getAbiVersion,
getArch,
getCloud,
getCloudMetadataTag,
getDistro,
getDistroVersion,
getEnv,
getHostname,
getKernel,
getOs,
isWindows,
mkdir,
spawnSafe,
which,
writeFile,
} from "./utils.mjs";
/**
* @param {"install" | "start"} action
*/
async function doBuildkiteAgent(action) {
const username = "buildkite-agent";
const command = which("buildkite-agent", { required: true });
let homePath, cachePath, logsPath, agentLogPath, pidPath;
if (isWindows) {
homePath = "C:\\buildkite-agent";
cachePath = join(homePath, "cache");
logsPath = join(homePath, "logs");
agentLogPath = join(logsPath, "buildkite-agent.log");
} else {
homePath = "/var/lib/buildkite-agent";
cachePath = "/var/cache/buildkite-agent";
logsPath = "/var/log/buildkite-agent";
agentLogPath = join(logsPath, "buildkite-agent.log");
pidPath = join(logsPath, "buildkite-agent.pid");
}
async function install() {
const command = process.execPath;
const args = [realpathSync(process.argv[1]), "start"];
if (isWindows) {
mkdir(logsPath);
const nssm = which("nssm", { required: true });
const nssmCommands = [
[nssm, "install", "buildkite-agent", command, ...args],
[nssm, "set", "buildkite-agent", "Start", "SERVICE_AUTO_START"],
[nssm, "set", "buildkite-agent", "AppDirectory", homePath],
[nssm, "set", "buildkite-agent", "AppStdout", agentLogPath],
[nssm, "set", "buildkite-agent", "AppStderr", agentLogPath],
];
for (const command of nssmCommands) {
await spawnSafe(command, { stdio: "inherit" });
}
}
if (isOpenRc()) {
const servicePath = "/etc/init.d/buildkite-agent";
const service = `#!/sbin/openrc-run
name="buildkite-agent"
description="Buildkite Agent"
command=${escape(command)}
command_args=${escape(args.map(escape).join(" "))}
command_user=${escape(username)}
pidfile=${escape(pidPath)}
start_stop_daemon_args=" \\
--background \\
--make-pidfile \\
--stdout ${escape(agentLogPath)} \\
--stderr ${escape(agentLogPath)}"
depend() {
need net
use dns logger
}
`;
writeFile(servicePath, service, { mode: 0o755 });
await spawnSafe(["rc-update", "add", "buildkite-agent", "default"], { stdio: "inherit", privileged: true });
}
if (isSystemd()) {
const servicePath = "/etc/systemd/system/buildkite-agent.service";
const service = `
[Unit]
Description=Buildkite Agent
After=syslog.target
After=network-online.target
[Service]
Type=simple
User=${username}
ExecStart=${escape(command)} ${args.map(escape).join(" ")}
RestartSec=5
Restart=on-failure
KillMode=process
[Journal]
Storage=persistent
StateDirectory=${escape(agentLogPath)}
[Install]
WantedBy=multi-user.target
`;
writeFile(servicePath, service);
await spawnSafe(["systemctl", "daemon-reload"], { stdio: "inherit", privileged: true });
await spawnSafe(["systemctl", "enable", "buildkite-agent"], { stdio: "inherit", privileged: true });
}
}
async function start() {
const cloud = await getCloud();
let token = getEnv("BUILDKITE_AGENT_TOKEN", false);
if (!token && cloud) {
token = await getCloudMetadataTag("buildkite:token");
}
if (!token) {
throw new Error(
"Buildkite token not found: either set BUILDKITE_AGENT_TOKEN or add a buildkite:token label to the instance",
);
}
let shell;
if (isWindows) {
// Command Prompt has a faster startup time than PowerShell.
// Also, it propogates the exit code of the command, which PowerShell does not.
const cmd = which("cmd", { required: true });
shell = `"${cmd}" /S /C`;
} else {
const sh = which("sh", { required: true });
shell = `${sh} -elc`;
}
const flags = ["enable-job-log-tmpfile", "no-feature-reporting"];
const options = {
"name": getHostname(),
"token": token || "xxx",
"shell": shell,
"job-log-path": logsPath,
"build-path": join(homePath, "builds"),
"hooks-path": join(homePath, "hooks"),
"plugins-path": join(homePath, "plugins"),
"experiment": "normalised-upload-paths,resolve-commit-after-checkout,agent-api",
};
let ephemeral;
if (cloud) {
const jobId = await getCloudMetadataTag("buildkite:job-uuid");
if (jobId) {
options["acquire-job"] = jobId;
flags.push("disconnect-after-job");
ephemeral = true;
}
}
if (ephemeral) {
options["git-clone-flags"] = "-v --depth=1";
options["git-fetch-flags"] = "-v --prune --depth=1";
} else {
options["git-mirrors-path"] = join(cachePath, "git");
}
const tags = {
"os": getOs(),
"arch": getArch(),
"kernel": getKernel(),
"abi": getAbi(),
"abi-version": getAbiVersion(),
"distro": getDistro(),
"distro-version": getDistroVersion(),
"cloud": cloud,
};
if (cloud) {
const requiredTags = ["robobun", "robobun2"];
for (const tag of requiredTags) {
const value = await getCloudMetadataTag(tag);
if (typeof value === "string") {
tags[tag] = value;
}
}
}
options["tags"] = Object.entries(tags)
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join(",");
await spawnSafe(
[
command,
"start",
...flags.map(flag => `--${flag}`),
...Object.entries(options).map(([key, value]) => `--${key}=${value}`),
],
{
stdio: "inherit",
},
);
}
if (action === "install") {
await install();
} else if (action === "start") {
await start();
}
}
/**
* @returns {boolean}
*/
function isSystemd() {
return !!which("systemctl");
}
/**
* @returns {boolean}
*/
function isOpenRc() {
return !!which("rc-service");
}
function escape(string) {
return JSON.stringify(string);
}
async function main() {
const { positionals: args } = parseArgs({
allowPositionals: true,
});
if (!args.length || args.includes("install")) {
console.log("Installing agent...");
await doBuildkiteAgent("install");
console.log("Agent installed.");
}
if (args.includes("start")) {
console.log("Starting agent...");
await doBuildkiteAgent("start");
console.log("Agent started.");
}
}
await main();