mindwire 0.1.2 → 0.1.3
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 +44 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +44 -31
- 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 +5 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -102,7 +102,7 @@ export declare class Mindwire {
|
|
|
102
102
|
* call before the target has connected (nothing to reap yet).
|
|
103
103
|
*/
|
|
104
104
|
close(): Promise<void>;
|
|
105
|
-
/** `GET /healthz` —
|
|
105
|
+
/** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
|
|
106
106
|
health(): Promise<Health>;
|
|
107
107
|
/**
|
|
108
108
|
* `GET /stats` — the daemon **process's** resource snapshot (heap in use, memory reserved from the
|
package/dist/index.cjs
CHANGED
|
@@ -469,7 +469,7 @@ var Run = class _Run {
|
|
|
469
469
|
};
|
|
470
470
|
|
|
471
471
|
// src/version.ts
|
|
472
|
-
var SDK_VERSION = "0.1.
|
|
472
|
+
var SDK_VERSION = "0.1.3" ;
|
|
473
473
|
|
|
474
474
|
// src/daemon-binary.ts
|
|
475
475
|
function supported(platform, arch) {
|
|
@@ -580,17 +580,19 @@ async function spawnDaemon(opts) {
|
|
|
580
580
|
const { spawn } = await import('child_process');
|
|
581
581
|
const net = await import('net');
|
|
582
582
|
const proc = serverRuntime().process;
|
|
583
|
+
const { randomBytes } = await import('crypto');
|
|
583
584
|
const resolved = await resolveBinary(opts.bin);
|
|
584
585
|
const bin = resolved.bin;
|
|
585
586
|
const port = await freePort(net);
|
|
586
587
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
588
|
+
const token = randomBytes(32).toString("hex");
|
|
587
589
|
const child = spawn(bin, [], {
|
|
588
590
|
env: {
|
|
589
591
|
...proc.env,
|
|
590
592
|
ADDR: `127.0.0.1:${port}`,
|
|
591
593
|
AGENT_CWD: opts.cwd ?? proc.cwd(),
|
|
592
594
|
STATE_PATH: opts.statePath ?? ".mindwire-state.json",
|
|
593
|
-
DAEMON_TOKEN:
|
|
595
|
+
DAEMON_TOKEN: token
|
|
594
596
|
},
|
|
595
597
|
stdio: "ignore"
|
|
596
598
|
});
|
|
@@ -609,8 +611,8 @@ async function spawnDaemon(opts) {
|
|
|
609
611
|
stop();
|
|
610
612
|
proc.exit?.(130);
|
|
611
613
|
});
|
|
612
|
-
await waitHealthy(baseUrl, () => spawnError, resolved);
|
|
613
|
-
return { baseUrl, stop };
|
|
614
|
+
await waitHealthy(baseUrl, token, () => spawnError, resolved);
|
|
615
|
+
return { baseUrl, token, stop };
|
|
614
616
|
}
|
|
615
617
|
async function resolveBinary(explicit) {
|
|
616
618
|
const fs = await import('fs');
|
|
@@ -662,7 +664,7 @@ function freePort(net) {
|
|
|
662
664
|
});
|
|
663
665
|
});
|
|
664
666
|
}
|
|
665
|
-
async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
|
|
667
|
+
async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
|
|
666
668
|
const deadline = Date.now() + timeoutMs;
|
|
667
669
|
while (Date.now() < deadline) {
|
|
668
670
|
const err = getError();
|
|
@@ -670,7 +672,7 @@ async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
|
|
|
670
672
|
throw new MindwireError(daemonStartError(resolved, err), { cause: err });
|
|
671
673
|
}
|
|
672
674
|
try {
|
|
673
|
-
const res = await fetch(`${baseUrl}/healthz`);
|
|
675
|
+
const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
|
|
674
676
|
if (res.ok) return;
|
|
675
677
|
} catch {
|
|
676
678
|
}
|
|
@@ -805,7 +807,7 @@ var Mindwire = class _Mindwire {
|
|
|
805
807
|
if (h) await h.stop();
|
|
806
808
|
}
|
|
807
809
|
// ---- health & catalog ----------------------------------------------------
|
|
808
|
-
/** `GET /healthz` —
|
|
810
|
+
/** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
|
|
809
811
|
health() {
|
|
810
812
|
return this.http.request("GET", "/healthz");
|
|
811
813
|
}
|
|
@@ -1307,11 +1309,12 @@ var STATE = `${DAEMON_DIR}/agent-state.json`;
|
|
|
1307
1309
|
var LOG = `${DAEMON_DIR}/daemon.log`;
|
|
1308
1310
|
async function ensureDaemon(host, cfg) {
|
|
1309
1311
|
const emit2 = makeEmit(cfg);
|
|
1312
|
+
const token = cfg.token ?? (await import('crypto')).randomBytes(32).toString("hex");
|
|
1310
1313
|
try {
|
|
1311
1314
|
await waitHostReady(host);
|
|
1312
1315
|
emit2({ phase: "connect", message: "runtime ready" });
|
|
1313
1316
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1314
|
-
const health = await probeHealth(host, cfg.port);
|
|
1317
|
+
const health = await probeHealth(host, cfg.port, token);
|
|
1315
1318
|
if (health.reachable) {
|
|
1316
1319
|
emit2({
|
|
1317
1320
|
phase: "probe",
|
|
@@ -1319,31 +1322,32 @@ async function ensureDaemon(host, cfg) {
|
|
|
1319
1322
|
version: health.version
|
|
1320
1323
|
});
|
|
1321
1324
|
const upToDate = health.version !== void 0 && health.version === desired;
|
|
1322
|
-
if (upToDate || !cfg.autoUpdate) {
|
|
1325
|
+
if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
|
|
1323
1326
|
emit2({
|
|
1324
1327
|
phase: "skip",
|
|
1325
1328
|
message: upToDate ? `daemon already at v${desired}` : "keeping the running daemon",
|
|
1326
1329
|
version: health.version
|
|
1327
1330
|
});
|
|
1328
|
-
return;
|
|
1331
|
+
return token;
|
|
1329
1332
|
}
|
|
1330
1333
|
} else {
|
|
1331
1334
|
emit2({ phase: "probe", message: "no daemon reachable; deploying" });
|
|
1332
1335
|
}
|
|
1333
|
-
await deploy(host, cfg, emit2);
|
|
1336
|
+
await deploy(host, cfg, emit2, token);
|
|
1337
|
+
return token;
|
|
1334
1338
|
} catch (err) {
|
|
1335
1339
|
emit2({ phase: "error", message: "ensure failed", error: err instanceof Error ? err.message : String(err) });
|
|
1336
1340
|
throw err;
|
|
1337
1341
|
}
|
|
1338
1342
|
}
|
|
1339
|
-
async function deploy(host, cfg, emit2) {
|
|
1343
|
+
async function deploy(host, cfg, emit2, token) {
|
|
1340
1344
|
const arch = await probeArch(host);
|
|
1341
1345
|
const desired = cfg.desiredVersion ?? SDK_VERSION;
|
|
1342
1346
|
let acquire = "";
|
|
1343
1347
|
if (cfg.daemonBin) {
|
|
1344
1348
|
const binPath = await resolveLinuxDaemon(cfg.daemonBin, arch);
|
|
1345
1349
|
const bytes = await readBytes(binPath);
|
|
1346
|
-
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${bytes.length}
|
|
1350
|
+
emit2({ phase: "upload", message: `uploading daemon (${arch}, ${formatMiB(bytes.length)})`, arch, bytes: bytes.length });
|
|
1347
1351
|
await host.putFile(BIN_NEW, bytes, { mode: "0755" });
|
|
1348
1352
|
} else {
|
|
1349
1353
|
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(desired)) {
|
|
@@ -1386,9 +1390,9 @@ async function deploy(host, cfg, emit2) {
|
|
|
1386
1390
|
`mv -f ${BIN_NEW} ${BIN}`,
|
|
1387
1391
|
`chmod +x ${BIN}`,
|
|
1388
1392
|
// Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
|
|
1389
|
-
`setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN
|
|
1393
|
+
`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 &`,
|
|
1390
1394
|
// Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
|
|
1391
|
-
`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`,
|
|
1395
|
+
`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`,
|
|
1392
1396
|
"echo MINDWIRE_FAIL",
|
|
1393
1397
|
`tail -n 40 ${LOG} 2>/dev/null || true`
|
|
1394
1398
|
].join("\n");
|
|
@@ -1402,8 +1406,8 @@ async function deploy(host, cfg, emit2) {
|
|
|
1402
1406
|
}
|
|
1403
1407
|
emit2({ phase: "ready", message: "daemon deployed and healthy" });
|
|
1404
1408
|
}
|
|
1405
|
-
async function probeHealth(host, port) {
|
|
1406
|
-
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>>'`;
|
|
1409
|
+
async function probeHealth(host, port, token) {
|
|
1410
|
+
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>>'`;
|
|
1407
1411
|
const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 15 });
|
|
1408
1412
|
const body = ((res.stdout ?? "").match(/<<MW_H>>([\s\S]*?)<<MW_H>>/)?.[1] ?? "").trim();
|
|
1409
1413
|
if (!body) return { reachable: false };
|
|
@@ -1439,10 +1443,11 @@ async function waitHostReady(host, timeoutMs = 6e4) {
|
|
|
1439
1443
|
async function resolveLinuxDaemon(explicit, arch) {
|
|
1440
1444
|
const fs = await import('fs');
|
|
1441
1445
|
if (explicit) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1446
|
+
const resolved = explicit.replaceAll("{arch}", arch);
|
|
1447
|
+
if (!fs.existsSync(resolved)) {
|
|
1448
|
+
throw new MindwireError(`mindwire: sandbox daemonBin not found at ${resolved}`);
|
|
1444
1449
|
}
|
|
1445
|
-
return
|
|
1450
|
+
return resolved;
|
|
1446
1451
|
}
|
|
1447
1452
|
return ensureDaemonBinary({ platform: "linux", arch: arch === "arm64" ? "arm64" : "x64" });
|
|
1448
1453
|
}
|
|
@@ -1456,6 +1461,9 @@ function sleep(ms) {
|
|
|
1456
1461
|
function shellQuote(value) {
|
|
1457
1462
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1458
1463
|
}
|
|
1464
|
+
function formatMiB(bytes) {
|
|
1465
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1466
|
+
}
|
|
1459
1467
|
|
|
1460
1468
|
// src/target/container.ts
|
|
1461
1469
|
async function provisionContainer(host, cfg, onLog) {
|
|
@@ -1469,7 +1477,7 @@ async function provisionContainer(host, cfg, onLog) {
|
|
|
1469
1477
|
};
|
|
1470
1478
|
await ensureDockerReady(host, cfg.install ?? "never", emit2);
|
|
1471
1479
|
const { containerId, hostPort, created } = await runContainer(host, cfg, emit2);
|
|
1472
|
-
await ensureDaemon(new ContainerHost(host, containerId), {
|
|
1480
|
+
const token = await ensureDaemon(new ContainerHost(host, containerId), {
|
|
1473
1481
|
port: cfg.daemonPort,
|
|
1474
1482
|
agent: cfg.agent,
|
|
1475
1483
|
agentCwd: cfg.agentCwd,
|
|
@@ -1488,7 +1496,7 @@ async function provisionContainer(host, cfg, onLog) {
|
|
|
1488
1496
|
} catch {
|
|
1489
1497
|
}
|
|
1490
1498
|
};
|
|
1491
|
-
return { containerId, hostPort, stop };
|
|
1499
|
+
return { containerId, hostPort, token, stop };
|
|
1492
1500
|
}
|
|
1493
1501
|
var ContainerHost = class {
|
|
1494
1502
|
constructor(base, containerId) {
|
|
@@ -1724,7 +1732,7 @@ async function connectSsh(opts, spec) {
|
|
|
1724
1732
|
});
|
|
1725
1733
|
}
|
|
1726
1734
|
async function provisionSsh(client, makeTunnel, cfg) {
|
|
1727
|
-
await ensureDaemon(new SshHost(client), {
|
|
1735
|
+
const token = await ensureDaemon(new SshHost(client), {
|
|
1728
1736
|
port: cfg.daemonPort,
|
|
1729
1737
|
agent: cfg.agent,
|
|
1730
1738
|
agentCwd: cfg.agentCwd,
|
|
@@ -1747,7 +1755,7 @@ async function provisionSsh(client, makeTunnel, cfg) {
|
|
|
1747
1755
|
}
|
|
1748
1756
|
}
|
|
1749
1757
|
};
|
|
1750
|
-
return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
|
|
1758
|
+
return { id: cfg.id, baseUrl: tunnel.baseUrl, token, stop };
|
|
1751
1759
|
}
|
|
1752
1760
|
async function provisionSshContainer(client, makeTunnel, cfg) {
|
|
1753
1761
|
const container = await provisionContainer(
|
|
@@ -1784,7 +1792,7 @@ async function provisionSshContainer(client, makeTunnel, cfg) {
|
|
|
1784
1792
|
}
|
|
1785
1793
|
}
|
|
1786
1794
|
};
|
|
1787
|
-
return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
|
|
1795
|
+
return { id: cfg.id, baseUrl: tunnel.baseUrl, token: container.token, stop };
|
|
1788
1796
|
}
|
|
1789
1797
|
async function openConnection(mod, opts) {
|
|
1790
1798
|
const auth = await buildAuth(opts);
|
|
@@ -1996,7 +2004,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
1996
2004
|
`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 + "`).")
|
|
1997
2005
|
);
|
|
1998
2006
|
}
|
|
1999
|
-
await ensureDaemon(new DockerHost(container), {
|
|
2007
|
+
const token = await ensureDaemon(new DockerHost(container), {
|
|
2000
2008
|
port,
|
|
2001
2009
|
agent,
|
|
2002
2010
|
agentCwd,
|
|
@@ -2015,7 +2023,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
|
|
|
2015
2023
|
} catch {
|
|
2016
2024
|
}
|
|
2017
2025
|
};
|
|
2018
|
-
return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, stop };
|
|
2026
|
+
return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, token, stop };
|
|
2019
2027
|
}
|
|
2020
2028
|
async function ensureImage(docker2, image, policy, onLog) {
|
|
2021
2029
|
const emit2 = (message, error) => {
|
|
@@ -2152,26 +2160,31 @@ async function provisionOblien(client, config = {}, onLog) {
|
|
|
2152
2160
|
const ws = await client.workspaces.create(params);
|
|
2153
2161
|
workspaceId = ws.id;
|
|
2154
2162
|
}
|
|
2163
|
+
await config.onWorkspace?.(workspaceId);
|
|
2155
2164
|
const handle = client.workspace(workspaceId);
|
|
2156
2165
|
await handle.start().catch(() => {
|
|
2157
2166
|
});
|
|
2158
2167
|
let rt = await handle.runtime();
|
|
2159
|
-
await ensureDaemon(new OblienHost(rt), {
|
|
2168
|
+
const token = await ensureDaemon(new OblienHost(rt), {
|
|
2160
2169
|
port,
|
|
2161
2170
|
agent,
|
|
2162
2171
|
agentCwd,
|
|
2163
2172
|
daemonBin: config.daemonBin,
|
|
2164
2173
|
autoUpdate: config.autoUpdate,
|
|
2174
|
+
forceDeploy: config.forceDeploy,
|
|
2165
2175
|
target: "oblien",
|
|
2166
2176
|
onLog
|
|
2167
2177
|
});
|
|
2168
2178
|
const fetchImpl = async (url, init) => {
|
|
2169
2179
|
const { pathname, search } = new URL(url);
|
|
2170
2180
|
const path = pathname + search;
|
|
2171
|
-
|
|
2181
|
+
const headers = new Headers(init?.headers);
|
|
2182
|
+
headers.set("X-Mindwire-Token", token);
|
|
2183
|
+
headers.delete("Authorization");
|
|
2184
|
+
let res = await rt.proxy(port).fetch(path, { ...init, headers });
|
|
2172
2185
|
if (res.status === 401) {
|
|
2173
2186
|
rt = await handle.runtime({ force: true });
|
|
2174
|
-
res = await rt.proxy(port).fetch(path, init);
|
|
2187
|
+
res = await rt.proxy(port).fetch(path, { ...init, headers });
|
|
2175
2188
|
}
|
|
2176
2189
|
return res;
|
|
2177
2190
|
};
|
|
@@ -2182,7 +2195,7 @@ async function provisionOblien(client, config = {}, onLog) {
|
|
|
2182
2195
|
} catch {
|
|
2183
2196
|
}
|
|
2184
2197
|
};
|
|
2185
|
-
return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, fetch: fetchImpl, stop };
|
|
2198
|
+
return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, token, fetch: fetchImpl, stop };
|
|
2186
2199
|
}
|
|
2187
2200
|
async function importOblien() {
|
|
2188
2201
|
const spec = "oblien";
|