@outerlayer/cli 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +5 -5
- package/dist/{agent-setup-TXLDY5RV.js → agent-setup-K6RD2Z4P.js} +4 -4
- package/dist/{build-JF5UOWKJ.js → build-MS34XEGN.js} +2 -1
- package/dist/build-info.json +1 -1
- package/dist/builder-IG6FNA54.js +13 -0
- package/dist/{check-ASZL5BEO.js → check-GX5ND6RO.js} +26 -22
- package/dist/chunk-2BRSMBBB.js +264 -0
- package/dist/{chunk-PUN7LMNS.js → chunk-4ACZCY4I.js} +2 -2
- package/dist/{chunk-2VO25HHO.js → chunk-4FPWKV7L.js} +3 -3
- package/dist/{chunk-AII7CKTH.js → chunk-5ODHQP6O.js} +68 -145
- package/dist/chunk-7RQ73DLO.js +23 -0
- package/dist/{chunk-7FTNIAHS.js → chunk-AX23QKHS.js} +7 -3
- package/dist/{chunk-6ZZHWY2Z.js → chunk-BF4KKEFY.js} +4 -4
- package/dist/{chunk-5FWNEQNZ.js → chunk-BQFNZOMQ.js} +1 -1
- package/dist/chunk-CX22S5BA.js +45 -0
- package/dist/{chunk-D6FXM5OW.js → chunk-GAYWY4GI.js} +8 -8
- package/dist/{chunk-MQPRB5K7.js → chunk-HSN5QNS5.js} +8 -8
- package/dist/chunk-I2UOAV7X.js +133 -0
- package/dist/chunk-I7CJIRMM.js +93 -0
- package/dist/{chunk-SSU747O6.js → chunk-ILEA3YHM.js} +1 -1
- package/dist/{chunk-OUUINEYB.js → chunk-JKIWMRFH.js} +1 -1
- package/dist/{chunk-JPR2WHHF.js → chunk-KSLNQOOS.js} +1 -1
- package/dist/{chunk-DGAIVMMN.js → chunk-KV47VQJZ.js} +5 -5
- package/dist/{chunk-E4PIFIC3.js → chunk-M4ERCYDB.js} +133 -159
- package/dist/chunk-PTW6W5NR.js +127 -0
- package/dist/{chunk-DTXOJDEH.js → chunk-PWBILI5P.js} +3 -2
- package/dist/{chunk-X2FSFU4C.js → chunk-PXFEYBDE.js} +3 -3
- package/dist/{chunk-6IYLDODX.js → chunk-Q4432IZT.js} +5 -5
- package/dist/{chunk-AJKEHABI.js → chunk-QB5JQH5V.js} +255 -54
- package/dist/{chunk-AQ54MKSQ.js → chunk-QNRSFEGY.js} +2 -2
- package/dist/{chunk-774HEQL6.js → chunk-TEKZRTM3.js} +91 -8
- package/dist/{chunk-CEBIA5FD.js → chunk-TTNFGMHO.js} +42 -50
- package/dist/{chunk-F6GFZXZ3.js → chunk-UXYLWVTQ.js} +1 -18
- package/dist/{chunk-M5POMKKW.js → chunk-V7NIVOFD.js} +5 -5
- package/dist/{chunk-BCLJSQCV.js → chunk-YO4PSLVB.js} +1 -1
- package/dist/{chunk-6QWV3YDJ.js → chunk-Z2THOHPP.js} +2 -2
- package/dist/{cli-LEVCGAQV.js → cli-X5ONGYNY.js} +128 -78
- package/dist/{command-FLSYCGIB.js → command-7NFU3IPO.js} +38 -20
- package/dist/{config-I3TVFYLH.js → config-Z3USDBLU.js} +3 -2
- package/dist/{connect-cmd-MJMUONDY.js → connect-cmd-PNZ6IJLJ.js} +2 -2
- package/dist/{context-adopt-CE4KDXS3.js → context-adopt-D2Z7F7PB.js} +3 -3
- package/dist/{context-materialize-QL33HDJN.js → context-materialize-4PMTDS6K.js} +3 -3
- package/dist/{dist-PXBSA7QX.js → dist-3ZTZF2TP.js} +1 -1
- package/dist/docker-PRHRSJM5.js +15 -0
- package/dist/{doctor-FVDT3KJZ.js → doctor-KALLPCRB.js} +39 -16
- package/dist/{emit-artifact-cmd-7RPMRSVP.js → emit-artifact-cmd-D5GMVWNC.js} +7 -7
- package/dist/{emit-criteria-cmd-MKI7WETO.js → emit-criteria-cmd-IE7VIM64.js} +5 -5
- package/dist/{emit-finding-cmd-RMLGZASB.js → emit-finding-cmd-46PTPN3A.js} +5 -5
- package/dist/{emit-result-cmd-MTXAZ2GV.js → emit-result-cmd-KXHHP6HX.js} +7 -7
- package/dist/{hook-fast-XBOZ3N6L.js → hook-fast-5H5QBJKT.js} +1 -1
- package/dist/{host-key-KDYZ5ECC.js → host-key-YMLXY6QB.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{init-VLW7H6RW.js → init-B4CECVOX.js} +25 -18
- package/dist/{init-BACXESVV.js → init-S4DH6PWW.js} +16 -13
- package/dist/{init-cmd-VJFB5L7X.js → init-cmd-P4MVCBCZ.js} +8 -8
- package/dist/{install-NMAPLLBQ.js → install-FHTVJZS6.js} +5 -4
- package/dist/{install-cmd-B5SEU3G7.js → install-cmd-BEC7XTNG.js} +3 -1
- package/dist/{loop-D3243JKT.js → loop-ODMRE3CW.js} +26 -22
- package/dist/{machine-EB3GEVI4.js → machine-J4SAB3Z2.js} +15 -12
- package/dist/{mcp-serve-cmd-N6UD2KBX.js → mcp-serve-cmd-CT47P4YQ.js} +2 -2
- package/dist/{session-sender-LMACEJJS.js → session-sender-V46VPWQ5.js} +6 -6
- package/dist/{status-F4THTJJK.js → status-QBKSH3QE.js} +19 -16
- package/dist/{sync-cmd-M6JA3OEE.js → sync-cmd-RJR77V4O.js} +5 -5
- package/dist/{watch-NB5TGPEZ.js → watch-YNZTFLEY.js} +5 -5
- package/dist/{work-claim-cmd-UHMWOS3G.js → work-claim-cmd-KAD3ZAH7.js} +10 -9
- package/dist/{work-cmd-6UMCFTAW.js → work-cmd-CNWPTH7W.js} +6 -6
- package/dist/{work-comment-cmd-E4LEUIXZ.js → work-comment-cmd-HH45S6UE.js} +6 -6
- package/dist/{work-open-pr-cmd-OPZWGSEQ.js → work-open-pr-cmd-5KR3KFCN.js} +7 -7
- package/dist/{work-pr-cmd-IDIAFFRU.js → work-pr-cmd-TTEGCP76.js} +6 -6
- package/package.json +2 -2
- package/skill-pack/maintained/amend/SKILL.md +22 -6
- package/dist/chunk-Q5G2ODUZ.js +0 -54
- package/dist/docker-NEGDLU6D.js +0 -7
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { readDefaultBranchHead, cloneRepository, buildRepositoryImage
|
|
3
|
+
import { readDefaultBranchHead, cloneRepository, buildRepositoryImage } from './chunk-TTNFGMHO.js';
|
|
4
4
|
import { startTimeOf, isSameProcess } from './chunk-N5FOE5PS.js';
|
|
5
5
|
import { currentCliBuild } from './chunk-OGFGZQA3.js';
|
|
6
|
-
import { createDockerHost, childEnvironment, spawnRun } from './chunk-
|
|
6
|
+
import { createDockerHost, childEnvironment, spawnRun } from './chunk-TEKZRTM3.js';
|
|
7
7
|
import { RecipeError } from './chunk-QOZKPLVJ.js';
|
|
8
|
+
import { builderSpec, runnerLabelValue, RUNNER_LABEL, BUILD_LABEL, prepareRunArgs, containerRunArgs, containerExecArgs } from './chunk-2BRSMBBB.js';
|
|
9
|
+
import { CLAUDE_CODE_VERSION, firecrackerArch, MICROVM_ARCHES, DEFAULT_BASE_IMAGE, GUEST_HELPER_SHA256, GUEST_ASSETS_BASE_URL, GUEST_KERNEL_SHA256, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_VERSION } from './chunk-CX22S5BA.js';
|
|
8
10
|
import { execCapture } from './chunk-ZOAEPWGL.js';
|
|
9
11
|
import { GUEST_CID } from './chunk-R7STSBLC.js';
|
|
10
|
-
import { WorkCommandError, postJson } from './chunk-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { WorkCommandError, postJson } from './chunk-KV47VQJZ.js';
|
|
13
|
+
import { hostAllowed } from './chunk-UXYLWVTQ.js';
|
|
14
|
+
import { parseDuKilobytes } from './chunk-7RQ73DLO.js';
|
|
15
|
+
import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, checkEnvironment, AGENT_CREDENTIAL_MISSING } from './chunk-OD2VTIHN.js';
|
|
16
|
+
import { defaultGitRunner, materializeContext } from './chunk-BQFNZOMQ.js';
|
|
14
17
|
import { readContextManifest, readSourceCache } from './chunk-ZZXLM64C.js';
|
|
15
|
-
import { signingFetchFor } from './chunk-
|
|
16
|
-
import { signerFor } from './chunk-
|
|
17
|
-
import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir,
|
|
18
|
-
import { relative, join, dirname,
|
|
19
|
-
import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync,
|
|
18
|
+
import { signingFetchFor } from './chunk-YO4PSLVB.js';
|
|
19
|
+
import { signerFor } from './chunk-V7NIVOFD.js';
|
|
20
|
+
import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
|
|
21
|
+
import { relative, join, dirname, basename } from 'path';
|
|
22
|
+
import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync, renameSync, lstatSync, unlinkSync, openSync, closeSync, mkdtempSync, createWriteStream, createReadStream } from 'fs';
|
|
20
23
|
import { spawnSync, spawn } from 'child_process';
|
|
21
|
-
import {
|
|
24
|
+
import { availableParallelism, networkInterfaces } from 'os';
|
|
25
|
+
import { randomBytes, createHash } from 'crypto';
|
|
22
26
|
import { createServer, request } from 'http';
|
|
23
27
|
import { request as request$1 } from 'https';
|
|
24
28
|
import { Transform } from 'stream';
|
|
@@ -26,7 +30,6 @@ import { pipeline, finished } from 'stream/promises';
|
|
|
26
30
|
import { createGunzip, gunzipSync, constants as constants$1 } from 'zlib';
|
|
27
31
|
import { isIP, connect, createServer as createServer$1 } from 'net';
|
|
28
32
|
import { lookup } from 'dns/promises';
|
|
29
|
-
import { networkInterfaces } from 'os';
|
|
30
33
|
|
|
31
34
|
createRequire(import.meta.url);
|
|
32
35
|
function logStamp(at) {
|
|
@@ -86,7 +89,7 @@ function isRetryable(err) {
|
|
|
86
89
|
}
|
|
87
90
|
async function retrySend(send, deps = {}) {
|
|
88
91
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
89
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((
|
|
92
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
90
93
|
const windowMs = deps.windowMs ?? RETRY_WINDOW_MS;
|
|
91
94
|
const startedMs = now().getTime();
|
|
92
95
|
let attempts = 0;
|
|
@@ -466,118 +469,62 @@ function lifecycleSteps(lifecycle) {
|
|
|
466
469
|
function lifecycleFailureReason(step, exitCode) {
|
|
467
470
|
return `lifecycle command ${step.name} (${step.source}) exited ${exitCode}`;
|
|
468
471
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
+
function parseCpuList(text) {
|
|
473
|
+
const cores = /* @__PURE__ */ new Set();
|
|
474
|
+
for (const part of text.trim().split(",")) {
|
|
475
|
+
const match = /^(\d+)(?:-(\d+))?$/.exec(part.trim());
|
|
476
|
+
if (match === null) return void 0;
|
|
477
|
+
const low = Number(match[1]);
|
|
478
|
+
const high = match[2] === void 0 ? low : Number(match[2]);
|
|
479
|
+
if (high < low) return void 0;
|
|
480
|
+
for (let core = low; core <= high; core++) cores.add(core);
|
|
481
|
+
}
|
|
482
|
+
return [...cores].sort((a, b) => a - b);
|
|
483
|
+
}
|
|
484
|
+
function formatCpuList(cores) {
|
|
485
|
+
const sorted = [...new Set(cores)].sort((a, b) => a - b);
|
|
486
|
+
const parts = [];
|
|
487
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
488
|
+
const start = sorted[i];
|
|
489
|
+
while (sorted[i + 1] === sorted[i] + 1) i++;
|
|
490
|
+
parts.push(start === sorted[i] ? String(start) : `${start}-${sorted[i]}`);
|
|
491
|
+
}
|
|
492
|
+
return parts.join(",");
|
|
493
|
+
}
|
|
494
|
+
function usableCpus(deps = {}) {
|
|
472
495
|
try {
|
|
473
|
-
|
|
496
|
+
const status = (deps.readStatus ?? (() => readFileSync("/proc/self/status", "utf8")))();
|
|
497
|
+
const cores = parseCpuList(/^Cpus_allowed_list:\s*(.+)$/m.exec(status)?.[1] ?? "");
|
|
498
|
+
if (cores !== void 0) return cores;
|
|
474
499
|
} catch {
|
|
475
|
-
try {
|
|
476
|
-
return join(realpathSync(dirname(resolve(configPath))), "runner");
|
|
477
|
-
} catch {
|
|
478
|
-
return resolve(runnerDir(configPath));
|
|
479
|
-
}
|
|
480
500
|
}
|
|
501
|
+
return Array.from({ length: Math.max(1, (deps.parallelism ?? availableParallelism)()) }, (_unused, core) => core);
|
|
481
502
|
}
|
|
482
|
-
function
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
String(cpus),
|
|
508
|
-
"--pids-limit",
|
|
509
|
-
String(pids)
|
|
510
|
-
];
|
|
511
|
-
}
|
|
512
|
-
function mountPath(path) {
|
|
513
|
-
if (/[",]/.test(path)) throw new Error(`${path} holds a comma or a quote, which a Docker mount cannot carry; move the runner's config file`);
|
|
514
|
-
return path;
|
|
515
|
-
}
|
|
516
|
-
function containerRunArgs(input) {
|
|
517
|
-
return [
|
|
518
|
-
"--detach",
|
|
519
|
-
"--name",
|
|
520
|
-
input.container,
|
|
521
|
-
...hardened(input),
|
|
522
|
-
"--mount",
|
|
523
|
-
`type=volume,src=${input.volume},dst=/build`,
|
|
524
|
-
// `/tmp` is a directory of the same volume, so it is measured and limited with the rest of the build.
|
|
525
|
-
"--mount",
|
|
526
|
-
`type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
|
|
527
|
-
"--mount",
|
|
528
|
-
`type=bind,src=${mountPath(input.buildDir)},dst=${JOB_MOUNT},readonly`,
|
|
529
|
-
"--entrypoint",
|
|
530
|
-
"/opt/outerlayer/bin/tini",
|
|
531
|
-
input.image,
|
|
532
|
-
"--",
|
|
533
|
-
"/opt/outerlayer/bin/node",
|
|
534
|
-
"/opt/outerlayer/cli/dist/index.js",
|
|
535
|
-
"broker-relay",
|
|
536
|
-
"--port",
|
|
537
|
-
String(RELAY_PORT),
|
|
538
|
-
"--socket",
|
|
539
|
-
`${JOB_MOUNT}/tunnel.sock`,
|
|
540
|
-
...Array.from({ length: input.destinations ?? 0 }, (_unused, index) => [
|
|
541
|
-
"--forward",
|
|
542
|
-
`${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
|
|
543
|
-
]).flat(),
|
|
544
|
-
...input.builtIns === true ? [
|
|
545
|
-
"--forward",
|
|
546
|
-
`${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
|
|
547
|
-
"--forward",
|
|
548
|
-
`${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
|
|
549
|
-
] : []
|
|
550
|
-
];
|
|
551
|
-
}
|
|
552
|
-
function prepareRunArgs(input) {
|
|
553
|
-
return [
|
|
554
|
-
"--rm",
|
|
555
|
-
...hardened(input),
|
|
556
|
-
"--mount",
|
|
557
|
-
`type=volume,src=${input.volume},dst=/build`,
|
|
558
|
-
"--entrypoint",
|
|
559
|
-
"mkdir",
|
|
560
|
-
input.image,
|
|
561
|
-
"-p",
|
|
562
|
-
"-m",
|
|
563
|
-
"1777",
|
|
564
|
-
"/build/tmp",
|
|
565
|
-
"/build/home",
|
|
566
|
-
"/build/work"
|
|
567
|
-
];
|
|
568
|
-
}
|
|
569
|
-
function containerExecArgs(input) {
|
|
570
|
-
const literals = Object.keys(input.env.literals).sort().flatMap((name) => ["-e", `${name}=${input.env.literals[name]}`]);
|
|
571
|
-
const passed = Object.keys(input.env.passed).filter((name) => input.env.passed[name] !== void 0).sort().flatMap((name) => ["-e", name]);
|
|
572
|
-
return [
|
|
573
|
-
"-u",
|
|
574
|
-
input.user,
|
|
575
|
-
...input.workdir === void 0 ? [] : ["-w", input.workdir],
|
|
576
|
-
...literals,
|
|
577
|
-
...passed,
|
|
578
|
-
input.container,
|
|
579
|
-
...input.argv
|
|
580
|
-
];
|
|
503
|
+
function createCpuAllocator(cores) {
|
|
504
|
+
const usable = [...new Set(cores)].sort((a, b) => a - b);
|
|
505
|
+
const held = /* @__PURE__ */ new Map();
|
|
506
|
+
const holders = (core) => {
|
|
507
|
+
let count = 0;
|
|
508
|
+
for (const set of held.values()) if (set.includes(core)) count++;
|
|
509
|
+
return count;
|
|
510
|
+
};
|
|
511
|
+
return {
|
|
512
|
+
acquire(buildId, cpus) {
|
|
513
|
+
const existing = held.get(buildId);
|
|
514
|
+
if (existing !== void 0) return existing;
|
|
515
|
+
const wanted = Math.min(Math.ceil(cpus), usable.length);
|
|
516
|
+
const ranked = usable.map((core) => ({ core, count: holders(core) })).sort((a, b) => a.count - b.count);
|
|
517
|
+
const chosen = ranked.slice(0, wanted).map(({ core }) => core).sort((a, b) => a - b);
|
|
518
|
+
held.set(buildId, chosen);
|
|
519
|
+
return chosen;
|
|
520
|
+
},
|
|
521
|
+
hold(buildId, set) {
|
|
522
|
+
held.set(buildId, [...set]);
|
|
523
|
+
},
|
|
524
|
+
release(buildId) {
|
|
525
|
+
held.delete(buildId);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
581
528
|
}
|
|
582
529
|
var AssetError = class extends Error {
|
|
583
530
|
constructor(file, message) {
|
|
@@ -693,7 +640,7 @@ function templateScript() {
|
|
|
693
640
|
].join("\n");
|
|
694
641
|
}
|
|
695
642
|
var ids = () => ({ uid: String(process.getuid?.() ?? 0), gid: String(process.getgid?.() ?? 0) });
|
|
696
|
-
function converterArgs(dest, extra, script) {
|
|
643
|
+
function converterArgs(dest, memoryBytes, extra, script) {
|
|
697
644
|
const { uid, gid } = ids();
|
|
698
645
|
return [
|
|
699
646
|
"run",
|
|
@@ -701,6 +648,10 @@ function converterArgs(dest, extra, script) {
|
|
|
701
648
|
"-i",
|
|
702
649
|
"--network",
|
|
703
650
|
"none",
|
|
651
|
+
"--memory",
|
|
652
|
+
String(memoryBytes),
|
|
653
|
+
"--memory-swap",
|
|
654
|
+
String(memoryBytes),
|
|
704
655
|
"--mount",
|
|
705
656
|
`type=bind,src=${dirname(dest)},dst=/out`,
|
|
706
657
|
"-e",
|
|
@@ -719,7 +670,7 @@ function converterArgs(dest, extra, script) {
|
|
|
719
670
|
}
|
|
720
671
|
var firstLine2 = (text) => text.trim().split("\n")[0] ?? "";
|
|
721
672
|
function exportInto(container, converter) {
|
|
722
|
-
return new Promise((
|
|
673
|
+
return new Promise((resolve, reject) => {
|
|
723
674
|
const exporter = spawn("docker", ["export", container], { stdio: ["ignore", "pipe", "pipe"], env: childEnvironment(process.env) });
|
|
724
675
|
const writer = spawn("docker", converter, { stdio: ["pipe", "ignore", "pipe"], env: childEnvironment(process.env) });
|
|
725
676
|
const errors = [];
|
|
@@ -736,7 +687,7 @@ function exportInto(container, converter) {
|
|
|
736
687
|
if (code !== 0 && failure === void 0) failure = `${name} exited ${code}`;
|
|
737
688
|
exits += 1;
|
|
738
689
|
if (exits < 2) return;
|
|
739
|
-
if (failure === void 0)
|
|
690
|
+
if (failure === void 0) resolve();
|
|
740
691
|
else reject(new Error(`${failure}: ${firstLine2(errors.join("\n"))}`));
|
|
741
692
|
};
|
|
742
693
|
exporter.once("exit", (code) => done("docker export", code));
|
|
@@ -746,18 +697,18 @@ function exportInto(container, converter) {
|
|
|
746
697
|
function createRootfsHost() {
|
|
747
698
|
const env = () => childEnvironment(process.env);
|
|
748
699
|
return {
|
|
749
|
-
async convert({ imageId, dest }) {
|
|
700
|
+
async convert({ imageId, dest, memoryBytes }) {
|
|
750
701
|
const created = await spawnRun("docker", ["create", imageId, "none"], { env: env() });
|
|
751
702
|
if (created.status !== 0) throw new Error(`docker create ${imageId} failed: ${firstLine2(created.stderr)}`);
|
|
752
703
|
const container = created.stdout.trim();
|
|
753
704
|
try {
|
|
754
|
-
await exportInto(container, converterArgs(dest, [], converterScript()));
|
|
705
|
+
await exportInto(container, converterArgs(dest, memoryBytes, [], converterScript()));
|
|
755
706
|
} finally {
|
|
756
707
|
await spawnRun("docker", ["rm", "--force", "--volumes", container], { env: env() });
|
|
757
708
|
}
|
|
758
709
|
},
|
|
759
|
-
async makeTemplate({ bytes, dest }) {
|
|
760
|
-
const made = await spawnRun("docker", converterArgs(dest, ["-e", `BYTES=${bytes}`], templateScript()), { env: env() });
|
|
710
|
+
async makeTemplate({ bytes, dest, memoryBytes }) {
|
|
711
|
+
const made = await spawnRun("docker", converterArgs(dest, memoryBytes, ["-e", `BYTES=${bytes}`], templateScript()), { env: env() });
|
|
761
712
|
if (made.status !== 0) throw new Error(`making the empty build disk failed: ${firstLine2(made.stderr)}`);
|
|
762
713
|
},
|
|
763
714
|
async imageConfig(imageId) {
|
|
@@ -806,14 +757,14 @@ async function ensureRootfs(input) {
|
|
|
806
757
|
const path = rootfsPathFor(input.runnerDir, input.imageId);
|
|
807
758
|
if (existsSync(path)) return path;
|
|
808
759
|
const { uid, gid } = ids();
|
|
809
|
-
await madeAtomically(path, (temp) => input.host.convert({ imageId: input.imageId, dest: temp, uid: Number(uid), gid: Number(gid) }));
|
|
760
|
+
await madeAtomically(path, (temp) => input.host.convert({ imageId: input.imageId, dest: temp, uid: Number(uid), gid: Number(gid), memoryBytes: input.memoryBytes }));
|
|
810
761
|
return path;
|
|
811
762
|
}
|
|
812
763
|
async function createBuildDisk(input) {
|
|
813
764
|
const template = join(diskTemplatesDir(input.runnerDir), `${input.bytes}.ext4`);
|
|
814
765
|
if (!existsSync(template)) {
|
|
815
766
|
const { uid, gid } = ids();
|
|
816
|
-
await madeAtomically(template, (temp) => input.host.makeTemplate({ bytes: input.bytes, dest: temp, uid: Number(uid), gid: Number(gid) }));
|
|
767
|
+
await madeAtomically(template, (temp) => input.host.makeTemplate({ bytes: input.bytes, dest: temp, uid: Number(uid), gid: Number(gid), memoryBytes: input.memoryBytes }));
|
|
817
768
|
}
|
|
818
769
|
mkdirSync(dirname(input.dest), { recursive: true });
|
|
819
770
|
if (input.host.copyTemplate) input.host.copyTemplate(template, input.dest);
|
|
@@ -885,7 +836,7 @@ function createFirecrackerHost(options = {}) {
|
|
|
885
836
|
const failed = new Promise((_resolve, reject) => child.once("error", reject));
|
|
886
837
|
const pid = child.pid;
|
|
887
838
|
if (pid === void 0) return await failed;
|
|
888
|
-
exits.set(pid, new Promise((
|
|
839
|
+
exits.set(pid, new Promise((resolve) => child.once("exit", () => resolve())));
|
|
889
840
|
child.unref();
|
|
890
841
|
const start = startTimeOf(pid);
|
|
891
842
|
if (start === null) throw new Error(`${command} exited as soon as it started; its console is at ${input.consolePath}`);
|
|
@@ -902,8 +853,8 @@ function createFirecrackerHost(options = {}) {
|
|
|
902
853
|
}
|
|
903
854
|
const exited = exits.get(vm.pid);
|
|
904
855
|
const deadline = Date.now() + STOP_WAIT_MS;
|
|
905
|
-
if (exited !== void 0) await Promise.race([exited, new Promise((
|
|
906
|
-
while (alive(vm) && Date.now() < deadline) await new Promise((
|
|
856
|
+
if (exited !== void 0) await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, STOP_WAIT_MS))]);
|
|
857
|
+
while (alive(vm) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
907
858
|
exits.delete(vm.pid);
|
|
908
859
|
},
|
|
909
860
|
alive
|
|
@@ -913,7 +864,7 @@ function createFirecrackerHost(options = {}) {
|
|
|
913
864
|
// src/runner/microvm/host.ts
|
|
914
865
|
function createMicroVmPorts(over = {}) {
|
|
915
866
|
return {
|
|
916
|
-
assets: over.assets ?? ((
|
|
867
|
+
assets: over.assets ?? ((runnerDir) => ensureAssets({ runnerDir })),
|
|
917
868
|
rootfs: over.rootfs ?? createRootfsHost(),
|
|
918
869
|
firecracker: over.firecracker ?? createFirecrackerHost(),
|
|
919
870
|
cgroup: over.cgroup ?? realCgroupPort(),
|
|
@@ -926,6 +877,10 @@ var aborted = () => ({ ok: false, reason: "", aborted: true });
|
|
|
926
877
|
function firstLine3(text) {
|
|
927
878
|
return text.trim().split("\n")[0] ?? "";
|
|
928
879
|
}
|
|
880
|
+
function gitErrorLine(text) {
|
|
881
|
+
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
882
|
+
return lines.find((line) => /^(fatal|error):/.test(line)) ?? lines.at(-1) ?? "";
|
|
883
|
+
}
|
|
929
884
|
function adoptionFailureReason(result) {
|
|
930
885
|
const line = result.stdout.trim().split("\n").at(-1) ?? "";
|
|
931
886
|
try {
|
|
@@ -940,7 +895,10 @@ async function runBuildSteps(input) {
|
|
|
940
895
|
const rewrite = await exec(["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"], "/build/work");
|
|
941
896
|
if (rewrite.status !== 0) return { ok: false, reason: `configuring the build's git remote failed (exit ${rewrite.status})` };
|
|
942
897
|
const clone = await exec(["git", "clone", "--filter=blob:none", "--single-branch", `https://github.com/${repository}.git`, CHECKOUT_DIR], "/build/work");
|
|
943
|
-
if (clone.status !== 0)
|
|
898
|
+
if (clone.status !== 0) {
|
|
899
|
+
const detail = gitErrorLine(clone.stderr);
|
|
900
|
+
return { ok: false, reason: `the clone of ${repository} failed (exit ${clone.status})${detail === "" ? "" : `: ${detail}`}` };
|
|
901
|
+
}
|
|
944
902
|
try {
|
|
945
903
|
const prepared = await prepareBranch({
|
|
946
904
|
workdir: CHECKOUT_DIR,
|
|
@@ -1155,9 +1113,9 @@ async function git(args, cwd, env, input) {
|
|
|
1155
1113
|
if (size < maxOutputBytes) out.push(chunk);
|
|
1156
1114
|
size += chunk.length;
|
|
1157
1115
|
});
|
|
1158
|
-
const closed = new Promise((
|
|
1116
|
+
const closed = new Promise((resolve, reject) => {
|
|
1159
1117
|
child.once("error", reject);
|
|
1160
|
-
child.once("close", (status) =>
|
|
1118
|
+
child.once("close", (status) => resolve(status));
|
|
1161
1119
|
});
|
|
1162
1120
|
if (input !== void 0) {
|
|
1163
1121
|
await pipeline(input, child.stdin).catch(() => void 0);
|
|
@@ -1354,14 +1312,14 @@ async function drain(req) {
|
|
|
1354
1312
|
}
|
|
1355
1313
|
}
|
|
1356
1314
|
function readPrefix(req, gzip) {
|
|
1357
|
-
return new Promise((
|
|
1315
|
+
return new Promise((resolve, reject) => {
|
|
1358
1316
|
const held = [];
|
|
1359
1317
|
const finish = (section) => {
|
|
1360
1318
|
req.off("data", onData);
|
|
1361
1319
|
req.off("end", onEnd);
|
|
1362
1320
|
req.off("error", reject);
|
|
1363
1321
|
req.pause();
|
|
1364
|
-
|
|
1322
|
+
resolve({ held, section });
|
|
1365
1323
|
};
|
|
1366
1324
|
const onData = (chunk) => {
|
|
1367
1325
|
held.push(chunk);
|
|
@@ -1375,19 +1333,19 @@ function readPrefix(req, gzip) {
|
|
|
1375
1333
|
});
|
|
1376
1334
|
}
|
|
1377
1335
|
function readBody(req, limit) {
|
|
1378
|
-
return new Promise((
|
|
1336
|
+
return new Promise((resolve, reject) => {
|
|
1379
1337
|
const chunks = [];
|
|
1380
1338
|
let size = 0;
|
|
1381
1339
|
req.on("data", (chunk) => {
|
|
1382
1340
|
size += chunk.length;
|
|
1383
1341
|
if (size > limit) {
|
|
1384
1342
|
req.pause();
|
|
1385
|
-
|
|
1343
|
+
resolve(void 0);
|
|
1386
1344
|
return;
|
|
1387
1345
|
}
|
|
1388
1346
|
chunks.push(chunk);
|
|
1389
1347
|
});
|
|
1390
|
-
req.once("end", () =>
|
|
1348
|
+
req.once("end", () => resolve(Buffer.concat(chunks)));
|
|
1391
1349
|
req.once("error", reject);
|
|
1392
1350
|
});
|
|
1393
1351
|
}
|
|
@@ -1400,7 +1358,7 @@ function createGitRemote(options) {
|
|
|
1400
1358
|
const { route, res } = input;
|
|
1401
1359
|
const url = new URL(`${gitUrl}/${route.owner}/${route.name}.git/${route.kind === "info-refs" ? "info/refs" : route.kind === "lfs-batch" ? "info/lfs/objects/batch" : `git-${route.kind}`}${route.search === "" ? "" : `?${route.search}`}`);
|
|
1402
1360
|
const send = url.protocol === "https:" ? request$1 : request;
|
|
1403
|
-
return new Promise((
|
|
1361
|
+
return new Promise((resolve) => {
|
|
1404
1362
|
const upstream = send(url, { method: input.method, headers: { ...input.headers, connection: "close" } }, (answer) => {
|
|
1405
1363
|
const headers = {};
|
|
1406
1364
|
for (const [name, value] of Object.entries(answer.headers)) {
|
|
@@ -1422,18 +1380,18 @@ function createGitRemote(options) {
|
|
|
1422
1380
|
});
|
|
1423
1381
|
answer.once("end", () => {
|
|
1424
1382
|
res.end();
|
|
1425
|
-
|
|
1383
|
+
resolve();
|
|
1426
1384
|
});
|
|
1427
1385
|
answer.once("error", () => {
|
|
1428
1386
|
res.destroy();
|
|
1429
|
-
|
|
1387
|
+
resolve();
|
|
1430
1388
|
});
|
|
1431
1389
|
});
|
|
1432
1390
|
upstream.on("error", (err) => {
|
|
1433
1391
|
log(`could not reach GitHub for ${route.kind}: ${err.message}`);
|
|
1434
1392
|
if (res.headersSent) res.destroy();
|
|
1435
1393
|
else plain(res, 502, "the runner could not reach GitHub\n");
|
|
1436
|
-
|
|
1394
|
+
resolve();
|
|
1437
1395
|
});
|
|
1438
1396
|
res.once("close", () => upstream.destroy());
|
|
1439
1397
|
input.feed(upstream);
|
|
@@ -1728,7 +1686,7 @@ async function startTunnel(socketPath, opts = {}) {
|
|
|
1728
1686
|
if (Buffer.byteLength(socketPath) > MAX_SOCKET_PATH_BYTES) {
|
|
1729
1687
|
throw new Error(`${socketPath} is too long for a Unix socket; keep the runner's config file nearer the root of the filesystem`);
|
|
1730
1688
|
}
|
|
1731
|
-
const
|
|
1689
|
+
const resolve = opts.resolve ?? systemResolve;
|
|
1732
1690
|
const openConnection = opts.connect ?? ((t) => connect({ host: t.address, port: t.port }));
|
|
1733
1691
|
const ownAddresses = opts.hostAddresses ?? systemHostAddresses;
|
|
1734
1692
|
const connectTimeoutMs = opts.connectTimeoutMs ?? CONNECT_TIMEOUT_MS;
|
|
@@ -1812,7 +1770,7 @@ async function startTunnel(socketPath, opts = {}) {
|
|
|
1812
1770
|
}
|
|
1813
1771
|
let addresses;
|
|
1814
1772
|
try {
|
|
1815
|
-
addresses = await
|
|
1773
|
+
addresses = await resolve(host);
|
|
1816
1774
|
} catch {
|
|
1817
1775
|
return { ok: false, status: 502, body: `could not resolve ${host}
|
|
1818
1776
|
` };
|
|
@@ -1983,6 +1941,7 @@ function createContainerRuntime(opts = {}) {
|
|
|
1983
1941
|
const { cli, remoteFor } = opts;
|
|
1984
1942
|
return {
|
|
1985
1943
|
host: opts.host ?? createContainerHost(),
|
|
1944
|
+
cpus: opts.cpus ?? createCpuAllocator(usableCpus()),
|
|
1986
1945
|
tunnels: opts.tunnels ?? /* @__PURE__ */ new Map(),
|
|
1987
1946
|
buildImage: opts.buildImage ?? ((build) => buildRepositoryImage(
|
|
1988
1947
|
{
|
|
@@ -1991,13 +1950,14 @@ function createContainerRuntime(opts = {}) {
|
|
|
1991
1950
|
configPath: build.configPath,
|
|
1992
1951
|
cli: cli ?? currentCliBuild(),
|
|
1993
1952
|
readToken: build.readToken,
|
|
1994
|
-
guestHelper: build.guestHelper
|
|
1953
|
+
guestHelper: build.guestHelper,
|
|
1954
|
+
log: build.log
|
|
1995
1955
|
},
|
|
1996
|
-
{ host: opts.imageHost ?? createDockerHost() }
|
|
1956
|
+
{ host: opts.imageHost ?? createDockerHost({ builder: builderSpec(build.configPath, build.buildMemory, opts.hostEnv ?? process.env) }) }
|
|
1997
1957
|
)),
|
|
1998
1958
|
startTunnel: opts.startTunnel ?? startTunnel,
|
|
1999
1959
|
tunnelOptions: opts.tunnelOptions,
|
|
2000
|
-
sleep: opts.sleep ?? ((ms) => new Promise((
|
|
1960
|
+
sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
2001
1961
|
hostEnv: opts.hostEnv ?? process.env,
|
|
2002
1962
|
credentials: opts.credentials ?? /* @__PURE__ */ new Map(),
|
|
2003
1963
|
github: { gitUrl: opts.github?.gitUrl ?? "https://github.com", apiUrl: opts.github?.apiUrl ?? "https://api.github.com" },
|
|
@@ -2068,7 +2028,17 @@ async function obtainImage(input) {
|
|
|
2068
2028
|
};
|
|
2069
2029
|
}
|
|
2070
2030
|
try {
|
|
2071
|
-
return {
|
|
2031
|
+
return {
|
|
2032
|
+
ok: true,
|
|
2033
|
+
built: await runtime.buildImage({
|
|
2034
|
+
repository,
|
|
2035
|
+
configPath: config.configPath,
|
|
2036
|
+
buildMemory: config.build?.memory ?? "8g",
|
|
2037
|
+
readToken,
|
|
2038
|
+
guestHelper: input.guestHelper,
|
|
2039
|
+
log: (line) => log.line(`${tag} ${line}`)
|
|
2040
|
+
})
|
|
2041
|
+
};
|
|
2072
2042
|
} catch (err) {
|
|
2073
2043
|
if (err instanceof RecipeError && err.code === "build_in_progress" && (input.now?.() ?? /* @__PURE__ */ new Date()).getTime() < input.deadlineMs) {
|
|
2074
2044
|
log.line(`${tag} waiting for another build of ${repository}'s image`);
|
|
@@ -2077,7 +2047,8 @@ async function obtainImage(input) {
|
|
|
2077
2047
|
}
|
|
2078
2048
|
if (err instanceof RecipeError) {
|
|
2079
2049
|
log.error(`${tag} image build for ${repository} failed: ${err.code}: ${err.message}`);
|
|
2080
|
-
|
|
2050
|
+
const says = err.code === "image_build_out_of_memory" || err.code === "image_builder_unavailable";
|
|
2051
|
+
return { ok: false, reason: says ? err.message : err.code };
|
|
2081
2052
|
}
|
|
2082
2053
|
return { ok: false, reason: `the image build failed: ${describeCallFailure(err)}` };
|
|
2083
2054
|
}
|
|
@@ -2118,7 +2089,8 @@ async function provisionContainer(input) {
|
|
|
2118
2089
|
imageId: report.imageId,
|
|
2119
2090
|
containerEnv: report.apply.containerEnv,
|
|
2120
2091
|
builtIns: true,
|
|
2121
|
-
lifecycle: report.apply.lifecycle
|
|
2092
|
+
lifecycle: report.apply.lifecycle,
|
|
2093
|
+
cpuset: formatCpuList(runtime.cpus.acquire(buildId, config.build?.cpus ?? 4))
|
|
2122
2094
|
};
|
|
2123
2095
|
const env = buildEnvironment({
|
|
2124
2096
|
config,
|
|
@@ -2170,7 +2142,7 @@ async function provisionContainer(input) {
|
|
|
2170
2142
|
} catch (err) {
|
|
2171
2143
|
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
2172
2144
|
}
|
|
2173
|
-
const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
|
|
2145
|
+
const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096, cpuset: state.cpuset };
|
|
2174
2146
|
const runnerId = runnerLabelValue(config.configPath);
|
|
2175
2147
|
const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
|
|
2176
2148
|
if (volume.status !== 0) return dockerFailed("creating the build's volume", volume);
|
|
@@ -2401,6 +2373,7 @@ async function cleanupContainer(runtime, state, jobDir) {
|
|
|
2401
2373
|
await tunnel.close();
|
|
2402
2374
|
}
|
|
2403
2375
|
rmSync(state.buildDir, { recursive: true, force: true });
|
|
2376
|
+
runtime.cpus.release(state.buildId);
|
|
2404
2377
|
if (failures.length > 0) throw new Error(failures.join("; "));
|
|
2405
2378
|
}
|
|
2406
2379
|
async function startCheckContainer(input) {
|
|
@@ -2416,7 +2389,8 @@ async function startCheckContainer(input) {
|
|
|
2416
2389
|
const name = `outerlayer-check-${buildId}`;
|
|
2417
2390
|
const buildDir2 = buildDir(config.configPath, buildId);
|
|
2418
2391
|
const agentUser = built.agentUser ?? ADDED_USER;
|
|
2419
|
-
const
|
|
2392
|
+
const cpuset = built.cpuset ?? formatCpuList(runtime.cpus.acquire(buildId, config.build?.cpus ?? 4));
|
|
2393
|
+
const state = { buildId, container: name, volume: name, buildDir: buildDir2, repository, imageId: built.imageId, agentUser, cpuset };
|
|
2420
2394
|
const checksDir = join(jobDir, "checks");
|
|
2421
2395
|
mkdirSync(checksDir, { recursive: true });
|
|
2422
2396
|
const logPath = join(checksDir, "environment.log");
|
|
@@ -2438,7 +2412,7 @@ async function startCheckContainer(input) {
|
|
|
2438
2412
|
} catch (err) {
|
|
2439
2413
|
return fail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
2440
2414
|
}
|
|
2441
|
-
const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
|
|
2415
|
+
const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096, cpuset };
|
|
2442
2416
|
const runnerId = runnerLabelValue(config.configPath);
|
|
2443
2417
|
const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
|
|
2444
2418
|
if (volume.status !== 0) return dockerFailed("creating the check environment's volume", volume);
|
|
@@ -2474,4 +2448,4 @@ async function startCheckContainer(input) {
|
|
|
2474
2448
|
};
|
|
2475
2449
|
}
|
|
2476
2450
|
|
|
2477
|
-
export { ADDED_USER, AgentUserError, AssetError,
|
|
2451
|
+
export { ADDED_USER, AgentUserError, AssetError, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, classifyRefusal, cleanupContainer, containerCommandLine, containerSenderCommand, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, outOfMemoryReason, parseCpuList, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, senderSignalScript, setUpRunnerCgroup, shellQuote, shortClaim, signalInContainer, stageGovernedContext, startCheckContainer, startServicesFor, stopContainer, syncInContainer };
|