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/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.2" ;
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` — public liveness check. Resolves with the daemon's health payload when it is up. */
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} bytes)`, arch, bytes: 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="" ${BIN} > ${LOG} 2>&1 < /dev/null &`,
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
- if (!fs.existsSync(explicit)) {
1440
- throw new MindwireError(`mindwire: sandbox daemonBin not found at ${explicit}`);
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 explicit;
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
- let res = await rt.proxy(port).fetch(path, init);
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";