@outerlayer/cli 0.7.0 → 0.8.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 +16 -0
- package/README.md +5 -5
- package/dist/{build-JF5UOWKJ.js → build-MS34XEGN.js} +2 -1
- package/dist/build-info.json +1 -1
- package/dist/builder-G52A6GW2.js +13 -0
- package/dist/{check-ASZL5BEO.js → check-FOF3PUKI.js} +21 -18
- package/dist/{chunk-2VO25HHO.js → chunk-3S4P55NE.js} +2 -2
- package/dist/{chunk-MQPRB5K7.js → chunk-4A5E2G4E.js} +6 -6
- package/dist/{chunk-PUN7LMNS.js → chunk-4ACZCY4I.js} +2 -2
- package/dist/{chunk-DGAIVMMN.js → chunk-5QLNZGAQ.js} +2 -2
- package/dist/chunk-5TNS3PL7.js +263 -0
- package/dist/chunk-7RQ73DLO.js +23 -0
- package/dist/{chunk-AJKEHABI.js → chunk-7VNURP6M.js} +248 -50
- package/dist/{chunk-6ZZHWY2Z.js → chunk-AIL2R4SI.js} +1 -1
- package/dist/{chunk-7FTNIAHS.js → chunk-AX23QKHS.js} +7 -3
- package/dist/{chunk-E4PIFIC3.js → chunk-CM5QTKM4.js} +58 -149
- package/dist/chunk-CX22S5BA.js +45 -0
- package/dist/{chunk-D6FXM5OW.js → chunk-GAYWY4GI.js} +8 -8
- package/dist/{chunk-AII7CKTH.js → chunk-IJBVPLZZ.js} +54 -16
- package/dist/{chunk-SSU747O6.js → chunk-ILEA3YHM.js} +1 -1
- package/dist/{chunk-6IYLDODX.js → chunk-IRCBTGWF.js} +4 -4
- package/dist/{chunk-OUUINEYB.js → chunk-JKIWMRFH.js} +1 -1
- package/dist/{chunk-X2FSFU4C.js → chunk-K35RW5HP.js} +2 -2
- package/dist/{chunk-6QWV3YDJ.js → chunk-NALVDFIS.js} +2 -2
- package/dist/{chunk-Q5G2ODUZ.js → chunk-OEK4JEB3.js} +33 -4
- package/dist/chunk-PTW6W5NR.js +127 -0
- package/dist/{chunk-DTXOJDEH.js → chunk-PWBILI5P.js} +3 -2
- package/dist/{chunk-CEBIA5FD.js → chunk-TTNFGMHO.js} +42 -50
- package/dist/{chunk-AQ54MKSQ.js → chunk-TUUMW4TC.js} +2 -2
- package/dist/{chunk-774HEQL6.js → chunk-U3GOI2B4.js} +91 -8
- package/dist/{chunk-F6GFZXZ3.js → chunk-UXYLWVTQ.js} +1 -18
- package/dist/{cli-LEVCGAQV.js → cli-AOJPAVFX.js} +94 -68
- package/dist/{command-FLSYCGIB.js → command-SY3SVLAI.js} +31 -16
- package/dist/{config-I3TVFYLH.js → config-Z3USDBLU.js} +3 -2
- package/dist/{connect-cmd-MJMUONDY.js → connect-cmd-PNZ6IJLJ.js} +2 -2
- package/dist/{dist-PXBSA7QX.js → dist-3ZTZF2TP.js} +1 -1
- package/dist/docker-676FD3CQ.js +15 -0
- package/dist/{doctor-FVDT3KJZ.js → doctor-WR6TRPSU.js} +36 -13
- package/dist/{emit-artifact-cmd-7RPMRSVP.js → emit-artifact-cmd-AVM4YXMQ.js} +4 -4
- package/dist/{emit-criteria-cmd-MKI7WETO.js → emit-criteria-cmd-LDBE4YDS.js} +3 -3
- package/dist/{emit-finding-cmd-RMLGZASB.js → emit-finding-cmd-6R5WBPCH.js} +3 -3
- package/dist/{emit-result-cmd-MTXAZ2GV.js → emit-result-cmd-2DMN6O6U.js} +4 -4
- package/dist/index.js +1 -1
- package/dist/{init-BACXESVV.js → init-KDK5QLQZ.js} +13 -10
- package/dist/{init-VLW7H6RW.js → init-R2FJ5KNS.js} +22 -15
- package/dist/{init-cmd-VJFB5L7X.js → init-cmd-BYZYAJ5P.js} +5 -5
- package/dist/{install-NMAPLLBQ.js → install-FHTVJZS6.js} +5 -4
- package/dist/{install-cmd-B5SEU3G7.js → install-cmd-DCF7TVXD.js} +2 -1
- package/dist/{loop-D3243JKT.js → loop-AG3PLXPO.js} +21 -18
- package/dist/{machine-EB3GEVI4.js → machine-LV6WBRRK.js} +12 -9
- package/dist/{session-sender-LMACEJJS.js → session-sender-Q5HAMOL2.js} +3 -3
- package/dist/{status-F4THTJJK.js → status-HH4OSOHR.js} +15 -12
- package/dist/{sync-cmd-M6JA3OEE.js → sync-cmd-CAEPJR7B.js} +2 -2
- package/dist/{watch-NB5TGPEZ.js → watch-LAAZFBHH.js} +2 -2
- package/dist/{work-claim-cmd-UHMWOS3G.js → work-claim-cmd-ARIYZARE.js} +8 -7
- package/dist/{work-cmd-6UMCFTAW.js → work-cmd-B3FOXZAL.js} +4 -4
- package/dist/{work-comment-cmd-E4LEUIXZ.js → work-comment-cmd-O5WGRI6K.js} +4 -4
- package/dist/{work-open-pr-cmd-OPZWGSEQ.js → work-open-pr-cmd-M6CQW7KW.js} +5 -5
- package/dist/{work-pr-cmd-IDIAFFRU.js → work-pr-cmd-C4YEGKD6.js} +4 -4
- package/package.json +1 -1
- package/dist/docker-NEGDLU6D.js +0 -7
- package/dist/{agent-setup-TXLDY5RV.js → agent-setup-C7PLRXZO.js} +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @outerlayer/cli
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8da1a39: The runner now builds every image in a Buildx builder of its own, whose container has a memory limit of `runner.build.memory`. A recipe step that uses more memory is stopped, and the build is released failed at provision with a reason that names the limit. Hosts need Docker's Buildx plugin (`docker-buildx-plugin`, version 0.18 or later); a host without it claims no work, and `outerlayer doctor` fails an "Image build limit" check that names the package. Housekeeping now trims the runner's builder cache, not Docker's default builder.
|
|
8
|
+
|
|
9
|
+
`outerlayer runner install`, `runner install --service` and `runner init` also write `~/.local/bin/outerlayer`, a launcher that runs the CLI in the runner's `current` link, so the command on PATH follows a self-update. `runner install --replace` replaces a launcher file the runner did not write.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 983c221: A build whose clone fails now says why: the release reason ends with the fatal or error line git printed, not only its exit status.
|
|
14
|
+
- 5a7ca29: The README now says the `OuterLayer evidence` check on GitHub completes once the automated checks pass, without waiting for a person's review in OuterLayer. It also gives the `review` policy key's default correctly as `required`.
|
|
15
|
+
- 628c6c9: A `builtin:vm` build's host checks now run in a new microVM booted from the build's image, where they used to always fail.
|
|
16
|
+
- c2a0315: `outerlayer runner init --vm` sizes the VM's disk with room for the free-disk minimum and the Docker build cache, so the runner in the VM keeps claiming work once its disk fills. With the default limits the disk is 90 GiB, allocated as it is used.
|
|
17
|
+
- 983c221: On WSL2, `outerlayer runner install --service` makes no scheduled task when `WSL_DISTRO_NAME` is unset, as under `sudo`, and prints the `schtasks` command to run instead. It no longer guesses `Ubuntu`, which made a task that started nothing.
|
|
18
|
+
|
|
3
19
|
## 0.7.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -413,11 +413,11 @@ pass or fail on an item that is closed or shipped, has no evaluation yet,
|
|
|
413
413
|
or whose evaluation is still waiting on a session link, with the code
|
|
414
414
|
`not_reviewable`. A recorded pass on the general thread carries the head
|
|
415
415
|
commit of every pull request the item had at the time; once one of them
|
|
416
|
-
moves, that pass stops counting and a fresh review is needed.
|
|
417
|
-
|
|
418
|
-
the
|
|
419
|
-
|
|
420
|
-
|
|
416
|
+
moves, that pass stops counting and a fresh review is needed. The base
|
|
417
|
+
branch's `review` policy key (default `required`, beside `merge_gate`)
|
|
418
|
+
holds the item at "Awaiting a person's review" until a current pass
|
|
419
|
+
exists. The evidence check on GitHub does not wait for that review: it
|
|
420
|
+
completes once the automated checks pass and says the review is pending.
|
|
421
421
|
|
|
422
422
|
A criterion thread is scoped to one acceptance criterion rather than the
|
|
423
423
|
whole item: an agent attaches proof with `outerlayer work comment
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
export { buildRepositoryImage } from './chunk-
|
|
3
|
+
export { buildRepositoryImage } from './chunk-TTNFGMHO.js';
|
|
4
4
|
import './chunk-N5FOE5PS.js';
|
|
5
5
|
export { RecipeError } from './chunk-QOZKPLVJ.js';
|
|
6
|
+
import './chunk-CX22S5BA.js';
|
|
6
7
|
import './chunk-HKMPVPO7.js';
|
|
7
8
|
import './chunk-ZM2IMMYN.js';
|
|
8
9
|
import './chunk-Z5GEFMRW.js';
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"2026-10-
|
|
1
|
+
{"id":"2026-10-05T12:31:55.812Z+ff35a38","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
export { MIN_BUILDX_VERSION, builderName, builderSpec, buildxState, ensureBuilder, ensureFailureCount, isOutOfMemory, proxyEnvironment, trackImageBuild } from './chunk-5TNS3PL7.js';
|
|
4
|
+
import './chunk-CX22S5BA.js';
|
|
5
|
+
import './chunk-GAYWY4GI.js';
|
|
6
|
+
import './chunk-7RQ73DLO.js';
|
|
7
|
+
import './chunk-OD2VTIHN.js';
|
|
8
|
+
import './chunk-HKMPVPO7.js';
|
|
9
|
+
import './chunk-YNJEOGDL.js';
|
|
10
|
+
import './chunk-ZM2IMMYN.js';
|
|
11
|
+
import './chunk-Z5GEFMRW.js';
|
|
12
|
+
|
|
13
|
+
createRequire(import.meta.url);
|
|
@@ -1,43 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { probeCredentials } from './chunk-
|
|
3
|
+
import { probeCredentials } from './chunk-IJBVPLZZ.js';
|
|
4
4
|
import './chunk-ABIWDUMS.js';
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-AJKEHABI.js';
|
|
5
|
+
import './chunk-7VNURP6M.js';
|
|
7
6
|
import './chunk-T6OIJQRT.js';
|
|
8
7
|
import './chunk-AIW6GLET.js';
|
|
9
8
|
import './chunk-UGP76IZ5.js';
|
|
10
|
-
import './chunk-
|
|
9
|
+
import './chunk-3S4P55NE.js';
|
|
11
10
|
import './chunk-WVGSB3Z2.js';
|
|
12
|
-
import './chunk-
|
|
11
|
+
import './chunk-AIL2R4SI.js';
|
|
13
12
|
import './chunk-HHJDN6AU.js';
|
|
14
|
-
import { placeholderQueues, placeholderProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem } from './chunk-
|
|
13
|
+
import { placeholderQueues, placeholderProblem, needsClaudeCredential, claudeCredentialCheck, claimPermissionProblem } from './chunk-IRCBTGWF.js';
|
|
14
|
+
import './chunk-TI7B7UEO.js';
|
|
15
15
|
import './chunk-YYYRBYWG.js';
|
|
16
16
|
import './chunk-TBT347UY.js';
|
|
17
|
-
import './chunk-
|
|
18
|
-
import './chunk-
|
|
19
|
-
import './chunk-
|
|
20
|
-
import './chunk-
|
|
21
|
-
import './chunk-
|
|
17
|
+
import './chunk-NALVDFIS.js';
|
|
18
|
+
import './chunk-TUUMW4TC.js';
|
|
19
|
+
import './chunk-CM5QTKM4.js';
|
|
20
|
+
import './chunk-JKIWMRFH.js';
|
|
21
|
+
import './chunk-TTNFGMHO.js';
|
|
22
22
|
import './chunk-N5FOE5PS.js';
|
|
23
23
|
import './chunk-OGFGZQA3.js';
|
|
24
|
-
import './chunk-
|
|
24
|
+
import './chunk-U3GOI2B4.js';
|
|
25
25
|
import './chunk-QOZKPLVJ.js';
|
|
26
|
+
import './chunk-5TNS3PL7.js';
|
|
27
|
+
import './chunk-CX22S5BA.js';
|
|
26
28
|
import './chunk-ZOAEPWGL.js';
|
|
27
29
|
import './chunk-R7STSBLC.js';
|
|
28
30
|
import './chunk-F7CU5ABH.js';
|
|
29
31
|
import './chunk-BKO6JEZI.js';
|
|
30
|
-
import './chunk-
|
|
31
|
-
import './chunk-
|
|
32
|
+
import './chunk-5QLNZGAQ.js';
|
|
33
|
+
import './chunk-4ACZCY4I.js';
|
|
32
34
|
import './chunk-BFESLKPP.js';
|
|
33
|
-
import './chunk-
|
|
35
|
+
import './chunk-ILEA3YHM.js';
|
|
34
36
|
import './chunk-ZYHKJKSU.js';
|
|
35
|
-
import './chunk-
|
|
37
|
+
import './chunk-GAYWY4GI.js';
|
|
36
38
|
import './chunk-USO2DKBD.js';
|
|
37
39
|
import './chunk-N6LURUEF.js';
|
|
38
40
|
import './chunk-YRYMNBGQ.js';
|
|
39
|
-
import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-
|
|
40
|
-
import './chunk-
|
|
41
|
+
import { loadRunnerConfig, RunnerConfigError, checkHooks } from './chunk-PWBILI5P.js';
|
|
42
|
+
import './chunk-UXYLWVTQ.js';
|
|
43
|
+
import './chunk-7RQ73DLO.js';
|
|
41
44
|
import './chunk-OD2VTIHN.js';
|
|
42
45
|
import './chunk-HKMPVPO7.js';
|
|
43
46
|
import './chunk-GQD22KKN.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { LiveUploader } from './chunk-
|
|
4
|
-
import { CaptureDaemon } from './chunk-
|
|
3
|
+
import { LiveUploader } from './chunk-AIL2R4SI.js';
|
|
4
|
+
import { CaptureDaemon } from './chunk-GAYWY4GI.js';
|
|
5
5
|
import { parseWorkItemEnv } from './chunk-GQD22KKN.js';
|
|
6
6
|
import { spoolDir } from './chunk-JPR2WHHF.js';
|
|
7
7
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { readStatuslineState, STATE_FRESH_MS } from './chunk-F47JBIAW.js';
|
|
4
|
-
import { resolveCredentials, getJson, WorkCommandError } from './chunk-
|
|
5
|
-
import { isLinkedRepository } from './chunk-
|
|
4
|
+
import { resolveCredentials, getJson, WorkCommandError } from './chunk-5QLNZGAQ.js';
|
|
5
|
+
import { isLinkedRepository } from './chunk-ILEA3YHM.js';
|
|
6
6
|
import { sanitizeTerminal } from './chunk-ZYHKJKSU.js';
|
|
7
|
-
import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-
|
|
7
|
+
import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-GAYWY4GI.js';
|
|
8
8
|
import { C } from './chunk-YRYMNBGQ.js';
|
|
9
9
|
import { readSettings, settingsPath, isOurStatuslineSlot, statuslineWrappedOriginal, REGISTERED_EVENTS, isOurHookCommand, ourCommandBins, cliBinResolvable, ourDirectScriptBins, wrappedOriginalCommand } from './chunk-5QRP3MVS.js';
|
|
10
10
|
import { readPin, readContextManifest, readDegraded, readSourceCache } from './chunk-ZZXLM64C.js';
|
|
@@ -1004,7 +1004,7 @@ function doctorExitCode(checks) {
|
|
|
1004
1004
|
|
|
1005
1005
|
// src/doctor-report.ts
|
|
1006
1006
|
async function gatherDoctorChecks() {
|
|
1007
|
-
const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-
|
|
1007
|
+
const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-Z3USDBLU.js');
|
|
1008
1008
|
const configPath = defaultRunnerConfigPath();
|
|
1009
1009
|
const checks = runDoctor({ runnerHost: hasRunnerBlock(configPath) });
|
|
1010
1010
|
const credential = await credentialChecks();
|
|
@@ -1016,7 +1016,7 @@ async function gatherDoctorChecks() {
|
|
|
1016
1016
|
const { runnerVmCheck } = await import('./doctor-53F2PYY7.js');
|
|
1017
1017
|
checks.push(runnerVmCheck(configPath, { lima: realLima(), home: homeFromEnv(), instance: runnerVm.instance }));
|
|
1018
1018
|
} else if (hasRunnerBlock(configPath)) {
|
|
1019
|
-
const { runnerHostChecks } = await import('./doctor-
|
|
1019
|
+
const { runnerHostChecks } = await import('./doctor-WR6TRPSU.js');
|
|
1020
1020
|
const hooks = (() => {
|
|
1021
1021
|
try {
|
|
1022
1022
|
return loadRunnerConfig(configPath).hooks;
|
|
@@ -1051,7 +1051,7 @@ async function gatherDoctorChecks() {
|
|
|
1051
1051
|
const { existsSync: existsSync2 } = await import('fs');
|
|
1052
1052
|
if (process.platform !== "linux" || !existsSync2("/run/systemd/system")) return void 0;
|
|
1053
1053
|
const { inspectService, realInspectProbes } = await import('./inspect-XAUSB7L3.js');
|
|
1054
|
-
const { DEFAULT_UNIT_NAME } = await import('./install-
|
|
1054
|
+
const { DEFAULT_UNIT_NAME } = await import('./install-FHTVJZS6.js');
|
|
1055
1055
|
const { dirname: dirname2 } = await import('path');
|
|
1056
1056
|
return inspectService(DEFAULT_UNIT_NAME, dirname2(configPath), realInspectProbes());
|
|
1057
1057
|
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { fetchFactories, selectFactory, saveFactoryConnection, connectedLine } from './chunk-
|
|
4
|
-
import { resolveRepoIdentity } from './chunk-
|
|
3
|
+
import { fetchFactories, selectFactory, saveFactoryConnection, connectedLine } from './chunk-ILEA3YHM.js';
|
|
4
|
+
import { resolveRepoIdentity } from './chunk-GAYWY4GI.js';
|
|
5
5
|
import { resolveGatewayCredentials } from './chunk-BOLTI6LR.js';
|
|
6
6
|
import { findGitDirs, readRepoConnection } from './chunk-BJ3KTMDH.js';
|
|
7
7
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { autoConnect } from './chunk-
|
|
3
|
+
import { autoConnect } from './chunk-4ACZCY4I.js';
|
|
4
4
|
import { detectActiveSession } from './chunk-BFESLKPP.js';
|
|
5
5
|
import { sanitizeTerminal } from './chunk-ZYHKJKSU.js';
|
|
6
|
-
import { resolveRepoIdentity } from './chunk-
|
|
6
|
+
import { resolveRepoIdentity } from './chunk-GAYWY4GI.js';
|
|
7
7
|
import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
|
|
8
8
|
import { defaultFetch } from './chunk-BCLJSQCV.js';
|
|
9
9
|
import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { BUILDKIT_IMAGE } from './chunk-CX22S5BA.js';
|
|
4
|
+
import { compareVersions } from './chunk-GAYWY4GI.js';
|
|
5
|
+
import { parseSize } from './chunk-7RQ73DLO.js';
|
|
6
|
+
import { JOB_MOUNT, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, RELAY_PORT, DESTINATION_BASE_PORT } from './chunk-OD2VTIHN.js';
|
|
7
|
+
import { runnerDir } from './chunk-ZM2IMMYN.js';
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
import { realpathSync } from 'fs';
|
|
10
|
+
import { join, dirname, resolve } from 'path';
|
|
11
|
+
|
|
12
|
+
createRequire(import.meta.url);
|
|
13
|
+
var BUILD_LABEL = "ai.outerlayer.build";
|
|
14
|
+
var RUNNER_LABEL = "ai.outerlayer.runner";
|
|
15
|
+
function resolvedRunnerDir(configPath) {
|
|
16
|
+
try {
|
|
17
|
+
return realpathSync(runnerDir(configPath));
|
|
18
|
+
} catch {
|
|
19
|
+
try {
|
|
20
|
+
return join(realpathSync(dirname(resolve(configPath))), "runner");
|
|
21
|
+
} catch {
|
|
22
|
+
return resolve(runnerDir(configPath));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function runnerLabelValue(configPath) {
|
|
27
|
+
return createHash("sha256").update(resolvedRunnerDir(configPath)).digest("hex").slice(0, 12);
|
|
28
|
+
}
|
|
29
|
+
function hardened(input) {
|
|
30
|
+
const { memory, cpus, pids } = input.limits;
|
|
31
|
+
return [
|
|
32
|
+
"--label",
|
|
33
|
+
`${BUILD_LABEL}=${input.buildId}`,
|
|
34
|
+
"--label",
|
|
35
|
+
`${RUNNER_LABEL}=${input.runnerId}`,
|
|
36
|
+
"--network",
|
|
37
|
+
"none",
|
|
38
|
+
"--cap-drop",
|
|
39
|
+
"ALL",
|
|
40
|
+
"--security-opt",
|
|
41
|
+
"no-new-privileges",
|
|
42
|
+
"--read-only",
|
|
43
|
+
"--user",
|
|
44
|
+
input.user,
|
|
45
|
+
// The same figure for swap as for memory leaves the container none.
|
|
46
|
+
"--memory",
|
|
47
|
+
memory,
|
|
48
|
+
"--memory-swap",
|
|
49
|
+
memory,
|
|
50
|
+
"--cpus",
|
|
51
|
+
String(cpus),
|
|
52
|
+
"--pids-limit",
|
|
53
|
+
String(pids)
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
function mountPath(path) {
|
|
57
|
+
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`);
|
|
58
|
+
return path;
|
|
59
|
+
}
|
|
60
|
+
function containerRunArgs(input) {
|
|
61
|
+
return [
|
|
62
|
+
"--detach",
|
|
63
|
+
"--name",
|
|
64
|
+
input.container,
|
|
65
|
+
...hardened(input),
|
|
66
|
+
"--mount",
|
|
67
|
+
`type=volume,src=${input.volume},dst=/build`,
|
|
68
|
+
// `/tmp` is a directory of the same volume, so it is measured and limited with the rest of the build.
|
|
69
|
+
"--mount",
|
|
70
|
+
`type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
|
|
71
|
+
"--mount",
|
|
72
|
+
`type=bind,src=${mountPath(input.buildDir)},dst=${JOB_MOUNT},readonly`,
|
|
73
|
+
"--entrypoint",
|
|
74
|
+
"/opt/outerlayer/bin/tini",
|
|
75
|
+
input.image,
|
|
76
|
+
"--",
|
|
77
|
+
"/opt/outerlayer/bin/node",
|
|
78
|
+
"/opt/outerlayer/cli/dist/index.js",
|
|
79
|
+
"broker-relay",
|
|
80
|
+
"--port",
|
|
81
|
+
String(RELAY_PORT),
|
|
82
|
+
"--socket",
|
|
83
|
+
`${JOB_MOUNT}/tunnel.sock`,
|
|
84
|
+
...Array.from({ length: input.destinations ?? 0 }, (_unused, index) => [
|
|
85
|
+
"--forward",
|
|
86
|
+
`${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
|
|
87
|
+
]).flat(),
|
|
88
|
+
...input.builtIns === true ? [
|
|
89
|
+
"--forward",
|
|
90
|
+
`${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
|
|
91
|
+
"--forward",
|
|
92
|
+
`${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
|
|
93
|
+
] : []
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
function prepareRunArgs(input) {
|
|
97
|
+
return [
|
|
98
|
+
"--rm",
|
|
99
|
+
...hardened(input),
|
|
100
|
+
"--mount",
|
|
101
|
+
`type=volume,src=${input.volume},dst=/build`,
|
|
102
|
+
"--entrypoint",
|
|
103
|
+
"mkdir",
|
|
104
|
+
input.image,
|
|
105
|
+
"-p",
|
|
106
|
+
"-m",
|
|
107
|
+
"1777",
|
|
108
|
+
"/build/tmp",
|
|
109
|
+
"/build/home",
|
|
110
|
+
"/build/work"
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
function containerExecArgs(input) {
|
|
114
|
+
const literals = Object.keys(input.env.literals).sort().flatMap((name) => ["-e", `${name}=${input.env.literals[name]}`]);
|
|
115
|
+
const passed = Object.keys(input.env.passed).filter((name) => input.env.passed[name] !== void 0).sort().flatMap((name) => ["-e", name]);
|
|
116
|
+
return [
|
|
117
|
+
"-u",
|
|
118
|
+
input.user,
|
|
119
|
+
...input.workdir === void 0 ? [] : ["-w", input.workdir],
|
|
120
|
+
...literals,
|
|
121
|
+
...passed,
|
|
122
|
+
input.container,
|
|
123
|
+
...input.argv
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/runner/image/builder.ts
|
|
128
|
+
var MIN_BUILDX_VERSION = "0.18.0";
|
|
129
|
+
var INSTALL_HINT = "install Docker's Buildx plugin (docker-buildx-plugin on Debian and Ubuntu with Docker's apt repository, docker-buildx on Ubuntu's own packages)";
|
|
130
|
+
function builderName(configPath) {
|
|
131
|
+
return `outerlayer-${runnerLabelValue(configPath)}`;
|
|
132
|
+
}
|
|
133
|
+
function proxyEnvironment(env) {
|
|
134
|
+
const found = {};
|
|
135
|
+
for (const name of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
136
|
+
const value = env[name];
|
|
137
|
+
if (value !== void 0 && value !== "") found[name] = value;
|
|
138
|
+
}
|
|
139
|
+
return found;
|
|
140
|
+
}
|
|
141
|
+
function builderSpec(configPath, memory, env) {
|
|
142
|
+
return { name: builderName(configPath), memory, env: proxyEnvironment(env) };
|
|
143
|
+
}
|
|
144
|
+
function driverOpt(pair) {
|
|
145
|
+
return /[",]/.test(pair) ? `"${pair.replace(/"/g, '""')}"` : pair;
|
|
146
|
+
}
|
|
147
|
+
async function buildxState(run) {
|
|
148
|
+
const result = await run("docker", ["buildx", "version"]);
|
|
149
|
+
if (result.status !== 0) {
|
|
150
|
+
const said = (result.stderr.trim() || result.stdout.trim()).split("\n")[0] ?? "";
|
|
151
|
+
return { problem: `docker buildx is not available${said === "" ? "" : ` (${said})`}, so image builds cannot be limited; ${INSTALL_HINT}` };
|
|
152
|
+
}
|
|
153
|
+
const version = /\bv?(\d+\.\d+\.\d+)/.exec(result.stdout)?.[1];
|
|
154
|
+
if (version !== void 0 && compareVersions(version, MIN_BUILDX_VERSION) < 0) {
|
|
155
|
+
return { problem: `docker buildx ${version} is too old: the runner's image builder needs Buildx ${MIN_BUILDX_VERSION} or later; update ${INSTALL_HINT}` };
|
|
156
|
+
}
|
|
157
|
+
return "usable";
|
|
158
|
+
}
|
|
159
|
+
function isOutOfMemory(output) {
|
|
160
|
+
if (/cannot allocate memory/i.test(output)) return true;
|
|
161
|
+
if (/did not complete successfully: (exit code: 137|signal: killed)/i.test(output)) return true;
|
|
162
|
+
return /ResourceExhausted/i.test(output) && !/larger than max|message size|too large/i.test(output);
|
|
163
|
+
}
|
|
164
|
+
var running = 0;
|
|
165
|
+
async function trackImageBuild(work) {
|
|
166
|
+
running++;
|
|
167
|
+
try {
|
|
168
|
+
return await work();
|
|
169
|
+
} finally {
|
|
170
|
+
running--;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
var queue = Promise.resolve();
|
|
174
|
+
function serialized(work) {
|
|
175
|
+
const next = queue.then(work, work);
|
|
176
|
+
queue = next.catch(() => void 0);
|
|
177
|
+
return next;
|
|
178
|
+
}
|
|
179
|
+
function readExisting(inspectOutput) {
|
|
180
|
+
const options = /^Driver Options:\s*(.*)$/m.exec(inspectOutput)?.[1] ?? "";
|
|
181
|
+
const option = (key) => new RegExp(`(?:^|\\s)${key}="([^"]*)"`).exec(options)?.[1];
|
|
182
|
+
return { driver: /^Driver:\s*(\S+)/m.exec(inspectOutput)?.[1], memory: option("memory"), image: option("image") };
|
|
183
|
+
}
|
|
184
|
+
function sameMemory(found, wanted) {
|
|
185
|
+
return (parseSize(found) ?? found) === (parseSize(wanted) ?? wanted);
|
|
186
|
+
}
|
|
187
|
+
var IDLE_POLL_MS = 1e3;
|
|
188
|
+
var failures = 0;
|
|
189
|
+
function ensureFailureCount() {
|
|
190
|
+
return failures;
|
|
191
|
+
}
|
|
192
|
+
function ensureBuilder(rawRun, spec, options = {}) {
|
|
193
|
+
const run = (command, args, opts) => rawRun(command, args, { ...opts, timeoutMs: args.includes("--bootstrap") ? 15 * 6e4 : 2 * 6e4 });
|
|
194
|
+
const result = serialized(async () => {
|
|
195
|
+
const state = await buildxState(run);
|
|
196
|
+
if (state !== "usable") return { ok: false, problem: state.problem };
|
|
197
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
198
|
+
const inspected = await run("docker", ["buildx", "inspect", spec.name]);
|
|
199
|
+
let create = inspected.status !== 0;
|
|
200
|
+
if (!create) {
|
|
201
|
+
const found = readExisting(inspected.stdout);
|
|
202
|
+
const current = found.driver === "docker-container" && sameMemory(found.memory ?? "", spec.memory) && found.image === BUILDKIT_IMAGE;
|
|
203
|
+
if (!current && options.replaceStale === false) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
problem: `the image builder ${spec.name} was made with other settings than runner.build.memory (${spec.memory}) and the pinned BuildKit image; the runner service replaces it when no build is running, or remove it with docker buildx rm ${spec.name}`
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (!current) {
|
|
210
|
+
if (options.waitForIdle === true) while (running > 0) await sleep(IDLE_POLL_MS);
|
|
211
|
+
if (running === 0) {
|
|
212
|
+
const removed = await run("docker", ["buildx", "rm", spec.name]);
|
|
213
|
+
if (removed.status !== 0) return { ok: false, problem: `could not replace the image builder ${spec.name}: ${removed.stderr.trim() || `docker exited ${removed.status}`}` };
|
|
214
|
+
create = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (create) {
|
|
219
|
+
const opts = [
|
|
220
|
+
`memory=${spec.memory}`,
|
|
221
|
+
`memory-swap=${spec.memory}`,
|
|
222
|
+
`image=${BUILDKIT_IMAGE}`,
|
|
223
|
+
...Object.entries(spec.env ?? {}).map(([name, value]) => `env.${name}=${value}`),
|
|
224
|
+
...spec.driverOpts ?? []
|
|
225
|
+
];
|
|
226
|
+
const created = await run("docker", [
|
|
227
|
+
"buildx",
|
|
228
|
+
"create",
|
|
229
|
+
"--name",
|
|
230
|
+
spec.name,
|
|
231
|
+
"--driver",
|
|
232
|
+
"docker-container",
|
|
233
|
+
...opts.flatMap((opt) => ["--driver-opt", driverOpt(opt)])
|
|
234
|
+
]);
|
|
235
|
+
if (created.status !== 0) {
|
|
236
|
+
const said = created.stderr.trim() || `docker exited ${created.status}`;
|
|
237
|
+
if (/failed to find driver/i.test(said)) {
|
|
238
|
+
return { ok: false, problem: `this Docker's Buildx has no docker-container driver, so image builds cannot be limited; ${INSTALL_HINT}` };
|
|
239
|
+
}
|
|
240
|
+
return { ok: false, problem: `could not create the image builder ${spec.name}: ${said}` };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (options.bootstrap === true) {
|
|
244
|
+
const started = await run("docker", ["buildx", "inspect", "--bootstrap", spec.name]);
|
|
245
|
+
if (started.status !== 0) {
|
|
246
|
+
return { ok: false, problem: `the image builder ${spec.name} did not start: ${started.stderr.trim() || `docker exited ${started.status}`}` };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { ok: true, changed: create };
|
|
250
|
+
});
|
|
251
|
+
return result.then(
|
|
252
|
+
(outcome) => {
|
|
253
|
+
if (!outcome.ok) failures++;
|
|
254
|
+
return outcome;
|
|
255
|
+
},
|
|
256
|
+
(err) => {
|
|
257
|
+
failures++;
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export { BUILD_LABEL, MIN_BUILDX_VERSION, RUNNER_LABEL, builderName, builderSpec, buildxState, containerExecArgs, containerRunArgs, ensureBuilder, ensureFailureCount, isOutOfMemory, prepareRunArgs, proxyEnvironment, runnerLabelValue, trackImageBuild };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// src/runner/container/disk.ts
|
|
7
|
+
var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3 };
|
|
8
|
+
function parseSize(text) {
|
|
9
|
+
const match = /^(\d+(?:\.\d+)?)([kKmMgG]?)$/.exec(text);
|
|
10
|
+
if (match === null) return void 0;
|
|
11
|
+
const bytes = Number(match[1]) * UNITS[match[2].toLowerCase()];
|
|
12
|
+
return bytes > 0 ? bytes : void 0;
|
|
13
|
+
}
|
|
14
|
+
function parseDuKilobytes(output) {
|
|
15
|
+
const match = /^(\d+)\s/.exec(output);
|
|
16
|
+
return match === null ? void 0 : Number(match[1]);
|
|
17
|
+
}
|
|
18
|
+
function exceedsDisk(usedKilobytes, limit) {
|
|
19
|
+
const bytes = parseSize(limit);
|
|
20
|
+
return usedKilobytes * 1024 > (bytes ?? Number.POSITIVE_INFINITY);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { exceedsDisk, parseDuKilobytes, parseSize };
|