@archme/cli 0.15.0 → 0.15.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/dist/cli.js +171 -7
- package/package.json +1 -1
- package/sandbox-image.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10110,6 +10110,39 @@ function buildInitVolumeArgs(volume, uid, labels, image, slot) {
|
|
|
10110
10110
|
"/work"
|
|
10111
10111
|
];
|
|
10112
10112
|
}
|
|
10113
|
+
function buildAnchorArgs(slot, image) {
|
|
10114
|
+
assertImage(image);
|
|
10115
|
+
const network = slotNetwork(slot);
|
|
10116
|
+
return [
|
|
10117
|
+
"run",
|
|
10118
|
+
"-d",
|
|
10119
|
+
"--rm",
|
|
10120
|
+
"--init",
|
|
10121
|
+
"--read-only",
|
|
10122
|
+
"--cap-drop",
|
|
10123
|
+
"ALL",
|
|
10124
|
+
"--user",
|
|
10125
|
+
`${BOX_UID}:${BOX_UID}`,
|
|
10126
|
+
"--no-dns",
|
|
10127
|
+
"--kernel-arg",
|
|
10128
|
+
"ipv6.disable=1",
|
|
10129
|
+
"--progress",
|
|
10130
|
+
"none",
|
|
10131
|
+
"--cpus",
|
|
10132
|
+
"1",
|
|
10133
|
+
"--memory",
|
|
10134
|
+
`${INIT_MEMORY_MIB}M`,
|
|
10135
|
+
"--name",
|
|
10136
|
+
anchorBoxName(slot),
|
|
10137
|
+
...labelArgs({ [JOB_LABEL]: "anchor", [SLOT_LABEL]: String(slot.index) }),
|
|
10138
|
+
"--network",
|
|
10139
|
+
network,
|
|
10140
|
+
"--entrypoint",
|
|
10141
|
+
"sleep",
|
|
10142
|
+
image,
|
|
10143
|
+
"infinity"
|
|
10144
|
+
];
|
|
10145
|
+
}
|
|
10113
10146
|
function buildCreateVolumeArgs(name, sizeGiB, labels) {
|
|
10114
10147
|
assertName("volume", name);
|
|
10115
10148
|
if (!Number.isInteger(sizeGiB) || sizeGiB < 1) throw new Error(`sandbox: invalid volume size ${sizeGiB}`);
|
|
@@ -10200,7 +10233,7 @@ function defaultSpawn(bin = "container") {
|
|
|
10200
10233
|
};
|
|
10201
10234
|
};
|
|
10202
10235
|
}
|
|
10203
|
-
var MIN_MEMORY_MIB, STDOUT_CAP_BYTES, STDERR_CAP_BYTES, INIT_MEMORY_MIB, NAME_RE, ENV_KEY_RE, LABEL_KEY_RE, CONTROL_RE, obj, str, TailBuffer, AppleContainerRuntime;
|
|
10236
|
+
var MIN_MEMORY_MIB, STDOUT_CAP_BYTES, STDERR_CAP_BYTES, INIT_MEMORY_MIB, NAME_RE, ENV_KEY_RE, LABEL_KEY_RE, CONTROL_RE, anchorBoxName, obj, str, TailBuffer, AppleContainerRuntime;
|
|
10204
10237
|
var init_appleContainer = __esm({
|
|
10205
10238
|
"src/daemon/sandbox/appleContainer.ts"() {
|
|
10206
10239
|
"use strict";
|
|
@@ -10215,6 +10248,7 @@ var init_appleContainer = __esm({
|
|
|
10215
10248
|
ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
10216
10249
|
LABEL_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
10217
10250
|
CONTROL_RE = /[\u0000\n\r]/;
|
|
10251
|
+
anchorBoxName = (slot) => `archme-anchor-${slot.index}`;
|
|
10218
10252
|
obj = (v) => v && typeof v === "object" ? v : {};
|
|
10219
10253
|
str = (v) => typeof v === "string" ? v : null;
|
|
10220
10254
|
TailBuffer = class {
|
|
@@ -10324,6 +10358,11 @@ var init_appleContainer = __esm({
|
|
|
10324
10358
|
}
|
|
10325
10359
|
return slots;
|
|
10326
10360
|
}
|
|
10361
|
+
/** Starts the slot's anchor box detached; resolves once it is running. */
|
|
10362
|
+
async startAnchor(slot, image) {
|
|
10363
|
+
await this.must(buildAnchorArgs(slot, image), `anchor ${slot.network}`);
|
|
10364
|
+
return anchorBoxName(slot);
|
|
10365
|
+
}
|
|
10327
10366
|
async createVolume(name, sizeGiB, labels) {
|
|
10328
10367
|
await this.must(buildCreateVolumeArgs(name, sizeGiB, labels), `volume create ${name}`);
|
|
10329
10368
|
}
|
|
@@ -17167,6 +17206,99 @@ var init_semaphore = __esm({
|
|
|
17167
17206
|
}
|
|
17168
17207
|
});
|
|
17169
17208
|
|
|
17209
|
+
// src/daemon/sandbox/bridges.ts
|
|
17210
|
+
function holding(gateway, ifaces) {
|
|
17211
|
+
return Object.entries(ifaces).filter(([, addrs]) => (addrs ?? []).some((a2) => a2.family === "IPv4" && a2.address === gateway)).map(([name]) => name);
|
|
17212
|
+
}
|
|
17213
|
+
function gatewayPresent(slot, ifaces) {
|
|
17214
|
+
return holding(slot.gateway, ifaces).length > 0;
|
|
17215
|
+
}
|
|
17216
|
+
function bridgeMapFailures(slots, ifaces) {
|
|
17217
|
+
const failures = [];
|
|
17218
|
+
const owner = /* @__PURE__ */ new Map();
|
|
17219
|
+
for (const s of slots) {
|
|
17220
|
+
const on6 = holding(s.gateway, ifaces);
|
|
17221
|
+
if (on6.length === 0) {
|
|
17222
|
+
failures.push(`bridge: slot ${s.index} gateway ${s.gateway} is on no host interface`);
|
|
17223
|
+
continue;
|
|
17224
|
+
}
|
|
17225
|
+
for (const name of on6) {
|
|
17226
|
+
if (!/^bridge\d+$/.test(name)) failures.push(`bridge: slot ${s.index} gateway is on ${name}, not a bridge`);
|
|
17227
|
+
const other = owner.get(name);
|
|
17228
|
+
if (other !== void 0) failures.push(`bridge: slots ${other} and ${s.index} share ${name}`);
|
|
17229
|
+
else owner.set(name, s.index);
|
|
17230
|
+
}
|
|
17231
|
+
}
|
|
17232
|
+
return failures;
|
|
17233
|
+
}
|
|
17234
|
+
function helperNetworks() {
|
|
17235
|
+
return ["default", ...Array.from({ length: MAX_SLOTS }, (_, i2) => slotFor(i2).network)];
|
|
17236
|
+
}
|
|
17237
|
+
function parseHelperPids(ps, networks) {
|
|
17238
|
+
return ps.split("\n").flatMap((line) => {
|
|
17239
|
+
const m = HELPER_RE.exec(line);
|
|
17240
|
+
return m && networks.has(m[2]) ? [Number(m[1])] : [];
|
|
17241
|
+
});
|
|
17242
|
+
}
|
|
17243
|
+
async function resetNetworkHelpers(networks, deps) {
|
|
17244
|
+
const want = new Set(networks);
|
|
17245
|
+
const pids = parseHelperPids(await deps.ps(), want);
|
|
17246
|
+
const signal = (sig, list2) => {
|
|
17247
|
+
for (const pid of list2) {
|
|
17248
|
+
try {
|
|
17249
|
+
deps.kill(pid, sig);
|
|
17250
|
+
} catch {
|
|
17251
|
+
}
|
|
17252
|
+
}
|
|
17253
|
+
};
|
|
17254
|
+
const survivors = async () => {
|
|
17255
|
+
const now = new Set(parseHelperPids(await deps.ps(), want));
|
|
17256
|
+
return pids.filter((p) => now.has(p));
|
|
17257
|
+
};
|
|
17258
|
+
const waitGone = async () => {
|
|
17259
|
+
let left2 = await survivors();
|
|
17260
|
+
for (let i2 = 0; i2 < 20 && left2.length > 0; i2++) {
|
|
17261
|
+
await deps.sleep(250);
|
|
17262
|
+
left2 = await survivors();
|
|
17263
|
+
}
|
|
17264
|
+
return left2;
|
|
17265
|
+
};
|
|
17266
|
+
signal("SIGTERM", pids);
|
|
17267
|
+
let left = await waitGone();
|
|
17268
|
+
if (left.length > 0) {
|
|
17269
|
+
signal("SIGKILL", left);
|
|
17270
|
+
left = await waitGone();
|
|
17271
|
+
}
|
|
17272
|
+
if (left.length > 0) throw new Error(`network helpers still running: ${left.join(", ")}`);
|
|
17273
|
+
return pids.length;
|
|
17274
|
+
}
|
|
17275
|
+
function realResetHelpers(networks) {
|
|
17276
|
+
return resetNetworkHelpers(networks, {
|
|
17277
|
+
ps: async () => (await execa("ps", ["-axo", "pid=,command="])).stdout,
|
|
17278
|
+
kill: (pid, sig) => process.kill(pid, sig),
|
|
17279
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
17280
|
+
});
|
|
17281
|
+
}
|
|
17282
|
+
async function pinBridges(slots, deps) {
|
|
17283
|
+
const killed = await deps.resetHelpers(helperNetworks());
|
|
17284
|
+
deps.log(` sandbox: restarted ${killed} network helper(s); anchoring ${slots.length} slot bridge(s)`);
|
|
17285
|
+
for (const s of slots) {
|
|
17286
|
+
if (!await deps.register(anchorBoxName(s))) throw new Error("shutdown began while anchoring");
|
|
17287
|
+
await deps.startAnchor(s);
|
|
17288
|
+
}
|
|
17289
|
+
}
|
|
17290
|
+
var ANCHOR_REGISTRY_ID, HELPER_RE;
|
|
17291
|
+
var init_bridges = __esm({
|
|
17292
|
+
"src/daemon/sandbox/bridges.ts"() {
|
|
17293
|
+
"use strict";
|
|
17294
|
+
init_execa();
|
|
17295
|
+
init_appleContainer();
|
|
17296
|
+
init_types();
|
|
17297
|
+
ANCHOR_REGISTRY_ID = "archme-anchors";
|
|
17298
|
+
HELPER_RE = /^\s*(\d+)\s+\S*\/container-network-vmnet start --id (\S+)(?:\s|$)/;
|
|
17299
|
+
}
|
|
17300
|
+
});
|
|
17301
|
+
|
|
17170
17302
|
// src/scan/sessionAuth.ts
|
|
17171
17303
|
var SessionAuth;
|
|
17172
17304
|
var init_sessionAuth = __esm({
|
|
@@ -19942,6 +20074,13 @@ function sandboxBootFailurePolicy(mode) {
|
|
|
19942
20074
|
function shutdownKillTargets(boxedJobs2, registry) {
|
|
19943
20075
|
return [...boxedJobs2].filter((id) => registry.hasLiveBoxes(id));
|
|
19944
20076
|
}
|
|
20077
|
+
function withBridgeCheck(probe, slots, bridgeFailures) {
|
|
20078
|
+
return async (slot) => {
|
|
20079
|
+
const report = await probe(slot);
|
|
20080
|
+
const failures = bridgeFailures(slots);
|
|
20081
|
+
return failures.length ? { ...report, ok: false, failures: [...report.failures, ...failures] } : report;
|
|
20082
|
+
};
|
|
20083
|
+
}
|
|
19945
20084
|
async function bootSandbox(settings, parts, deps) {
|
|
19946
20085
|
const sweep = await parts.runtime.sweep();
|
|
19947
20086
|
const inputDirs = await (deps.sweepInputDirs ?? sweepInputDirs)();
|
|
@@ -19977,13 +20116,31 @@ async function bootSandbox(settings, parts, deps) {
|
|
|
19977
20116
|
});
|
|
19978
20117
|
throw new SandboxBootError(`sandbox preflight failed: ${failedChecks(preflight)}`);
|
|
19979
20118
|
}
|
|
19980
|
-
const
|
|
20119
|
+
const pin = deps.pinBridges ?? ((s) => pinBridges(s, {
|
|
20120
|
+
resetHelpers: realResetHelpers,
|
|
20121
|
+
startAnchor: (slot) => parts.runtime.startAnchor(slot, settings.image),
|
|
20122
|
+
register: (box) => parts.registry.beginStep(ANCHOR_REGISTRY_ID, box),
|
|
20123
|
+
log: deps.log
|
|
20124
|
+
}));
|
|
20125
|
+
let pinError = null;
|
|
20126
|
+
try {
|
|
20127
|
+
await pin(slots);
|
|
20128
|
+
} catch (e) {
|
|
20129
|
+
pinError = e.message;
|
|
20130
|
+
deps.log(` sandbox: could not pin the slot bridges: ${pinError}`);
|
|
20131
|
+
}
|
|
20132
|
+
const probe = withBridgeCheck(
|
|
20133
|
+
deps.probe ?? ((slot) => runConfinementProbe({ runtime: parts.runtime, image: settings.image, log: deps.log }, slot, primaryLanIpv4(networkInterfaces()))),
|
|
20134
|
+
slots,
|
|
20135
|
+
deps.bridgeFailures ?? ((s) => bridgeMapFailures(s, networkInterfaces()))
|
|
20136
|
+
);
|
|
19981
20137
|
let report;
|
|
19982
20138
|
try {
|
|
19983
20139
|
report = await probe(slots[0]);
|
|
19984
20140
|
} catch (e) {
|
|
19985
20141
|
report = { ok: false, slot: 0, failures: [`probe threw: ${e.message}`], outcomes: {}, ms: 0 };
|
|
19986
20142
|
}
|
|
20143
|
+
if (pinError) report = { ...report, ok: false, failures: [...report.failures, `bridge pinning: ${pinError}`] };
|
|
19987
20144
|
preflight = {
|
|
19988
20145
|
ok: preflight.ok && report.ok,
|
|
19989
20146
|
checks: [...preflight.checks, { name: "confinement_probe", ok: report.ok, detail: report.ok ? void 0 : report.failures.join("; ") }]
|
|
@@ -20013,6 +20170,7 @@ var init_host = __esm({
|
|
|
20013
20170
|
"use strict";
|
|
20014
20171
|
init_confinement();
|
|
20015
20172
|
init_appleContainer();
|
|
20173
|
+
init_bridges();
|
|
20016
20174
|
init_claudeToken();
|
|
20017
20175
|
init_inputDir();
|
|
20018
20176
|
init_types();
|
|
@@ -20121,9 +20279,13 @@ async function runSandboxedJob(deps, job, opts) {
|
|
|
20121
20279
|
if (spec2.args[0] === "agent") agentStarted = true;
|
|
20122
20280
|
const handle = await deps.runtime.runStep(spec2);
|
|
20123
20281
|
const timer = setTimeout(() => kill("timeout"), timeoutMs);
|
|
20282
|
+
let gatewaySeen = false;
|
|
20124
20283
|
const poll = setInterval(() => {
|
|
20125
20284
|
const c3 = currentKillCause();
|
|
20126
|
-
if (c3) kill(c3);
|
|
20285
|
+
if (c3) return kill(c3);
|
|
20286
|
+
if (!deps.gatewayPresent) return;
|
|
20287
|
+
if (deps.gatewayPresent(slot)) gatewaySeen = true;
|
|
20288
|
+
else if (gatewaySeen) kill("network_lost");
|
|
20127
20289
|
}, pollMs);
|
|
20128
20290
|
try {
|
|
20129
20291
|
const exit = await handle.done;
|
|
@@ -22480,7 +22642,7 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
22480
22642
|
import { mkdtemp as mkdtemp4, mkdir as mkdir16, writeFile as writeFile15, rm as rm8, stat as stat8, chmod as chmod6 } from "node:fs/promises";
|
|
22481
22643
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
22482
22644
|
import { join as join25 } from "node:path";
|
|
22483
|
-
import { homedir as homedir8 } from "node:os";
|
|
22645
|
+
import { homedir as homedir8, networkInterfaces as networkInterfaces2 } from "node:os";
|
|
22484
22646
|
function attributeKillCause(result, recorded) {
|
|
22485
22647
|
if (recorded) return recorded;
|
|
22486
22648
|
if (result.signal === "SIGTERM") return "timeout";
|
|
@@ -23376,7 +23538,8 @@ function sandboxDepsFor(ctx, job, deviceId, host) {
|
|
|
23376
23538
|
log: (msg) => console.log(`[${job.id}] ${msg}`),
|
|
23377
23539
|
isCancelled: (id) => cancelled.has(id),
|
|
23378
23540
|
killReason: (id) => killReasons.get(id),
|
|
23379
|
-
isLeaseLost: (id) => leaseLost.has(id)
|
|
23541
|
+
isLeaseLost: (id) => leaseLost.has(id),
|
|
23542
|
+
gatewayPresent: (slot) => gatewayPresent(slot, networkInterfaces2())
|
|
23380
23543
|
};
|
|
23381
23544
|
}
|
|
23382
23545
|
async function completeClaudeRun(ctx, job, deviceId, run) {
|
|
@@ -24552,6 +24715,7 @@ var init_run = __esm({
|
|
|
24552
24715
|
init_supervision();
|
|
24553
24716
|
init_paths();
|
|
24554
24717
|
init_semaphore();
|
|
24718
|
+
init_bridges();
|
|
24555
24719
|
init_bearer();
|
|
24556
24720
|
init_credentials();
|
|
24557
24721
|
init_sessionAuth();
|
|
@@ -25022,7 +25186,7 @@ __export(setupIo_exports, {
|
|
|
25022
25186
|
createSetupIo: () => createSetupIo
|
|
25023
25187
|
});
|
|
25024
25188
|
import { mkdtemp as mkdtemp5, readFile as readFile16, writeFile as writeFile16 } from "node:fs/promises";
|
|
25025
|
-
import { networkInterfaces as
|
|
25189
|
+
import { networkInterfaces as networkInterfaces3, tmpdir as tmpdir5 } from "node:os";
|
|
25026
25190
|
import { join as join26 } from "node:path";
|
|
25027
25191
|
import { createInterface as createInterface2 } from "node:readline";
|
|
25028
25192
|
import { Writable as Writable4 } from "node:stream";
|
|
@@ -25109,7 +25273,7 @@ function createSetupIo(env = process.env) {
|
|
|
25109
25273
|
console.log(" (the daemon's gatekeepers are answering)");
|
|
25110
25274
|
}
|
|
25111
25275
|
try {
|
|
25112
|
-
return await runConfinementProbe({ runtime, image }, slot, primaryLanIpv4(
|
|
25276
|
+
return await runConfinementProbe({ runtime, image }, slot, primaryLanIpv4(networkInterfaces3()));
|
|
25113
25277
|
} finally {
|
|
25114
25278
|
if (own) await gates.stop();
|
|
25115
25279
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archme/cli",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Map your codebase into a living architecture diagram: run `archme init` in a repo to scan it, `archme daemon start` to keep it current on every commit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
package/sandbox-image.json
CHANGED