bitpull 0.1.10 → 0.1.13
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/bitpull.cjs +133 -8
- package/package.json +1 -1
package/bitpull.cjs
CHANGED
|
@@ -18444,6 +18444,55 @@ function sandboxArgs(sb, name, workdir, spec) {
|
|
|
18444
18444
|
...spec.command
|
|
18445
18445
|
];
|
|
18446
18446
|
}
|
|
18447
|
+
function dockerCreateArgs(sb, name, volume, spec) {
|
|
18448
|
+
const args = sandboxArgs(sb, name, "HOST", spec);
|
|
18449
|
+
const v = args.indexOf("-v");
|
|
18450
|
+
args.splice(v, 2, "-v", `${volume}:/work:rw`);
|
|
18451
|
+
args.splice(0, 2, "create");
|
|
18452
|
+
return args;
|
|
18453
|
+
}
|
|
18454
|
+
var usesVolumes = (sb) => sb.cli === "docker";
|
|
18455
|
+
function tarArchive(entries) {
|
|
18456
|
+
const blocks = [];
|
|
18457
|
+
const octal = (n, len) => n.toString(8).padStart(len - 1, "0") + "\0";
|
|
18458
|
+
for (const e of entries) {
|
|
18459
|
+
const isDir = e.data === void 0;
|
|
18460
|
+
const full = isDir && !e.path.endsWith("/") ? `${e.path}/` : e.path;
|
|
18461
|
+
let name = full;
|
|
18462
|
+
let prefix = "";
|
|
18463
|
+
if (Buffer.byteLength(full) > 100) {
|
|
18464
|
+
const cut = [...full.matchAll(/\//g)].map((m) => m.index).find((i) => i <= 155 && Buffer.byteLength(full.slice(i + 1)) <= 100);
|
|
18465
|
+
if (cut === void 0) throw new Error(`path too long: ${e.path}`);
|
|
18466
|
+
prefix = full.slice(0, cut);
|
|
18467
|
+
name = full.slice(cut + 1);
|
|
18468
|
+
}
|
|
18469
|
+
const h = Buffer.alloc(512);
|
|
18470
|
+
h.write(name, 0, 100);
|
|
18471
|
+
h.write(octal(isDir ? 511 : 438, 8), 100);
|
|
18472
|
+
h.write(octal(65534, 8), 108);
|
|
18473
|
+
h.write(octal(65534, 8), 116);
|
|
18474
|
+
h.write(octal(isDir ? 0 : e.data.length, 12), 124);
|
|
18475
|
+
h.write(octal(Math.floor(Date.now() / 1e3), 12), 136);
|
|
18476
|
+
h.write(" ", 148);
|
|
18477
|
+
h.write(isDir ? "5" : "0", 156);
|
|
18478
|
+
h.write("ustar\0", 257);
|
|
18479
|
+
h.write("00", 263);
|
|
18480
|
+
h.write("nobody", 265);
|
|
18481
|
+
h.write("nobody", 297);
|
|
18482
|
+
h.write(prefix, 345, 155);
|
|
18483
|
+
let sum = 0;
|
|
18484
|
+
for (const b of h) sum += b;
|
|
18485
|
+
h.write(octal(sum, 7) + " ", 148);
|
|
18486
|
+
blocks.push(h);
|
|
18487
|
+
if (!isDir && e.data.length) {
|
|
18488
|
+
blocks.push(e.data);
|
|
18489
|
+
const pad = (512 - e.data.length % 512) % 512;
|
|
18490
|
+
if (pad) blocks.push(Buffer.alloc(pad));
|
|
18491
|
+
}
|
|
18492
|
+
}
|
|
18493
|
+
blocks.push(Buffer.alloc(1024));
|
|
18494
|
+
return Buffer.concat(blocks);
|
|
18495
|
+
}
|
|
18447
18496
|
function killArgs(sb, name) {
|
|
18448
18497
|
return sb.runtime === "apple" ? ["kill", name] : ["kill", name];
|
|
18449
18498
|
}
|
|
@@ -18515,7 +18564,8 @@ async function ensureImage(sb, image, timeoutMs = 6e5) {
|
|
|
18515
18564
|
}
|
|
18516
18565
|
var cached2 = null;
|
|
18517
18566
|
async function selectSandbox(preference = "auto", probe = probeSandbox) {
|
|
18518
|
-
|
|
18567
|
+
const ttl = cached2?.sandbox ? 864e5 : 5 * 6e4;
|
|
18568
|
+
if (cached2 && cached2.preference === preference && Date.now() - cached2.at < ttl) return cached2;
|
|
18519
18569
|
const candidates = (await availableRuntimes()).filter((r) => preference === "auto" || r.runtime === preference);
|
|
18520
18570
|
const tried = [];
|
|
18521
18571
|
let chosen = null;
|
|
@@ -18751,9 +18801,82 @@ async function collectOutputs(dir) {
|
|
|
18751
18801
|
return out;
|
|
18752
18802
|
}
|
|
18753
18803
|
async function runContainer(spec, sandbox, signal) {
|
|
18804
|
+
return usesVolumes(sandbox) ? runDockerJob(spec, sandbox, signal) : runFolderJob(spec, sandbox, signal);
|
|
18805
|
+
}
|
|
18806
|
+
function inputFiles(spec) {
|
|
18807
|
+
const files = [];
|
|
18808
|
+
for (const [path, b64] of Object.entries(spec.inputs ?? {})) {
|
|
18809
|
+
const rel = normalizeRepoPath(path);
|
|
18810
|
+
if (!rel || rel.startsWith("out/")) return null;
|
|
18811
|
+
files.push({ path: rel, data: Buffer.from(b64, "base64") });
|
|
18812
|
+
}
|
|
18813
|
+
return files;
|
|
18814
|
+
}
|
|
18815
|
+
function runWithInput(cmd, args, input) {
|
|
18816
|
+
return new Promise((resolve, reject) => {
|
|
18817
|
+
const child = (0, import_node_child_process5.spawn)(cmd, args, { stdio: ["pipe", "ignore", "pipe"], windowsHide: true });
|
|
18818
|
+
let stderr = "";
|
|
18819
|
+
child.stderr.on("data", (c) => stderr += c.toString("utf8"));
|
|
18820
|
+
child.on("error", reject);
|
|
18821
|
+
child.on("close", (code) => resolve({ code: code ?? -1, stderr }));
|
|
18822
|
+
child.stdin.end(input);
|
|
18823
|
+
});
|
|
18824
|
+
}
|
|
18825
|
+
async function runDockerJob(spec, sandbox, signal) {
|
|
18826
|
+
const files = inputFiles(spec);
|
|
18827
|
+
if (!files) return { ok: false, usage: {}, error: "bad input path" };
|
|
18828
|
+
const name = `bitpull-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}`;
|
|
18829
|
+
const volume = `${name}-work`;
|
|
18830
|
+
const docker = (args, timeout = 12e4) => exec4("docker", args, { timeout, windowsHide: true });
|
|
18831
|
+
let outDir = null;
|
|
18832
|
+
try {
|
|
18833
|
+
for (const image of [spec.image, PROBE_IMAGE]) {
|
|
18834
|
+
const pulled = await ensureImage(sandbox, image);
|
|
18835
|
+
if (!pulled.ok) return { ok: false, usage: {}, error: `sandbox_error: ${pulled.error}` };
|
|
18836
|
+
}
|
|
18837
|
+
await docker(["volume", "create", volume]);
|
|
18838
|
+
await docker(dockerCreateArgs(sandbox, name, volume, spec));
|
|
18839
|
+
const tar = tarArchive([{ path: "." }, { path: "out" }, ...[...new Set(files.flatMap((f) => f.path.split("/").slice(0, -1).map((_, i, a) => a.slice(0, i + 1).join("/"))))].map((d) => ({ path: d })), ...files]);
|
|
18840
|
+
const copied = await runWithInput("docker", ["cp", "-a", "-", `${name}:/work`], tar);
|
|
18841
|
+
if (copied.code !== 0) return { ok: false, usage: {}, error: `sandbox_error: ${copied.stderr.trim().slice(0, 300)}` };
|
|
18842
|
+
await docker(["run", "--rm", "--network", "none", "--cap-drop", "ALL", "--cap-add", "CHOWN", "--security-opt", "no-new-privileges", "-v", `${volume}:/work`, PROBE_IMAGE, "chown", "65534:65534", "/work"]);
|
|
18843
|
+
const started = Date.now();
|
|
18844
|
+
const stdout = capped();
|
|
18845
|
+
const stderr = capped();
|
|
18846
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
18847
|
+
const child = (0, import_node_child_process5.spawn)("docker", ["start", "-a", name], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
18848
|
+
child.stdout.on("data", (c) => stdout.push(c));
|
|
18849
|
+
child.stderr.on("data", (c) => stderr.push(c));
|
|
18850
|
+
const kill = () => (0, import_node_child_process5.spawn)("docker", ["kill", name], { stdio: "ignore", windowsHide: true });
|
|
18851
|
+
const timer = setTimeout(kill, spec.timeoutSec * 1e3);
|
|
18852
|
+
signal.addEventListener("abort", kill, { once: true });
|
|
18853
|
+
child.on("error", reject);
|
|
18854
|
+
child.on("close", (code) => {
|
|
18855
|
+
clearTimeout(timer);
|
|
18856
|
+
resolve(code ?? -1);
|
|
18857
|
+
});
|
|
18858
|
+
});
|
|
18859
|
+
const seconds = (Date.now() - started) / 1e3;
|
|
18860
|
+
if (signal.aborted) return { ok: false, usage: { seconds }, error: "stopped_by_owner" };
|
|
18861
|
+
outDir = await (0, import_promises12.mkdtemp)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "bitpull-out-"));
|
|
18862
|
+
await docker(["cp", `${name}:/work/out/.`, outDir]).catch(() => void 0);
|
|
18863
|
+
return {
|
|
18864
|
+
ok: true,
|
|
18865
|
+
result: { exitCode, stdout: stdout.text(), stderr: stderr.text(), outputs: await collectOutputs(outDir) },
|
|
18866
|
+
usage: { seconds }
|
|
18867
|
+
};
|
|
18868
|
+
} catch (err) {
|
|
18869
|
+
return { ok: false, usage: {}, error: `sandbox_error: ${String(err.stderr || err.message).trim().slice(0, 300)}` };
|
|
18870
|
+
} finally {
|
|
18871
|
+
await docker(["rm", "-f", name], 6e4).catch(() => void 0);
|
|
18872
|
+
await docker(["volume", "rm", "-f", volume], 6e4).catch(() => void 0);
|
|
18873
|
+
if (outDir) await (0, import_promises12.rm)(outDir, { recursive: true, force: true });
|
|
18874
|
+
}
|
|
18875
|
+
}
|
|
18876
|
+
async function runFolderJob(spec, sandbox, signal) {
|
|
18754
18877
|
const workdir = await (0, import_promises12.mkdtemp)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "bitpull-job-"));
|
|
18755
18878
|
const name = `bitpull-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}`;
|
|
18756
|
-
|
|
18879
|
+
let started = Date.now();
|
|
18757
18880
|
try {
|
|
18758
18881
|
for (const [path, b64] of Object.entries(spec.inputs ?? {})) {
|
|
18759
18882
|
const rel = normalizeRepoPath(path);
|
|
@@ -18765,7 +18888,8 @@ async function runContainer(spec, sandbox, signal) {
|
|
|
18765
18888
|
await (0, import_promises12.chmod)(workdir, 511);
|
|
18766
18889
|
await (0, import_promises12.chmod)((0, import_node_path10.join)(workdir, "out"), 511);
|
|
18767
18890
|
const pulled = await ensureImage(sandbox, spec.image);
|
|
18768
|
-
if (!pulled.ok) return { ok: false, usage: {
|
|
18891
|
+
if (!pulled.ok) return { ok: false, usage: {}, error: `sandbox_error: ${pulled.error}` };
|
|
18892
|
+
started = Date.now();
|
|
18769
18893
|
const stdout = capped();
|
|
18770
18894
|
const stderr = capped();
|
|
18771
18895
|
const exitCode = await new Promise((resolve, reject) => {
|
|
@@ -19131,7 +19255,7 @@ async function doctor(cfg) {
|
|
|
19131
19255
|
}
|
|
19132
19256
|
|
|
19133
19257
|
// apps/agent/src/control.ts
|
|
19134
|
-
var APP_VERSION = true ? "0.1.
|
|
19258
|
+
var APP_VERSION = true ? "0.1.13" : "dev";
|
|
19135
19259
|
var UPDATE_MANIFEST = "https://github.com/SamuealPK/bitpull-releases/releases/latest/download/latest.json";
|
|
19136
19260
|
var inDesktopApp = () => /bitpull-agent(\.exe)?$/i.test(process.execPath);
|
|
19137
19261
|
function newerVersion(a, b) {
|
|
@@ -19273,7 +19397,8 @@ function startControlServer(ctx, node, port = ctx.config.controlPort) {
|
|
|
19273
19397
|
const home = ctx.home ?? bitpullHome();
|
|
19274
19398
|
const status = await (0, import_promises15.readFile)((0, import_node_path12.join)(home, "update-status.json"), "utf8").then((x) => JSON.parse(x)).catch(() => null);
|
|
19275
19399
|
const latest = await fetch(UPDATE_MANIFEST, { signal: AbortSignal.timeout(8e3) }).then((r) => r.ok ? r.json() : null).then((m) => m?.version ?? null).catch(() => null);
|
|
19276
|
-
|
|
19400
|
+
const shown = latest && APP_VERSION !== "dev" && newerVersion(APP_VERSION, latest) ? APP_VERSION : latest;
|
|
19401
|
+
return { current: APP_VERSION, latest: shown, available: !!latest && APP_VERSION !== "dev" && newerVersion(latest, APP_VERSION), inApp: inDesktopApp(), status };
|
|
19277
19402
|
},
|
|
19278
19403
|
/** Asks the desktop app to check for an update and install it now. */
|
|
19279
19404
|
"POST /api/update": async () => {
|
|
@@ -30460,7 +30585,7 @@ async function startNode(ctx, home, opts = {}) {
|
|
|
30460
30585
|
busy = false;
|
|
30461
30586
|
}
|
|
30462
30587
|
};
|
|
30463
|
-
|
|
30588
|
+
void tick();
|
|
30464
30589
|
const timer = setInterval(tick, opts.heartbeatMs ?? 3e4);
|
|
30465
30590
|
const worker = startWorker(ctx, { pollMs: opts.workerPollMs, sandbox });
|
|
30466
30591
|
status.worker = worker.status;
|
|
@@ -30550,7 +30675,7 @@ function openBrowser(url) {
|
|
|
30550
30675
|
const args = (0, import_node_os10.platform)() === "win32" ? ["/c", "start", "", url] : [url];
|
|
30551
30676
|
(0, import_node_child_process7.spawn)(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
30552
30677
|
}
|
|
30553
|
-
var agentVersion = () => true ? "0.1.
|
|
30678
|
+
var agentVersion = () => true ? "0.1.13" : "dev";
|
|
30554
30679
|
var dashboardUrl = (cfg) => `http://127.0.0.1:${cfg.controlPort}/#token=${cfg.controlToken}`;
|
|
30555
30680
|
var AppNotRunning = class extends Error {
|
|
30556
30681
|
};
|
|
@@ -30881,7 +31006,7 @@ ${DRAFT_INSTRUCTIONS.replace(/call publish_repo.*/, 'run "bitpull publish <folde
|
|
|
30881
31006
|
const control2 = await startControlServer(ctx, node);
|
|
30882
31007
|
await (0, import_promises21.writeFile)((0, import_node_path18.join)(bitpullHome(), "agent.json"), JSON.stringify({ pid: process.pid, version: agentVersion() }));
|
|
30883
31008
|
console.log(`BitPull is running. Dashboard: ${dashboardUrl(cfg)}`);
|
|
30884
|
-
console.log(
|
|
31009
|
+
console.log('Connecting to the network in the background (see the dashboard or "bitpull status").');
|
|
30885
31010
|
const stop = async () => {
|
|
30886
31011
|
control2.close();
|
|
30887
31012
|
await node.stop();
|
package/package.json
CHANGED