@outerlayer/cli 0.5.1 → 0.7.0
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 +29 -0
- package/README.md +19 -0
- package/dist/{agent-setup-IFH3FB5X.js → agent-setup-TXLDY5RV.js} +7 -6
- package/dist/{build-PAEKVYVK.js → build-JF5UOWKJ.js} +2 -1
- package/dist/build-info.json +1 -1
- package/dist/{check-4Q33B5ZE.js → check-ASZL5BEO.js} +39 -23
- package/dist/chunk-2VO25HHO.js +75 -0
- package/dist/{chunk-XDDW4FRS.js → chunk-5FWNEQNZ.js} +3 -3
- package/dist/{chunk-CHLVJNTE.js → chunk-6IYLDODX.js} +18 -6
- package/dist/{chunk-W7UTFZDH.js → chunk-6QWV3YDJ.js} +2 -2
- package/dist/{chunk-EGABNHZG.js → chunk-6ZZHWY2Z.js} +149 -18
- package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
- package/dist/chunk-7FTNIAHS.js +211 -0
- package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
- package/dist/{chunk-F4NU4H3V.js → chunk-AII7CKTH.js} +133 -35
- package/dist/{chunk-ZXBITXTS.js → chunk-AIW6GLET.js} +1 -1
- package/dist/{chunk-UWH5DSUP.js → chunk-AJKEHABI.js} +507 -90
- package/dist/{chunk-RTXKYLCA.js → chunk-AQ54MKSQ.js} +2 -2
- package/dist/{chunk-Z62HNKEJ.js → chunk-CEBIA5FD.js} +31 -5
- package/dist/{chunk-EX6M5JPR.js → chunk-D6FXM5OW.js} +6302 -28611
- package/dist/{chunk-RMAZ2NWA.js → chunk-DGAIVMMN.js} +23 -2
- package/dist/{chunk-3DD6AOWC.js → chunk-DTXOJDEH.js} +26 -2
- package/dist/{chunk-T52PKE2Y.js → chunk-E4PIFIC3.js} +244 -17
- package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
- package/dist/chunk-FBTE2A2X.js +38 -0
- package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
- package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
- package/dist/chunk-HKMPVPO7.js +55 -0
- package/dist/{chunk-UFFXNXLQ.js → chunk-JPR2WHHF.js} +31 -29
- package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
- package/dist/{chunk-H4CK2YK4.js → chunk-MQPRB5K7.js} +18 -10
- package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
- package/dist/chunk-OUUINEYB.js +27 -0
- package/dist/chunk-PUN7LMNS.js +39 -0
- package/dist/{install-cmd-7WXOIFDO.js → chunk-Q5G2ODUZ.js} +1 -2
- package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
- package/dist/{chunk-OR6AEEDO.js → chunk-SSU747O6.js} +85 -25
- package/dist/chunk-T6OIJQRT.js +31 -0
- package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
- package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
- package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
- package/dist/{chunk-JLDZTG3A.js → chunk-X2FSFU4C.js} +4 -1
- package/dist/chunk-YNJEOGDL.js +22434 -0
- package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
- package/dist/{chunk-WOBNRVQT.js → chunk-ZYHKJKSU.js} +1 -1
- package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
- package/dist/{cli-LHSUYYMI.js → cli-LEVCGAQV.js} +154 -79
- package/dist/command-FLSYCGIB.js +187 -0
- package/dist/config-I3TVFYLH.js +10 -0
- package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-MJMUONDY.js} +4 -4
- package/dist/{context-adopt-UMA4O5NY.js → context-adopt-CE4KDXS3.js} +5 -4
- package/dist/{context-materialize-G2FUFC72.js → context-materialize-QL33HDJN.js} +5 -4
- package/dist/{dist-WDV7U7UJ.js → dist-PXBSA7QX.js} +2 -1
- package/dist/{doctor-MOA3RIEP.js → doctor-FVDT3KJZ.js} +46 -19
- package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-7RPMRSVP.js} +13 -7
- package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
- package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-MKI7WETO.js} +12 -3
- package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-RMLGZASB.js} +14 -3
- package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-MTXAZ2GV.js} +15 -9
- package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
- package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
- package/dist/{hook-fast-EYENPK6F.js → hook-fast-XBOZ3N6L.js} +1 -1
- package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
- package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
- package/dist/index.js +3 -2
- package/dist/{init-FYDJAKTM.js → init-BACXESVV.js} +21 -14
- package/dist/{init-2IPOIXJV.js → init-VLW7H6RW.js} +40 -62
- package/dist/{init-cmd-KLIDRVXQ.js → init-cmd-VJFB5L7X.js} +10 -9
- package/dist/inspect-XAUSB7L3.js +63 -0
- package/dist/install-NMAPLLBQ.js +14 -0
- package/dist/install-cmd-B5SEU3G7.js +8 -0
- package/dist/lima-7NPCNGWT.js +6 -0
- package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
- package/dist/loop-D3243JKT.js +64 -0
- package/dist/{machine-ECU3QCZD.js → machine-EB3GEVI4.js} +20 -13
- package/dist/session-sender-LMACEJJS.js +29 -0
- package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
- package/dist/{status-CLATHCTQ.js → status-F4THTJJK.js} +36 -16
- package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-M6JA3OEE.js} +7 -7
- package/dist/{watch-3YW6Z3DQ.js → watch-NB5TGPEZ.js} +7 -7
- package/dist/{work-claim-cmd-3OPWA77E.js → work-claim-cmd-UHMWOS3G.js} +14 -7
- package/dist/{work-cmd-NUR4CAG6.js → work-cmd-6UMCFTAW.js} +9 -3
- package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-E4LEUIXZ.js} +9 -3
- package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
- package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-OPZWGSEQ.js} +10 -4
- package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-IDIAFFRU.js} +9 -2
- package/package.json +2 -1
- package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
- package/dist/chunk-D2KWEAN6.js +0 -55
- package/dist/chunk-ZOC7JSYC.js +0 -83
- package/dist/config-HL2AIR3K.js +0 -9
- package/dist/loop-NEBUGD2Q.js +0 -45
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { autoConnect } from './chunk-PUN7LMNS.js';
|
|
3
4
|
import { detectActiveSession } from './chunk-BFESLKPP.js';
|
|
4
|
-
import { sanitizeTerminal } from './chunk-
|
|
5
|
-
import { resolveRepoIdentity } from './chunk-
|
|
5
|
+
import { sanitizeTerminal } from './chunk-ZYHKJKSU.js';
|
|
6
|
+
import { resolveRepoIdentity } from './chunk-D6FXM5OW.js';
|
|
6
7
|
import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
|
|
7
8
|
import { defaultFetch } from './chunk-BCLJSQCV.js';
|
|
8
9
|
import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
|
|
@@ -79,6 +80,10 @@ function resolveCredentials(opts) {
|
|
|
79
80
|
if (!resolved.ok) throw new WorkCommandError(resolved.message);
|
|
80
81
|
return { url: resolved.url, apiKey: resolved.credential, appId: resolved.appId };
|
|
81
82
|
}
|
|
83
|
+
async function connectIfOneFits(opts) {
|
|
84
|
+
const refusal = await autoConnect(opts);
|
|
85
|
+
if (refusal !== void 0) throw new WorkCommandError(refusal);
|
|
86
|
+
}
|
|
82
87
|
function resolveRepository(repoFlag, cwd) {
|
|
83
88
|
if (repoFlag) return repoFlag;
|
|
84
89
|
const identity = resolveRepoIdentity(cwd);
|
|
@@ -299,6 +304,7 @@ async function runWorkBuild(opts) {
|
|
|
299
304
|
const cwd = opts.cwd ?? process.cwd();
|
|
300
305
|
const env = opts.env ?? process.env;
|
|
301
306
|
const home = opts.home ?? homeFromEnv(env);
|
|
307
|
+
await connectIfOneFits(opts);
|
|
302
308
|
const { url, apiKey, appId } = resolveCredentials(opts);
|
|
303
309
|
const fetchImpl = opts.fetchImpl ?? defaultFetch;
|
|
304
310
|
const facts = environmentFacts({ cwd, env, home, sessionIdOverride: opts.sessionId });
|
|
@@ -337,6 +343,18 @@ async function runWorkBuild(opts) {
|
|
|
337
343
|
return { data, output };
|
|
338
344
|
}
|
|
339
345
|
async function runWorkLinkSession(opts) {
|
|
346
|
+
try {
|
|
347
|
+
return await linkSession(opts);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
if (opts.logFailures) {
|
|
350
|
+
const { logHookError } = await import('./hook-fast-XBOZ3N6L.js');
|
|
351
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
352
|
+
logHookError(opts.home ?? homeFromEnv(opts.env ?? process.env), new Error(`work link-session #${opts.number} failed: ${reason}`));
|
|
353
|
+
}
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function linkSession(opts) {
|
|
340
358
|
const cwd = opts.cwd ?? process.cwd();
|
|
341
359
|
const env = opts.env ?? process.env;
|
|
342
360
|
const home = opts.home ?? homeFromEnv(env);
|
|
@@ -377,6 +395,7 @@ async function runWorkRemove(opts) {
|
|
|
377
395
|
const cwd = opts.cwd ?? process.cwd();
|
|
378
396
|
const env = opts.env ?? process.env;
|
|
379
397
|
const home = opts.home ?? homeFromEnv(env);
|
|
398
|
+
await connectIfOneFits(opts);
|
|
380
399
|
const creds = resolveCredentials(opts);
|
|
381
400
|
if (!opts.reason) {
|
|
382
401
|
throw new WorkCommandError("--reason is required \u2014 a removal records why");
|
|
@@ -420,6 +439,7 @@ async function runWorkRemove(opts) {
|
|
|
420
439
|
}
|
|
421
440
|
async function runWorkStatus(opts) {
|
|
422
441
|
const cwd = opts.cwd ?? process.cwd();
|
|
442
|
+
await connectIfOneFits(opts);
|
|
423
443
|
const creds = resolveCredentials(opts);
|
|
424
444
|
const fetchByItem = opts.item !== void 0 ? parsePositiveInt(opts.item, "--item") : void 0;
|
|
425
445
|
if (fetchByItem !== void 0) {
|
|
@@ -449,6 +469,7 @@ async function runWorkStatus(opts) {
|
|
|
449
469
|
}
|
|
450
470
|
async function runWorkList(opts) {
|
|
451
471
|
const cwd = opts.cwd ?? process.cwd();
|
|
472
|
+
await connectIfOneFits(opts);
|
|
452
473
|
const { url, apiKey, appId } = resolveCredentials(opts);
|
|
453
474
|
const fetchImpl = opts.fetchImpl ?? defaultFetch;
|
|
454
475
|
const repository = opts.repo ?? resolveRepoIdentity(cwd).gitRepo;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { parseSize, MAX_ALLOW_HOSTS, allowHostProblem } from './chunk-F6GFZXZ3.js';
|
|
4
|
-
import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-
|
|
4
|
+
import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-OD2VTIHN.js';
|
|
5
5
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
6
6
|
import { existsSync, statSync, accessSync, constants, readFileSync } from 'fs';
|
|
7
7
|
import { hostname } from 'os';
|
|
@@ -30,6 +30,9 @@ var DEFAULT_CONCURRENCY = 1;
|
|
|
30
30
|
var DEFAULT_POLL_SECONDS = 60;
|
|
31
31
|
var DEFAULT_TIME_LIMIT_MINUTES = 480;
|
|
32
32
|
var DEFAULT_IDLE_LIMIT_MINUTES = 30;
|
|
33
|
+
var DEFAULT_CHECK_TIME_LIMIT_MINUTES = 10;
|
|
34
|
+
var CHECKS_KEYS = ["timeLimitMinutes"];
|
|
35
|
+
var MAX_CHECK_TIME_LIMIT_MINUTES = 240;
|
|
33
36
|
var DEFAULT_BUILD = { memory: "8g", cpus: 4, pids: 4096, disk: "40g", variables: [] };
|
|
34
37
|
var BUILD_KEYS = ["memory", "cpus", "pids", "disk", "variables", "destinations", "allowHosts"];
|
|
35
38
|
var HOUSEKEEPING_KEYS = ["jobsKeepDays", "buildCacheMax", "minFreeDisk"];
|
|
@@ -81,6 +84,7 @@ var RUNNER_KEYS = [
|
|
|
81
84
|
"hooks",
|
|
82
85
|
"build",
|
|
83
86
|
"housekeeping",
|
|
87
|
+
"checks",
|
|
84
88
|
"autoUpdate"
|
|
85
89
|
];
|
|
86
90
|
function runnerNumbers() {
|
|
@@ -199,6 +203,7 @@ function loadRunnerConfig(configPath) {
|
|
|
199
203
|
checkBuiltinHooks(configPath, runner.hooks);
|
|
200
204
|
const build = resolveBuild(configPath, runner.build);
|
|
201
205
|
const housekeeping = resolveHousekeeping(configPath, runner.housekeeping, build.disk);
|
|
206
|
+
const checks = resolveChecks(configPath, runner.checks);
|
|
202
207
|
if (config.scrub?.literals !== void 0 && !Array.isArray(config.scrub.literals)) {
|
|
203
208
|
fail(configPath, "scrub.literals", "must be a list of strings");
|
|
204
209
|
}
|
|
@@ -226,6 +231,7 @@ function loadRunnerConfig(configPath) {
|
|
|
226
231
|
},
|
|
227
232
|
build,
|
|
228
233
|
housekeeping,
|
|
234
|
+
checks,
|
|
229
235
|
// A host with executables of its own for provision and cleanup chose
|
|
230
236
|
// its own environment, and an update could change what its hooks run
|
|
231
237
|
// under; it opts in. The built-in hooks are the runner's own. Provision
|
|
@@ -275,6 +281,24 @@ function resolveHousekeeping(configPath, block, buildDisk) {
|
|
|
275
281
|
if (minFreeDisk === void 0) fail(configPath, "runner.housekeeping.minFreeDisk", 'must be a size such as "40g"');
|
|
276
282
|
return { jobsKeepDays: keys.jobsKeepDays ?? DEFAULT_JOBS_KEEP_DAYS, buildCacheMax, minFreeDisk };
|
|
277
283
|
}
|
|
284
|
+
function resolveChecks(configPath, block) {
|
|
285
|
+
const given = block ?? {};
|
|
286
|
+
if (typeof given !== "object" || Array.isArray(given)) fail(configPath, "runner.checks", "must be an object");
|
|
287
|
+
const keys = given;
|
|
288
|
+
for (const key of Object.keys(keys)) {
|
|
289
|
+
if (CHECKS_KEYS.includes(key)) continue;
|
|
290
|
+
const near = nearestKey(key, CHECKS_KEYS);
|
|
291
|
+
fail(configPath, `runner.checks.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
|
|
292
|
+
}
|
|
293
|
+
const limit = keys.timeLimitMinutes === void 0 ? DEFAULT_CHECK_TIME_LIMIT_MINUTES : keys.timeLimitMinutes;
|
|
294
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
295
|
+
fail(configPath, "runner.checks.timeLimitMinutes", "must be a whole number of at least 1");
|
|
296
|
+
}
|
|
297
|
+
if (limit > MAX_CHECK_TIME_LIMIT_MINUTES) {
|
|
298
|
+
fail(configPath, "runner.checks.timeLimitMinutes", `must be at most ${MAX_CHECK_TIME_LIMIT_MINUTES}`);
|
|
299
|
+
}
|
|
300
|
+
return { timeLimitMinutes: limit };
|
|
301
|
+
}
|
|
278
302
|
function wholeBytes(bytes) {
|
|
279
303
|
if (bytes === void 0) return void 0;
|
|
280
304
|
const whole = Math.floor(bytes);
|
|
@@ -334,4 +358,4 @@ function resolveBuild(configPath, block) {
|
|
|
334
358
|
};
|
|
335
359
|
}
|
|
336
360
|
|
|
337
|
-
export { RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
|
|
361
|
+
export { DEFAULT_CHECK_TIME_LIMIT_MINUTES, RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { readDefaultBranchHead, cloneRepository, buildRepositoryImage, 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-
|
|
3
|
+
import { readDefaultBranchHead, cloneRepository, buildRepositoryImage, 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-CEBIA5FD.js';
|
|
4
4
|
import { startTimeOf, isSameProcess } from './chunk-N5FOE5PS.js';
|
|
5
5
|
import { currentCliBuild } from './chunk-OGFGZQA3.js';
|
|
6
6
|
import { createDockerHost, childEnvironment, spawnRun } from './chunk-774HEQL6.js';
|
|
7
7
|
import { RecipeError } from './chunk-QOZKPLVJ.js';
|
|
8
|
-
import { execCapture } from './chunk-
|
|
9
|
-
import { GUEST_CID } from './chunk-
|
|
10
|
-
import { WorkCommandError, postJson } from './chunk-
|
|
8
|
+
import { execCapture } from './chunk-ZOAEPWGL.js';
|
|
9
|
+
import { GUEST_CID } from './chunk-R7STSBLC.js';
|
|
10
|
+
import { WorkCommandError, postJson } from './chunk-DGAIVMMN.js';
|
|
11
11
|
import { parseDuKilobytes, hostAllowed } from './chunk-F6GFZXZ3.js';
|
|
12
|
-
import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, AGENT_CREDENTIAL_MISSING, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT } from './chunk-
|
|
13
|
-
import { defaultGitRunner, materializeContext } from './chunk-
|
|
14
|
-
import { readContextManifest, readSourceCache } from './chunk-
|
|
12
|
+
import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, checkEnvironment, AGENT_CREDENTIAL_MISSING, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT } from './chunk-OD2VTIHN.js';
|
|
13
|
+
import { defaultGitRunner, materializeContext } from './chunk-5FWNEQNZ.js';
|
|
14
|
+
import { readContextManifest, readSourceCache } from './chunk-ZZXLM64C.js';
|
|
15
15
|
import { signingFetchFor } from './chunk-BCLJSQCV.js';
|
|
16
16
|
import { signerFor } from './chunk-M5POMKKW.js';
|
|
17
17
|
import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, runnerDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
|
|
18
18
|
import { relative, join, dirname, resolve, basename } from 'path';
|
|
19
|
+
import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync, realpathSync, renameSync, lstatSync, unlinkSync, openSync, closeSync, mkdtempSync, createWriteStream, createReadStream } from 'fs';
|
|
19
20
|
import { spawnSync, spawn } from 'child_process';
|
|
20
21
|
import { createHash, randomBytes } from 'crypto';
|
|
21
|
-
import { existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, realpathSync, renameSync, lstatSync, readFileSync, unlinkSync, readdirSync, openSync, closeSync, mkdtempSync, createWriteStream, statSync, createReadStream } from 'fs';
|
|
22
22
|
import { createServer, request } from 'http';
|
|
23
23
|
import { request as request$1 } from 'https';
|
|
24
24
|
import { Transform } from 'stream';
|
|
25
25
|
import { pipeline, finished } from 'stream/promises';
|
|
26
|
-
import { createGunzip, gunzipSync, constants } from 'zlib';
|
|
26
|
+
import { createGunzip, gunzipSync, constants as constants$1 } from 'zlib';
|
|
27
27
|
import { isIP, connect, createServer as createServer$1 } from 'net';
|
|
28
28
|
import { lookup } from 'dns/promises';
|
|
29
29
|
import { networkInterfaces } from 'os';
|
|
@@ -255,6 +255,116 @@ var BuildTokens = class {
|
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
257
|
};
|
|
258
|
+
var BUILD_PREFIX = "build-";
|
|
259
|
+
var LEAF = "runner";
|
|
260
|
+
function serviceDirectory(selfCgroup, mount) {
|
|
261
|
+
const line = selfCgroup?.split("\n").find((l) => l.startsWith("0::"));
|
|
262
|
+
if (line === void 0) return void 0;
|
|
263
|
+
const path = line.slice(3).trim();
|
|
264
|
+
if (!path.endsWith(".service")) return void 0;
|
|
265
|
+
return join(mount, path);
|
|
266
|
+
}
|
|
267
|
+
function prepareRunnerCgroup(setup) {
|
|
268
|
+
if (setup.invocationId === void 0 || setup.invocationId === "") return void 0;
|
|
269
|
+
const root = serviceDirectory(setup.selfCgroup, setup.mount);
|
|
270
|
+
if (root === void 0) return void 0;
|
|
271
|
+
const { fs } = setup;
|
|
272
|
+
if (!fs.exists(root) || !fs.writable(join(root, "cgroup.procs"))) return void 0;
|
|
273
|
+
if (!(fs.read(join(root, "cgroup.controllers")) ?? "").split(/\s+/).includes("memory")) return void 0;
|
|
274
|
+
for (const name of fs.list(root)) {
|
|
275
|
+
if (!name.startsWith(BUILD_PREFIX)) continue;
|
|
276
|
+
try {
|
|
277
|
+
fs.rmdir(join(root, name));
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!fs.exists(join(root, LEAF))) fs.mkdir(join(root, LEAF));
|
|
282
|
+
fs.write(join(root, LEAF, "cgroup.procs"), String(setup.pid));
|
|
283
|
+
fs.write(join(root, "cgroup.subtree_control"), "+memory");
|
|
284
|
+
return { root };
|
|
285
|
+
}
|
|
286
|
+
var BUILD_RECLAIM_HEADROOM_BYTES = 256 * 1024 ** 2;
|
|
287
|
+
var BUILD_KILL_HEADROOM_BYTES = 1024 ** 3;
|
|
288
|
+
function createBuildCgroup(fs, root, buildId, buildMemoryBytes) {
|
|
289
|
+
const path = join(root, `${BUILD_PREFIX}${buildId}`);
|
|
290
|
+
fs.mkdir(path);
|
|
291
|
+
try {
|
|
292
|
+
fs.write(join(path, "memory.high"), String(buildMemoryBytes + BUILD_RECLAIM_HEADROOM_BYTES));
|
|
293
|
+
fs.write(join(path, "memory.max"), String(buildMemoryBytes + BUILD_KILL_HEADROOM_BYTES));
|
|
294
|
+
fs.write(join(path, "memory.swap.max"), "0");
|
|
295
|
+
} catch (err) {
|
|
296
|
+
removeBuildCgroup(fs, path);
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
return path;
|
|
300
|
+
}
|
|
301
|
+
function outOfMemoryReason(memory) {
|
|
302
|
+
return `the build ran out of memory: the kernel killed its microVM at the limit of runner.build.memory (${memory ?? "8g"}) plus 1 GiB for Firecracker and its disks' page cache. Raise runner.build.memory, or lower runner.concurrency`;
|
|
303
|
+
}
|
|
304
|
+
function oomKills(fs, path) {
|
|
305
|
+
const match = /^oom_kill (\d+)$/m.exec(fs.read(join(path, "memory.events")) ?? "");
|
|
306
|
+
return match === null ? 0 : Number(match[1]);
|
|
307
|
+
}
|
|
308
|
+
function removeBuildCgroup(fs, path) {
|
|
309
|
+
try {
|
|
310
|
+
fs.rmdir(path);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
if (err.code !== "ENOENT") throw err;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function realCgroupFs() {
|
|
316
|
+
return {
|
|
317
|
+
exists: (path) => existsSync(path),
|
|
318
|
+
read: (path) => {
|
|
319
|
+
try {
|
|
320
|
+
return readFileSync(path, "utf8");
|
|
321
|
+
} catch {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
write: (path, data) => writeFileSync(path, data),
|
|
326
|
+
mkdir: (path) => mkdirSync(path),
|
|
327
|
+
rmdir: (path) => rmdirSync(path),
|
|
328
|
+
list: (path) => readdirSync(path).filter((name) => statSync(join(path, name)).isDirectory()),
|
|
329
|
+
writable: (path) => {
|
|
330
|
+
try {
|
|
331
|
+
accessSync(path, constants.W_OK);
|
|
332
|
+
return true;
|
|
333
|
+
} catch {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
var delegatedRoot;
|
|
340
|
+
function setUpRunnerCgroup(over = {}) {
|
|
341
|
+
let selfCgroup;
|
|
342
|
+
try {
|
|
343
|
+
selfCgroup = readFileSync("/proc/self/cgroup", "utf8");
|
|
344
|
+
} catch {
|
|
345
|
+
selfCgroup = void 0;
|
|
346
|
+
}
|
|
347
|
+
let refusal;
|
|
348
|
+
try {
|
|
349
|
+
const prepared = prepareRunnerCgroup({ fs: realCgroupFs(), selfCgroup, pid: process.pid, invocationId: process.env.INVOCATION_ID, mount: "/sys/fs/cgroup", ...over });
|
|
350
|
+
delegatedRoot = prepared?.root;
|
|
351
|
+
} catch (err) {
|
|
352
|
+
delegatedRoot = void 0;
|
|
353
|
+
refusal = err instanceof Error ? err.message : String(err);
|
|
354
|
+
}
|
|
355
|
+
return { delegated: delegatedRoot !== void 0, ...refusal === void 0 ? {} : { refusal } };
|
|
356
|
+
}
|
|
357
|
+
function realCgroupPort() {
|
|
358
|
+
const fs = realCgroupFs();
|
|
359
|
+
return {
|
|
360
|
+
create: (buildId, bytes) => {
|
|
361
|
+
if (delegatedRoot === void 0) return void 0;
|
|
362
|
+
return createBuildCgroup(fs, delegatedRoot, buildId, bytes);
|
|
363
|
+
},
|
|
364
|
+
oomKills: (path) => oomKills(fs, path),
|
|
365
|
+
remove: (path) => removeBuildCgroup(fs, path)
|
|
366
|
+
};
|
|
367
|
+
}
|
|
258
368
|
var BranchPrepareError = class extends Error {
|
|
259
369
|
};
|
|
260
370
|
var OWN_PREFIX = "outerlayer/";
|
|
@@ -431,12 +541,12 @@ function containerRunArgs(input) {
|
|
|
431
541
|
"--forward",
|
|
432
542
|
`${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
|
|
433
543
|
]).flat(),
|
|
434
|
-
...[
|
|
544
|
+
...input.builtIns === true ? [
|
|
435
545
|
"--forward",
|
|
436
546
|
`${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
|
|
437
547
|
"--forward",
|
|
438
548
|
`${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
|
|
439
|
-
]
|
|
549
|
+
] : []
|
|
440
550
|
];
|
|
441
551
|
}
|
|
442
552
|
function prepareRunArgs(input) {
|
|
@@ -747,7 +857,9 @@ function machineConfig(limits2, paths) {
|
|
|
747
857
|
};
|
|
748
858
|
}
|
|
749
859
|
function firecrackerCommand(input) {
|
|
750
|
-
|
|
860
|
+
const args = ["--no-api", "--config-file", input.configPath];
|
|
861
|
+
if (input.cgroup === void 0) return { command: input.binary, args };
|
|
862
|
+
return { command: "sh", args: ["-c", 'echo $$ > "$1/cgroup.procs" && shift && exec "$@"', "sh", input.cgroup, input.binary, ...args] };
|
|
751
863
|
}
|
|
752
864
|
var STOP_WAIT_MS = 1e4;
|
|
753
865
|
var POLL_MS = 20;
|
|
@@ -804,6 +916,7 @@ function createMicroVmPorts(over = {}) {
|
|
|
804
916
|
assets: over.assets ?? ((runnerDir2) => ensureAssets({ runnerDir: runnerDir2 })),
|
|
805
917
|
rootfs: over.rootfs ?? createRootfsHost(),
|
|
806
918
|
firecracker: over.firecracker ?? createFirecrackerHost(),
|
|
919
|
+
cgroup: over.cgroup ?? realCgroupPort(),
|
|
807
920
|
exec: over.exec ?? execCapture
|
|
808
921
|
};
|
|
809
922
|
}
|
|
@@ -1093,7 +1206,7 @@ function decode(prefix, gzip) {
|
|
|
1093
1206
|
if (!gzip) return prefix;
|
|
1094
1207
|
try {
|
|
1095
1208
|
const { compressedWindow, inflatedBytes } = limits();
|
|
1096
|
-
return gunzipSync(prefix.subarray(0, compressedWindow), { finishFlush: constants.Z_SYNC_FLUSH, maxOutputLength: inflatedBytes });
|
|
1209
|
+
return gunzipSync(prefix.subarray(0, compressedWindow), { finishFlush: constants$1.Z_SYNC_FLUSH, maxOutputLength: inflatedBytes });
|
|
1097
1210
|
} catch (err) {
|
|
1098
1211
|
const code = err.code;
|
|
1099
1212
|
if (code === "ERR_BUFFER_TOO_LARGE") return refused(TOO_LONG);
|
|
@@ -1429,6 +1542,11 @@ function createGitRemote(options) {
|
|
|
1429
1542
|
req.resume();
|
|
1430
1543
|
return plain(res, 404, "not found\n");
|
|
1431
1544
|
}
|
|
1545
|
+
if (options.readOnly === true && (route.kind === "receive-pack" || route.kind === "info-refs" && route.service === "git-receive-pack")) {
|
|
1546
|
+
log(`push refused: this remote is read-only`);
|
|
1547
|
+
req.resume();
|
|
1548
|
+
return plain(res, 403, "this remote is read-only: a check cannot push\n");
|
|
1549
|
+
}
|
|
1432
1550
|
const own = isOwnRepository(options.repository, route.owner, route.name);
|
|
1433
1551
|
if (own && route.kind === "receive-pack") return push(req, res, route);
|
|
1434
1552
|
let kind = route.kind === "info-refs" && route.service === "git-receive-pack" ? "push" : "read";
|
|
@@ -1437,6 +1555,7 @@ function createGitRemote(options) {
|
|
|
1437
1555
|
body = await readBody(req, maxLfsBodyBytes);
|
|
1438
1556
|
if (body === void 0) return plain(res, 413, "the request is too large\n");
|
|
1439
1557
|
kind = isLfsUpload(body) ? "push" : "read";
|
|
1558
|
+
if (options.readOnly === true && kind === "push") return plain(res, 403, "this remote is read-only: a check cannot upload\n");
|
|
1440
1559
|
}
|
|
1441
1560
|
let authorization;
|
|
1442
1561
|
if (own) {
|
|
@@ -1913,7 +2032,8 @@ function gitRemoteFor(runtime, input) {
|
|
|
1913
2032
|
onWorkflowRefused: () => {
|
|
1914
2033
|
input.tokens.reason = WORKFLOW_PERMISSION_REASON;
|
|
1915
2034
|
},
|
|
1916
|
-
log: input.log
|
|
2035
|
+
log: input.log,
|
|
2036
|
+
...input.readOnly === true ? { readOnly: true } : {}
|
|
1917
2037
|
});
|
|
1918
2038
|
}
|
|
1919
2039
|
function firstLine4(text) {
|
|
@@ -1997,7 +2117,8 @@ async function provisionContainer(input) {
|
|
|
1997
2117
|
repository,
|
|
1998
2118
|
imageId: report.imageId,
|
|
1999
2119
|
containerEnv: report.apply.containerEnv,
|
|
2000
|
-
builtIns: true
|
|
2120
|
+
builtIns: true,
|
|
2121
|
+
lifecycle: report.apply.lifecycle
|
|
2001
2122
|
};
|
|
2002
2123
|
const env = buildEnvironment({
|
|
2003
2124
|
config,
|
|
@@ -2063,7 +2184,9 @@ async function provisionContainer(input) {
|
|
|
2063
2184
|
image: report.imageId,
|
|
2064
2185
|
user: ADDED_USER,
|
|
2065
2186
|
limits: limits2,
|
|
2066
|
-
destinations: config.build?.destinations?.length ?? 0
|
|
2187
|
+
destinations: config.build?.destinations?.length ?? 0,
|
|
2188
|
+
builtIns: true
|
|
2189
|
+
};
|
|
2067
2190
|
const prepared = await host.run(prepareRunArgs(runInput), { logPath });
|
|
2068
2191
|
if (prepared.status !== 0) return dockerFailed("preparing the build's volume", prepared);
|
|
2069
2192
|
const started = await host.run(containerRunArgs(runInput), { logPath });
|
|
@@ -2149,6 +2272,40 @@ function containerCommandLine(input) {
|
|
|
2149
2272
|
env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed }
|
|
2150
2273
|
};
|
|
2151
2274
|
}
|
|
2275
|
+
function containerSenderCommand(input) {
|
|
2276
|
+
const state = input.job.container;
|
|
2277
|
+
const env = buildEnvironment({
|
|
2278
|
+
config: input.config,
|
|
2279
|
+
item: input.job.item,
|
|
2280
|
+
itemKey: input.job.itemKey,
|
|
2281
|
+
branch: input.job.branch,
|
|
2282
|
+
repository: state.repository,
|
|
2283
|
+
hostEnv: input.runtime.hostEnv,
|
|
2284
|
+
containerEnv: state.containerEnv ?? {},
|
|
2285
|
+
builtIns: state.builtIns === true,
|
|
2286
|
+
identity: input.job.commitIdentity
|
|
2287
|
+
});
|
|
2288
|
+
const args = containerExecArgs({
|
|
2289
|
+
container: state.container,
|
|
2290
|
+
user: state.agentUser ?? ADDED_USER,
|
|
2291
|
+
workdir: CHECKOUT_DIR,
|
|
2292
|
+
env,
|
|
2293
|
+
argv: ["outerlayer", "session-sender"]
|
|
2294
|
+
});
|
|
2295
|
+
return { command: "docker", args: ["exec", ...args], env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
|
|
2296
|
+
}
|
|
2297
|
+
function senderSignalScript(pidfile, signal) {
|
|
2298
|
+
const file = shellQuote(pidfile);
|
|
2299
|
+
return `p="$(cat ${file})" && [ -n "$p" ] && grep -qa session-sender "/proc/$p/cmdline" && kill -${signal} "$p"`;
|
|
2300
|
+
}
|
|
2301
|
+
async function signalInContainer(runtime, state, pidfile, signal) {
|
|
2302
|
+
await runtime.host.exec({
|
|
2303
|
+
container: state.container,
|
|
2304
|
+
user: state.agentUser ?? ADDED_USER,
|
|
2305
|
+
env: NO_ENV,
|
|
2306
|
+
argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)]
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2152
2309
|
async function stopContainer(runtime, state) {
|
|
2153
2310
|
await runtime.host.stop(state.container);
|
|
2154
2311
|
state.stopped = true;
|
|
@@ -2246,5 +2403,75 @@ async function cleanupContainer(runtime, state, jobDir) {
|
|
|
2246
2403
|
rmSync(state.buildDir, { recursive: true, force: true });
|
|
2247
2404
|
if (failures.length > 0) throw new Error(failures.join("; "));
|
|
2248
2405
|
}
|
|
2406
|
+
async function startCheckContainer(input) {
|
|
2407
|
+
const { config, runtime, job, jobDir, tokens, log } = input;
|
|
2408
|
+
const { host } = runtime;
|
|
2409
|
+
const tag = `#${job.item}`;
|
|
2410
|
+
const built = job.container;
|
|
2411
|
+
const repository = built?.repository ?? job.repository;
|
|
2412
|
+
if (built === void 0 || built.imageId === void 0 || repository === void 0 || job.branch === void 0) {
|
|
2413
|
+
return { ok: false, reason: "the build's image, repository or branch is not recorded" };
|
|
2414
|
+
}
|
|
2415
|
+
const buildId = randomBytes(6).toString("hex");
|
|
2416
|
+
const name = `outerlayer-check-${buildId}`;
|
|
2417
|
+
const buildDir2 = buildDir(config.configPath, buildId);
|
|
2418
|
+
const agentUser = built.agentUser ?? ADDED_USER;
|
|
2419
|
+
const state = { buildId, container: name, volume: name, buildDir: buildDir2, repository, imageId: built.imageId, agentUser };
|
|
2420
|
+
const checksDir = join(jobDir, "checks");
|
|
2421
|
+
mkdirSync(checksDir, { recursive: true });
|
|
2422
|
+
const logPath = join(checksDir, "environment.log");
|
|
2423
|
+
input.save(state);
|
|
2424
|
+
const fail = async (reason) => {
|
|
2425
|
+
try {
|
|
2426
|
+
await cleanupContainer(runtime, state);
|
|
2427
|
+
} catch (err) {
|
|
2428
|
+
log.error(`${tag} cleanup of the check environment failed: ${describeCallFailure(err)}`);
|
|
2429
|
+
}
|
|
2430
|
+
return { ok: false, reason };
|
|
2431
|
+
};
|
|
2432
|
+
const dockerFailed = (what, result) => fail(`${what} failed (exit ${result.status})${firstLine4(result.stderr) === "" ? "" : `: ${firstLine4(result.stderr)}`}`);
|
|
2433
|
+
mkdirSync(buildDir2, { recursive: true });
|
|
2434
|
+
chmodSync(buildDir2, 493);
|
|
2435
|
+
try {
|
|
2436
|
+
const gitRemote = gitRemoteFor(runtime, { repository, branch: job.branch, tokens, buildDir: buildDir2, log: (line) => log.line(`${tag} ${line}`), readOnly: true });
|
|
2437
|
+
runtime.tunnels.set(buildId, await startServicesFor(runtime, config, buildDir2, checksDir, void 0, false, gitRemote));
|
|
2438
|
+
} catch (err) {
|
|
2439
|
+
return fail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
2440
|
+
}
|
|
2441
|
+
const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
|
|
2442
|
+
const runnerId = runnerLabelValue(config.configPath);
|
|
2443
|
+
const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
|
|
2444
|
+
if (volume.status !== 0) return dockerFailed("creating the check environment's volume", volume);
|
|
2445
|
+
const runInput = {
|
|
2446
|
+
buildId,
|
|
2447
|
+
runnerId,
|
|
2448
|
+
container: name,
|
|
2449
|
+
volume: name,
|
|
2450
|
+
buildDir: buildDir2,
|
|
2451
|
+
image: built.imageId,
|
|
2452
|
+
user: ADDED_USER,
|
|
2453
|
+
limits: limits2,
|
|
2454
|
+
destinations: config.build?.destinations?.length ?? 0,
|
|
2455
|
+
builtIns: false
|
|
2456
|
+
};
|
|
2457
|
+
const prepared = await host.run(prepareRunArgs(runInput), { logPath });
|
|
2458
|
+
if (prepared.status !== 0) return dockerFailed("preparing the check environment's volume", prepared);
|
|
2459
|
+
const started = await host.run(containerRunArgs(runInput), { logPath });
|
|
2460
|
+
if (started.status !== 0) return dockerFailed("starting the check environment's container", started);
|
|
2461
|
+
if (!await waitForRelay(runtime, state)) return fail("the relay in the check environment's container did not start");
|
|
2462
|
+
const env = checkEnvironment({ config, item: job.item, repository, hostEnv: runtime.hostEnv, containerEnv: built.containerEnv ?? {} });
|
|
2463
|
+
return {
|
|
2464
|
+
ok: true,
|
|
2465
|
+
container: {
|
|
2466
|
+
state,
|
|
2467
|
+
exec: (argv, workdir, opts) => host.exec({ container: name, user: agentUser, workdir, env, argv }, opts?.logPath === void 0 ? void 0 : { logPath: opts.logPath }),
|
|
2468
|
+
restart: async () => {
|
|
2469
|
+
await host.stop(name);
|
|
2470
|
+
const again = await host.start(name);
|
|
2471
|
+
return again.status === 0 && await waitForRelay(runtime, state);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2249
2476
|
|
|
2250
|
-
export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, shellQuote, shortClaim, stageGovernedContext, startServicesFor, stopContainer, syncInContainer };
|
|
2477
|
+
export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, containerSenderCommand, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, outOfMemoryReason, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, senderSignalScript, setUpRunnerCgroup, shellQuote, shortClaim, signalInContainer, stageGovernedContext, startCheckContainer, startServicesFor, stopContainer, syncInContainer };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-
|
|
4
|
-
import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-
|
|
3
|
+
import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-VXLJORHL.js';
|
|
4
|
+
import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-7ZR5UPMB.js';
|
|
5
5
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, readdirSync } from 'fs';
|
|
6
6
|
import { join, dirname, relative, sep } from 'path';
|
|
7
7
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// src/runner/vm/service.ts
|
|
7
|
+
var VM_SERVICE_NAME = "outerlayer-runner";
|
|
8
|
+
var UNIT_MARKER = "# Written by outerlayer runner install --service; edit the runner config, not this file.";
|
|
9
|
+
var STOP_MARGIN_MINUTES = 10;
|
|
10
|
+
function escapeUnitPath(path) {
|
|
11
|
+
return path.replace(/%/g, "%%").replace(/ /g, "\\x20");
|
|
12
|
+
}
|
|
13
|
+
function runnerUnit(unit) {
|
|
14
|
+
return `${UNIT_MARKER}
|
|
15
|
+
[Unit]
|
|
16
|
+
Description=OuterLayer runner
|
|
17
|
+
After=network-online.target${unit.docker ? " docker.service" : ""}
|
|
18
|
+
Wants=network-online.target
|
|
19
|
+
${unit.docker ? "Requires=docker.service\n" : ""}
|
|
20
|
+
[Service]
|
|
21
|
+
User=${unit.user}
|
|
22
|
+
ExecStart=${unit.execStart}
|
|
23
|
+
EnvironmentFile=-${escapeUnitPath(`${unit.configDir}/runner.env`)}
|
|
24
|
+
Restart=on-failure
|
|
25
|
+
RestartForceExitStatus=75
|
|
26
|
+
RestartSec=30
|
|
27
|
+
KillSignal=SIGTERM
|
|
28
|
+
KillMode=mixed
|
|
29
|
+
TimeoutStopSec=${unit.timeLimitMinutes + STOP_MARGIN_MINUTES}min
|
|
30
|
+
OOMPolicy=continue
|
|
31
|
+
Delegate=yes
|
|
32
|
+
|
|
33
|
+
[Install]
|
|
34
|
+
WantedBy=multi-user.target
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { UNIT_MARKER, VM_SERVICE_NAME, runnerUnit };
|
|
@@ -27,7 +27,12 @@ function launchesPath(home) {
|
|
|
27
27
|
}
|
|
28
28
|
function appendLaunchRecord(input, home) {
|
|
29
29
|
mkdirSync(join(home, ".outerlayer", "spool"), { recursive: true });
|
|
30
|
-
const record = {
|
|
30
|
+
const record = {
|
|
31
|
+
sessionId: input.sessionId,
|
|
32
|
+
item: input.item,
|
|
33
|
+
...input.job === void 0 ? {} : { job: input.job },
|
|
34
|
+
t: input.t ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
35
|
+
};
|
|
31
36
|
appendFileSync(launchesPath(home), `${JSON.stringify(record)}
|
|
32
37
|
`);
|
|
33
38
|
}
|
|
@@ -75,22 +80,28 @@ function launchedItemsBySession(home, readImpl = readLaunchSpool) {
|
|
|
75
80
|
}
|
|
76
81
|
function readLaunchSpoolState(home, readImpl = readLaunchSpool) {
|
|
77
82
|
const ids = /* @__PURE__ */ new Set();
|
|
83
|
+
const records = /* @__PURE__ */ new Map();
|
|
78
84
|
const read = readImpl(launchesPath(home));
|
|
79
|
-
if (read.kind === "unreadable") return { ids, unreadable: read.reason };
|
|
80
|
-
if (read.kind === "absent") return { ids };
|
|
85
|
+
if (read.kind === "unreadable") return { ids, records, unreadable: read.reason };
|
|
86
|
+
if (read.kind === "absent") return { ids, records };
|
|
81
87
|
let malformedLines = 0;
|
|
82
88
|
for (const line of read.text.split("\n")) {
|
|
83
89
|
const text = line.trim();
|
|
84
90
|
if (!text) continue;
|
|
85
91
|
try {
|
|
86
92
|
const record = JSON.parse(text);
|
|
87
|
-
if (typeof record.sessionId === "string" && record.sessionId !== "")
|
|
88
|
-
|
|
93
|
+
if (typeof record.sessionId === "string" && record.sessionId !== "") {
|
|
94
|
+
ids.add(record.sessionId);
|
|
95
|
+
records.set(record.sessionId, {
|
|
96
|
+
...typeof record.item === "number" ? { item: record.item } : {},
|
|
97
|
+
...typeof record.job === "string" ? { job: record.job } : {}
|
|
98
|
+
});
|
|
99
|
+
} else malformedLines += 1;
|
|
89
100
|
} catch {
|
|
90
101
|
malformedLines += 1;
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
|
-
return { ids, ...malformedLines > 0 ? { malformedLines } : {} };
|
|
104
|
+
return { ids, records, ...malformedLines > 0 ? { malformedLines } : {} };
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
export { appendLaunchRecord, launchStatusIsDecisive, launchedItemForSession, launchedItemsBySession, launchesPath, parseWorkItemEnv, readLaunchSpoolState };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { readLaunchSpoolState, launchesPath } from './chunk-
|
|
3
|
+
import { readLaunchSpoolState, launchesPath } from './chunk-GQD22KKN.js';
|
|
4
4
|
|
|
5
5
|
createRequire(import.meta.url);
|
|
6
6
|
|
|
@@ -17,18 +17,33 @@ function launchRefusalForSession(sessionId, home) {
|
|
|
17
17
|
return `this session was not launched with OUTERLAYER_WORK naming a work item, so nothing it produces may leave the machine. Start the session with OUTERLAYER_WORK set (for example OUTERLAYER_WORK=42), or run this from CI.`;
|
|
18
18
|
}
|
|
19
19
|
var LaunchFilter = class {
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* `records`, when given, scopes a live sender:
|
|
22
|
+
* - a build sender (`build`) admits only a session launched for its item in its own job; a record with no job
|
|
23
|
+
* is one an older hook wrote, which is admitted on the item alone;
|
|
24
|
+
* - any other live sender skips a session launched inside a runner job, which only that job's sender sends.
|
|
25
|
+
* `sync` passes no records and decides by the launch record's existence alone.
|
|
26
|
+
*/
|
|
27
|
+
constructor(launchedSessionIds, records, build) {
|
|
21
28
|
this.launchedSessionIds = launchedSessionIds;
|
|
29
|
+
this.records = records;
|
|
30
|
+
this.build = build;
|
|
22
31
|
/** Sessions dropped for carrying no launch record — for the sync summary. */
|
|
23
32
|
this.skippedTotal = 0;
|
|
24
33
|
}
|
|
25
34
|
/** True when the session should sync. Records skips for reporting. */
|
|
26
35
|
allows(session) {
|
|
27
36
|
const key = session.subagent?.parentSessionId ?? session.id;
|
|
28
|
-
const allowed = typeof key === "string" && this.launchedSessionIds.has(key);
|
|
37
|
+
const allowed = typeof key === "string" && this.launchedSessionIds.has(key) && this.inScope(this.records?.get(key));
|
|
29
38
|
if (!allowed) this.skippedTotal += 1;
|
|
30
39
|
return allowed;
|
|
31
40
|
}
|
|
41
|
+
inScope(facts) {
|
|
42
|
+
if (this.build !== void 0) {
|
|
43
|
+
return facts !== void 0 && facts.item === this.build.item && (facts.job === void 0 || facts.job === this.build.jobDir);
|
|
44
|
+
}
|
|
45
|
+
return facts?.job === void 0;
|
|
46
|
+
}
|
|
32
47
|
};
|
|
33
48
|
|
|
34
49
|
export { LaunchFilter, launchRefusalForSession };
|