mindwire 0.1.2 → 0.1.5
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/client.d.ts +1 -1
- package/dist/index.cjs +46 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -32
- package/dist/index.js.map +1 -1
- package/dist/target/container.d.ts +2 -0
- package/dist/target/host.d.ts +12 -4
- package/dist/target/oblien.d.ts +7 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -466,7 +466,7 @@ var Run = class _Run {
|
|
|
466
466
|
};
|
|
467
467
|
|
|
468
468
|
// src/version.ts
|
|
469
|
-
var SDK_VERSION = "0.1.
|
|
469
|
+
var SDK_VERSION = "0.1.5" ;
|
|
470
470
|
|
|
471
471
|
// src/daemon-binary.ts
|
|
472
472
|
function supported(platform, arch) {
|
|
@@ -577,17 +577,19 @@ async function spawnDaemon(opts) {
|
|
|
577
577
|
const { spawn } = await import('child_process');
|
|
578
578
|
const net = await import('net');
|
|
579
579
|
const proc = serverRuntime().process;
|
|
580
|
+
const { randomBytes } = await import('crypto');
|
|
580
581
|
const resolved = await resolveBinary(opts.bin);
|
|
581
582
|
const bin = resolved.bin;
|
|
582
583
|
const port = await freePort(net);
|
|
583
584
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
585
|
+
const token = randomBytes(32).toString("hex");
|
|
584
586
|
const child = spawn(bin, [], {
|
|
585
587
|
env: {
|
|
586
588
|
...proc.env,
|
|
587
589
|
ADDR: `127.0.0.1:${port}`,
|
|
588
590
|
AGENT_CWD: opts.cwd ?? proc.cwd(),
|
|
589
591
|
STATE_PATH: opts.statePath ?? ".mindwire-state.json",
|
|
590
|
-
DAEMON_TOKEN:
|
|
592
|
+
DAEMON_TOKEN: token
|
|
591
593
|
},
|
|
592
594
|
stdio: "ignore"
|
|
593
595
|
});
|
|
@@ -606,8 +608,8 @@ async function spawnDaemon(opts) {
|
|
|
606
608
|
stop();
|
|
607
609
|
proc.exit?.(130);
|
|
608
610
|
});
|
|
609
|
-
await waitHealthy(baseUrl, () => spawnError, resolved);
|
|
610
|
-
return { baseUrl, stop };
|
|
611
|
+
await waitHealthy(baseUrl, token, () => spawnError, resolved);
|
|
612
|
+
return { baseUrl, token, stop };
|
|
611
613
|
}
|
|
612
614
|
async function resolveBinary(explicit) {
|
|
613
615
|
const fs = await import('fs');
|
|
@@ -659,7 +661,7 @@ function freePort(net) {
|
|
|
659
661
|
});
|
|
660
662
|
});
|
|
661
663
|
}
|
|
662
|
-
async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
|
|
664
|
+
async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
|
|
663
665
|
const deadline = Date.now() + timeoutMs;
|
|
664
666
|
while (Date.now() < deadline) {
|
|
665
667
|
const err = getError();
|
|
@@ -667,7 +669,7 @@ async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
|
|
|
667
669
|
throw new MindwireError(daemonStartError(resolved, err), { cause: err });
|
|
668
670
|
}
|
|
669
671
|
try {
|
|
670
|
-
const res = await fetch(`${baseUrl}/healthz`);
|
|
672
|
+
const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
|
|
671
673
|
if (res.ok) return;
|
|
672
674
|
} catch {
|
|
673
675
|
}
|
|
@@ -802,7 +804,7 @@ var Mindwire = class _Mindwire {
|
|
|
802
804
|
if (h) await h.stop();
|
|
803
805
|
}
|
|
804
806
|
// ---- health & catalog ----------------------------------------------------
|
|
805
|
-
/** `GET /healthz` —
|
|
807
|
+
/** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
|
|
806
808
|
health() {
|
|
807
809
|
return this.http.request("GET", "/healthz");
|
|
808
810
|
}
|
|
@@ -1304,11 +1306,12 @@ var STATE = `${DAEMON_DIR}/agent-state.json`;
|
|
|
1304
1306
|
var LOG = `${DAEMON_DIR}/daemon.log`;
|
|
1305
1307
|
async function ensureDaemon(host, cfg) {
|
|
1306
1308
|
const emit2 = makeEmit(cfg);
|
|
1309
|
+
const token = cfg.token ?? (await import('crypto')).randomBytes(32).toString("hex");
|
|
1307
1310
|
try {
|
|
1308
1311
|
await waitHostReady(host);
|
|
1309
1312
|
emit2({ phase: "connect", message: "runtime ready" });
|
|
1310
1313
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1311
|
-
const health = await probeHealth(host, cfg.port);
|
|
1314
|
+
const health = await probeHealth(host, cfg.port, token);
|
|
1312
1315
|
if (health.reachable) {
|
|
1313
1316
|
emit2({
|
|
1314
1317
|
phase: "probe",
|
|
@@ -1316,31 +1319,32 @@ async function ensureDaemon(host, cfg) {
|
|
|
1316
1319
|
version: health.version
|
|
1317
1320
|
});
|
|
1318
1321
|
const upToDate = health.version !== void 0 && health.version === desired;
|
|
1319
|
-
if (upToDate || !cfg.autoUpdate) {
|
|
1322
|
+
if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
|
|
1320
1323
|
emit2({
|
|
1321
1324
|
phase: "skip",
|
|
1322
1325
|
message: upToDate ? `daemon already at v${desired}` : "keeping the running daemon",
|
|
1323
1326
|
version: health.version
|
|
1324
1327
|
});
|
|
1325
|
-
return;
|
|
1328
|
+
return token;
|
|
1326
1329
|
}
|
|
1327
1330
|
} else {
|
|
1328
1331
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1329
1332
|
}
|
|
1330
|
-
await deploy(host, cfg, emit2);
|
|
1333
|
+
await deploy(host, cfg, emit2, token);
|
|
1334
|
+
return token;
|
|
1331
1335
|
} catch (err) {
|
|
1332
1336
|
emit2({ phase: "error", message: "ensure failed", error: err instanceof Error ? err.message : String(err) });
|
|
1333
1337
|
throw err;
|
|
1334
1338
|
}
|
|
1335
1339
|
}
|
|
1336
|
-
async function deploy(host, cfg, emit2) {
|
|
1340
|
+
async function deploy(host, cfg, emit2, token) {
|
|
1337
1341
|
const arch = await probeArch(host);
|
|
1338
1342
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1339
1343
|
let acquire = "";
|
|
1340
1344
|
if (cfg.daemonBin) {
|
|
1341
1345
|
const binPath = await resolveLinuxDaemon(cfg.daemonBin, arch);
|
|
1342
1346
|
const bytes = await readBytes(binPath);
|
|
1343
|
-
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${bytes.length}
|
|
1347
|
+
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${formatMiB(bytes.length)})`, arch, bytes: bytes.length });
|
|
1344
1348
|
await host.putFile(BIN_NEW, bytes, { mode: "0755" });
|
|
1345
1349
|
} else {
|
|
1346
1350
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(desired)) {
|
|
@@ -1383,9 +1387,9 @@ async function deploy(host, cfg, emit2) {
|
|
|
1383
1387
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
1384
1388
|
`chmod +x ${BIN}`,
|
|
1385
1389
|
// Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
|
|
1386
|
-
`setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN
|
|
1390
|
+
`setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN=${shellQuote(token)} ${BIN} > ${LOG} 2>&1 < /dev/null &`,
|
|
1387
1391
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1388
|
-
`for i in $(seq 1 60); do curl -fsS --max-time 2 http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
|
|
1392
|
+
`for i in $(seq 1 60); do curl -fsS --max-time 2 -H ${shellQuote(`Authorization: Bearer ${token}`)} http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
|
|
1389
1393
|
"echo MINDWIRE_FAIL",
|
|
1390
1394
|
`tail -n 40 ${LOG} 2>/dev/null || true`
|
|
1391
1395
|
].join("\n");
|
|
@@ -1399,8 +1403,8 @@ async function deploy(host, cfg, emit2) {
|
|
|
1399
1403
|
}
|
|
1400
1404
|
emit2({ phase: "ready", message: "daemon deployed and healthy" });
|
|
1401
1405
|
}
|
|
1402
|
-
async function probeHealth(host, port) {
|
|
1403
|
-
const script = `out=$(curl -fsS --max-time 3 http://127.0.0.1:${port}/healthz 2>/dev/null) && printf '<<MW_H>>%s<<MW_H>>' "$out" || printf '<<MW_H>><<MW_H>>'`;
|
|
1406
|
+
async function probeHealth(host, port, token) {
|
|
1407
|
+
const script = `out=$(curl -fsS --max-time 3 -H ${shellQuote(`Authorization: Bearer ${token}`)} http://127.0.0.1:${port}/healthz 2>/dev/null) && printf '<<MW_H>>%s<<MW_H>>' "$out" || printf '<<MW_H>><<MW_H>>'`;
|
|
1404
1408
|
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 15 });
|
|
1405
1409
|
const body = ((res.stdout ?? "").match(/<<MW_H>>([\s\S]*?)<<MW_H>>/)?.[1] ?? "").trim();
|
|
1406
1410
|
if (!body) return { reachable: false };
|
|
@@ -1436,10 +1440,11 @@ async function waitHostReady(host, timeoutMs = 6e4) {
|
|
|
1436
1440
|
async function resolveLinuxDaemon(explicit, arch) {
|
|
1437
1441
|
const fs = await import('fs');
|
|
1438
1442
|
if (explicit) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1443
|
+
const resolved = explicit.replaceAll("{arch}", arch);
|
|
1444
|
+
if (!fs.existsSync(resolved)) {
|
|
1445
|
+
throw new MindwireError(`mindwire: sandbox daemonBin not found at ${resolved}`);
|
|
1441
1446
|
}
|
|
1442
|
-
return
|
|
1447
|
+
return resolved;
|
|
1443
1448
|
}
|
|
1444
1449
|
return ensureDaemonBinary({ platform: "linux", arch: arch === "arm64" ? "arm64" : "x64" });
|
|
1445
1450
|
}
|
|
@@ -1453,6 +1458,9 @@ function sleep(ms) {
|
|
|
1453
1458
|
function shellQuote(value) {
|
|
1454
1459
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1455
1460
|
}
|
|
1461
|
+
function formatMiB(bytes) {
|
|
1462
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1463
|
+
}
|
|
1456
1464
|
|
|
1457
1465
|
// src/target/container.ts
|
|
1458
1466
|
async function provisionContainer(host, cfg, onLog) {
|
|
@@ -1466,7 +1474,7 @@ async function provisionContainer(host, cfg, onLog) {
|
|
|
1466
1474
|
};
|
|
1467
1475
|
await ensureDockerReady(host, cfg.install ?? "never", emit2);
|
|
1468
1476
|
const { containerId, hostPort, created } = await runContainer(host, cfg, emit2);
|
|
1469
|
-
await ensureDaemon(new ContainerHost(host, containerId), {
|
|
1477
|
+
const token = await ensureDaemon(new ContainerHost(host, containerId), {
|
|
1470
1478
|
port: cfg.daemonPort,
|
|
1471
1479
|
agent: cfg.agent,
|
|
1472
1480
|
agentCwd: cfg.agentCwd,
|
|
@@ -1485,7 +1493,7 @@ async function provisionContainer(host, cfg, onLog) {
|
|
|
1485
1493
|
} catch {
|
|
1486
1494
|
}
|
|
1487
1495
|
};
|
|
1488
|
-
return { containerId, hostPort, stop };
|
|
1496
|
+
return { containerId, hostPort, token, stop };
|
|
1489
1497
|
}
|
|
1490
1498
|
var ContainerHost = class {
|
|
1491
1499
|
constructor(base, containerId) {
|
|
@@ -1721,7 +1729,7 @@ async function connectSsh(opts, spec) {
|
|
|
1721
1729
|
});
|
|
1722
1730
|
}
|
|
1723
1731
|
async function provisionSsh(client, makeTunnel, cfg) {
|
|
1724
|
-
await ensureDaemon(new SshHost(client), {
|
|
1732
|
+
const token = await ensureDaemon(new SshHost(client), {
|
|
1725
1733
|
port: cfg.daemonPort,
|
|
1726
1734
|
agent: cfg.agent,
|
|
1727
1735
|
agentCwd: cfg.agentCwd,
|
|
@@ -1744,7 +1752,7 @@ async function provisionSsh(client, makeTunnel, cfg) {
|
|
|
1744
1752
|
}
|
|
1745
1753
|
}
|
|
1746
1754
|
};
|
|
1747
|
-
return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
|
|
1755
|
+
return { id: cfg.id, baseUrl: tunnel.baseUrl, token, stop };
|
|
1748
1756
|
}
|
|
1749
1757
|
async function provisionSshContainer(client, makeTunnel, cfg) {
|
|
1750
1758
|
const container = await provisionContainer(
|
|
@@ -1781,7 +1789,7 @@ async function provisionSshContainer(client, makeTunnel, cfg) {
|
|
|
1781
1789
|
}
|
|
1782
1790
|
}
|
|
1783
1791
|
};
|
|
1784
|
-
return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
|
|
1792
|
+
return { id: cfg.id, baseUrl: tunnel.baseUrl, token: container.token, stop };
|
|
1785
1793
|
}
|
|
1786
1794
|
async function openConnection(mod, opts) {
|
|
1787
1795
|
const auth = await buildAuth(opts);
|
|
@@ -1993,7 +2001,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
1993
2001
|
`mindwire: the Docker container does not publish a host port for ${port}/tcp. ` + (created ? "This is unexpected for a container mindwire created." : "Attach to a container started with that port published (e.g. `-p 0:" + port + "`).")
|
|
1994
2002
|
);
|
|
1995
2003
|
}
|
|
1996
|
-
await ensureDaemon(new DockerHost(container), {
|
|
2004
|
+
const token = await ensureDaemon(new DockerHost(container), {
|
|
1997
2005
|
port,
|
|
1998
2006
|
agent,
|
|
1999
2007
|
agentCwd,
|
|
@@ -2012,7 +2020,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
2012
2020
|
} catch {
|
|
2013
2021
|
}
|
|
2014
2022
|
};
|
|
2015
|
-
return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, stop };
|
|
2023
|
+
return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, token, stop };
|
|
2016
2024
|
}
|
|
2017
2025
|
async function ensureImage(docker2, image, policy, onLog) {
|
|
2018
2026
|
const emit2 = (message, error) => {
|
|
@@ -2149,26 +2157,32 @@ async function provisionOblien(client, config = {}, onLog) {
|
|
|
2149
2157
|
const ws = await client.workspaces.create(params);
|
|
2150
2158
|
workspaceId = ws.id;
|
|
2151
2159
|
}
|
|
2160
|
+
await config.onWorkspace?.(workspaceId);
|
|
2152
2161
|
const handle = client.workspace(workspaceId);
|
|
2153
2162
|
await handle.start().catch(() => {
|
|
2154
2163
|
});
|
|
2155
2164
|
let rt = await handle.runtime();
|
|
2156
|
-
await ensureDaemon(new OblienHost(rt), {
|
|
2165
|
+
const token = await ensureDaemon(new OblienHost(rt), {
|
|
2157
2166
|
port,
|
|
2158
2167
|
agent,
|
|
2159
2168
|
agentCwd,
|
|
2160
2169
|
daemonBin: config.daemonBin,
|
|
2161
2170
|
autoUpdate: config.autoUpdate,
|
|
2171
|
+
forceDeploy: config.forceDeploy,
|
|
2162
2172
|
target: "oblien",
|
|
2163
|
-
onLog
|
|
2173
|
+
onLog,
|
|
2174
|
+
token: config.daemonToken
|
|
2164
2175
|
});
|
|
2165
2176
|
const fetchImpl = async (url, init) => {
|
|
2166
2177
|
const { pathname, search } = new URL(url);
|
|
2167
2178
|
const path = pathname + search;
|
|
2168
|
-
|
|
2179
|
+
const headers = new Headers(init?.headers);
|
|
2180
|
+
headers.set("X-Mindwire-Token", token);
|
|
2181
|
+
headers.delete("Authorization");
|
|
2182
|
+
let res = await rt.proxy(port).fetch(path, { ...init, headers });
|
|
2169
2183
|
if (res.status === 401) {
|
|
2170
2184
|
rt = await handle.runtime({ force: true });
|
|
2171
|
-
res = await rt.proxy(port).fetch(path, init);
|
|
2185
|
+
res = await rt.proxy(port).fetch(path, { ...init, headers });
|
|
2172
2186
|
}
|
|
2173
2187
|
return res;
|
|
2174
2188
|
};
|
|
@@ -2179,7 +2193,7 @@ async function provisionOblien(client, config = {}, onLog) {
|
|
|
2179
2193
|
} catch {
|
|
2180
2194
|
}
|
|
2181
2195
|
};
|
|
2182
|
-
return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, fetch: fetchImpl, stop };
|
|
2196
|
+
return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, token, fetch: fetchImpl, stop };
|
|
2183
2197
|
}
|
|
2184
2198
|
async function importOblien() {
|
|
2185
2199
|
const spec = "oblien";
|