@cryptiklemur/lattice 5.1.1 → 5.3.0
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/server/daemon.js +17 -2
- package/dist/server/index.js +170 -5
- package/dist/server/tls.js +17 -2
- package/package.json +1 -1
package/dist/server/daemon.js
CHANGED
|
@@ -404,12 +404,27 @@ export async function startDaemon(portOverride, tlsOverride) {
|
|
|
404
404
|
cert: readFileSync(certs.cert),
|
|
405
405
|
key: readFileSync(certs.key),
|
|
406
406
|
};
|
|
407
|
-
log.server("TLS enabled");
|
|
407
|
+
log.server("TLS enabled (cert: %s)", certs.cert);
|
|
408
408
|
}
|
|
409
409
|
catch (err) {
|
|
410
|
-
|
|
410
|
+
if (err?.code === "EACCES") {
|
|
411
|
+
console.error("[lattice] Permission denied reading TLS certs. Run 'lattice setup-tls' to fix permissions.");
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
console.error("[lattice] Failed to load TLS certs, falling back to HTTP:", err);
|
|
415
|
+
}
|
|
411
416
|
}
|
|
412
417
|
}
|
|
418
|
+
if (tlsOptions) {
|
|
419
|
+
var certsDir = join(getLatticeHome(), "certs");
|
|
420
|
+
log.server("TLS cert: %s", join(certsDir, "cert.pem"));
|
|
421
|
+
log.server("To trust the self-signed cert:");
|
|
422
|
+
log.server(" Linux: sudo cp %s /usr/local/share/ca-certificates/lattice.crt && sudo update-ca-certificates", join(certsDir, "cert.pem"));
|
|
423
|
+
log.server(" macOS: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain %s", join(certsDir, "cert.pem"));
|
|
424
|
+
log.server(" Or use Tailscale HTTPS (no trust needed):");
|
|
425
|
+
log.server(" tailscale cert $(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\\.$//')");
|
|
426
|
+
log.server(" Then copy: cp <hostname>.crt %s && cp <hostname>.key %s", join(certsDir, "cert.pem"), join(certsDir, "key.pem"));
|
|
427
|
+
}
|
|
413
428
|
var protocol = tlsOptions ? "https" : "http";
|
|
414
429
|
var httpServer = tlsOptions
|
|
415
430
|
? createHttpsServer(tlsOptions, app)
|
package/dist/server/index.js
CHANGED
|
@@ -18,7 +18,7 @@ function getCurrentVersion() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
var args = process.argv.slice(2);
|
|
21
|
-
var command = "
|
|
21
|
+
var command = "help";
|
|
22
22
|
var portOverride = null;
|
|
23
23
|
var tlsOverride = null;
|
|
24
24
|
for (var i = 0; i < args.length; i++) {
|
|
@@ -92,11 +92,11 @@ function spawnDaemon(port, tls) {
|
|
|
92
92
|
var pkgRoot = join(__dirname_local, "..");
|
|
93
93
|
var localTsx = join(pkgRoot, "node_modules", ".bin", "tsx");
|
|
94
94
|
spawnCmd = existsSync(localTsx) ? localTsx : "tsx";
|
|
95
|
-
spawnArgs = ["--tsconfig", join(pkgRoot, "tsconfig.json"), __filename_local, "
|
|
95
|
+
spawnArgs = ["--tsconfig", join(pkgRoot, "tsconfig.json"), __filename_local, "run", "--port", String(port)];
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
98
98
|
spawnCmd = process.execPath;
|
|
99
|
-
spawnArgs = [__filename_local, "
|
|
99
|
+
spawnArgs = [__filename_local, "run", "--port", String(port)];
|
|
100
100
|
}
|
|
101
101
|
if (tls === true)
|
|
102
102
|
spawnArgs.push("--tls");
|
|
@@ -111,6 +111,7 @@ function spawnDaemon(port, tls) {
|
|
|
111
111
|
return child.pid;
|
|
112
112
|
}
|
|
113
113
|
switch (command) {
|
|
114
|
+
case "run":
|
|
114
115
|
case "daemon":
|
|
115
116
|
await runDaemon();
|
|
116
117
|
break;
|
|
@@ -141,6 +142,9 @@ switch (command) {
|
|
|
141
142
|
case "config":
|
|
142
143
|
runConfigInfo();
|
|
143
144
|
break;
|
|
145
|
+
case "setup-tls":
|
|
146
|
+
await runSetupTls();
|
|
147
|
+
break;
|
|
144
148
|
case "help":
|
|
145
149
|
case "--help":
|
|
146
150
|
case "-h":
|
|
@@ -260,7 +264,8 @@ function runHelp() {
|
|
|
260
264
|
console.log(" Usage: lattice [command] [options]");
|
|
261
265
|
console.log("");
|
|
262
266
|
console.log(" Commands:");
|
|
263
|
-
console.log(" start Start the daemon and open the UI
|
|
267
|
+
console.log(" start Start the daemon and open the UI");
|
|
268
|
+
console.log(" run Run the daemon in the foreground");
|
|
264
269
|
console.log(" stop Stop the running daemon");
|
|
265
270
|
console.log(" restart Stop and restart the daemon");
|
|
266
271
|
console.log(" status Show daemon status and connection info");
|
|
@@ -269,7 +274,8 @@ function runHelp() {
|
|
|
269
274
|
console.log(" logs Tail the daemon log");
|
|
270
275
|
console.log(" open Open the UI in the browser");
|
|
271
276
|
console.log(" config Show configuration paths and settings");
|
|
272
|
-
console.log("
|
|
277
|
+
console.log(" setup-tls Set up HTTPS (Tailscale or self-signed)");
|
|
278
|
+
console.log(" help Show this help message (default)");
|
|
273
279
|
console.log("");
|
|
274
280
|
console.log(" Options:");
|
|
275
281
|
console.log(" --port=N Override the server port");
|
|
@@ -281,6 +287,165 @@ function runHelp() {
|
|
|
281
287
|
console.log(" DEBUG Enable debug logging (e.g. DEBUG=lattice:*)");
|
|
282
288
|
console.log("");
|
|
283
289
|
}
|
|
290
|
+
async function runSetupTls() {
|
|
291
|
+
var { spawnSync } = await import("node:child_process");
|
|
292
|
+
var { createInterface } = await import("node:readline");
|
|
293
|
+
var { mkdirSync, chmodSync, chownSync, statSync } = await import("node:fs");
|
|
294
|
+
var certsDir = join(getLatticeHome(), "certs");
|
|
295
|
+
var certPath = join(certsDir, "cert.pem");
|
|
296
|
+
var keyPath = join(certsDir, "key.pem");
|
|
297
|
+
function prompt(question) {
|
|
298
|
+
var rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
299
|
+
return new Promise(function (resolve) {
|
|
300
|
+
rl.question(question, function (answer) {
|
|
301
|
+
rl.close();
|
|
302
|
+
resolve(answer.trim().toLowerCase());
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
console.log("");
|
|
307
|
+
console.log(" lattice — TLS Setup");
|
|
308
|
+
console.log("");
|
|
309
|
+
var hasTailscale = false;
|
|
310
|
+
var hostname = "";
|
|
311
|
+
try {
|
|
312
|
+
spawnSync("tailscale", ["version"], { stdio: "ignore" });
|
|
313
|
+
hasTailscale = true;
|
|
314
|
+
var statusResult = spawnSync("tailscale", ["status", "--json"], { encoding: "utf-8" });
|
|
315
|
+
if (statusResult.status === 0) {
|
|
316
|
+
hostname = JSON.parse(statusResult.stdout).Self.DNSName.replace(/\.$/, "");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch { }
|
|
320
|
+
if (hasTailscale && hostname) {
|
|
321
|
+
console.log(" Tailscale detected: " + hostname);
|
|
322
|
+
console.log("");
|
|
323
|
+
var answer = await prompt(" Set up Tailscale HTTPS certs automatically? [Y/n] ");
|
|
324
|
+
if (answer !== "n" && answer !== "no") {
|
|
325
|
+
if (!existsSync(certsDir)) {
|
|
326
|
+
mkdirSync(certsDir, { recursive: true });
|
|
327
|
+
}
|
|
328
|
+
console.log("");
|
|
329
|
+
console.log(" Generating Tailscale cert (sudo required)...");
|
|
330
|
+
console.log("");
|
|
331
|
+
var certResult = spawnSync("sudo", ["tailscale", "cert", "--cert-file", certPath, "--key-file", keyPath, hostname], { stdio: "inherit" });
|
|
332
|
+
if (certResult.status !== 0) {
|
|
333
|
+
console.error("");
|
|
334
|
+
console.error(" Failed to generate Tailscale certs.");
|
|
335
|
+
console.error(" You can run manually:");
|
|
336
|
+
console.error(" sudo tailscale cert --cert-file " + certPath + " --key-file " + keyPath + " " + hostname);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
var uid = process.getuid ? process.getuid() : 0;
|
|
340
|
+
var gid = process.getgid ? process.getgid() : 0;
|
|
341
|
+
try {
|
|
342
|
+
chownSync(certPath, uid, gid);
|
|
343
|
+
chownSync(keyPath, uid, gid);
|
|
344
|
+
chmodSync(certPath, 0o644);
|
|
345
|
+
chmodSync(keyPath, 0o600);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
console.log(" Fixing permissions with sudo...");
|
|
349
|
+
spawnSync("sudo", ["chown", uid + ":" + gid, certPath, keyPath], { stdio: "inherit" });
|
|
350
|
+
spawnSync("sudo", ["chmod", "644", certPath], { stdio: "inherit" });
|
|
351
|
+
spawnSync("sudo", ["chmod", "600", keyPath], { stdio: "inherit" });
|
|
352
|
+
}
|
|
353
|
+
console.log("");
|
|
354
|
+
console.log(" Certs installed and permissions fixed.");
|
|
355
|
+
var config = loadConfig();
|
|
356
|
+
if (!config.tls) {
|
|
357
|
+
var { saveConfig: saveCfg } = await import("./config.js");
|
|
358
|
+
config.tls = true;
|
|
359
|
+
saveCfg(config);
|
|
360
|
+
console.log(" TLS enabled in config.");
|
|
361
|
+
}
|
|
362
|
+
var pid = readPid();
|
|
363
|
+
if (pid !== null && isDaemonRunning(pid)) {
|
|
364
|
+
var restartAnswer = await prompt(" Daemon is running. Restart with TLS? [Y/n] ");
|
|
365
|
+
if (restartAnswer !== "n" && restartAnswer !== "no") {
|
|
366
|
+
console.log(" Restarting daemon...");
|
|
367
|
+
try {
|
|
368
|
+
process.kill(pid, "SIGTERM");
|
|
369
|
+
}
|
|
370
|
+
catch { }
|
|
371
|
+
removePid();
|
|
372
|
+
await new Promise(function (r) { setTimeout(r, 1500); });
|
|
373
|
+
var port = portOverride ?? config.port;
|
|
374
|
+
var childPid = spawnDaemon(port, true);
|
|
375
|
+
writePid(childPid);
|
|
376
|
+
console.log(" Daemon restarted (PID " + childPid + ")");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
console.log("");
|
|
380
|
+
console.log(" Access at: https://" + hostname + ":" + loadConfig().port);
|
|
381
|
+
console.log("");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (hasTailscale) {
|
|
386
|
+
console.log(" Tailscale detected but could not determine hostname.");
|
|
387
|
+
console.log(" Run 'tailscale status' to verify your connection.");
|
|
388
|
+
console.log("");
|
|
389
|
+
}
|
|
390
|
+
console.log(" ── Self-signed certificate ──");
|
|
391
|
+
console.log("");
|
|
392
|
+
if (existsSync(certPath) && existsSync(keyPath)) {
|
|
393
|
+
console.log(" Cert exists: " + certPath);
|
|
394
|
+
console.log(" To regenerate: rm -rf " + certsDir + " && lattice setup-tls");
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
var selfSignAnswer = await prompt(" Generate a self-signed certificate? [Y/n] ");
|
|
398
|
+
if (selfSignAnswer !== "n" && selfSignAnswer !== "no") {
|
|
399
|
+
var { ensureCerts } = await import("./tls.js");
|
|
400
|
+
try {
|
|
401
|
+
var certs = ensureCerts();
|
|
402
|
+
console.log(" Certificate generated: " + certs.cert);
|
|
403
|
+
var config = loadConfig();
|
|
404
|
+
if (!config.tls) {
|
|
405
|
+
var { saveConfig: saveCfg } = await import("./config.js");
|
|
406
|
+
config.tls = true;
|
|
407
|
+
saveCfg(config);
|
|
408
|
+
console.log(" TLS enabled in config.");
|
|
409
|
+
}
|
|
410
|
+
var pid = readPid();
|
|
411
|
+
if (pid !== null && isDaemonRunning(pid)) {
|
|
412
|
+
var restartAnswer = await prompt(" Daemon is running. Restart with TLS? [Y/n] ");
|
|
413
|
+
if (restartAnswer !== "n" && restartAnswer !== "no") {
|
|
414
|
+
console.log(" Restarting daemon...");
|
|
415
|
+
try {
|
|
416
|
+
process.kill(pid, "SIGTERM");
|
|
417
|
+
}
|
|
418
|
+
catch { }
|
|
419
|
+
removePid();
|
|
420
|
+
await new Promise(function (r) { setTimeout(r, 1500); });
|
|
421
|
+
var port = portOverride ?? config.port;
|
|
422
|
+
var childPid = spawnDaemon(port, true);
|
|
423
|
+
writePid(childPid);
|
|
424
|
+
console.log(" Daemon restarted (PID " + childPid + ")");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
console.error(" Failed to generate certificate:", err);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
console.log("");
|
|
435
|
+
console.log(" To trust the self-signed cert (removes browser warnings):");
|
|
436
|
+
console.log("");
|
|
437
|
+
console.log(" Linux:");
|
|
438
|
+
console.log(" sudo cp " + certPath + " /usr/local/share/ca-certificates/lattice.crt");
|
|
439
|
+
console.log(" sudo update-ca-certificates");
|
|
440
|
+
console.log("");
|
|
441
|
+
console.log(" macOS:");
|
|
442
|
+
console.log(" sudo security add-trusted-cert -d -r trustRoot \\");
|
|
443
|
+
console.log(" -k /Library/Keychains/System.keychain " + certPath);
|
|
444
|
+
console.log("");
|
|
445
|
+
console.log(" Windows (PowerShell as admin):");
|
|
446
|
+
console.log(" Import-Certificate -FilePath " + certPath + " -CertStoreLocation Cert:\\LocalMachine\\Root");
|
|
447
|
+
console.log("");
|
|
448
|
+
}
|
|
284
449
|
async function runRestart() {
|
|
285
450
|
var pid = readPid();
|
|
286
451
|
if (pid !== null && isDaemonRunning(pid)) {
|
package/dist/server/tls.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { networkInterfaces } from "node:os";
|
|
4
5
|
import { getLatticeHome } from "./config.js";
|
|
5
6
|
export function getCertsDir() {
|
|
6
7
|
var certsDir = join(getLatticeHome(), "certs");
|
|
@@ -33,6 +34,20 @@ export function ensureCerts() {
|
|
|
33
34
|
return { cert: certPath, key: keyPath };
|
|
34
35
|
}
|
|
35
36
|
console.log("[lattice] Generating self-signed TLS certificate...");
|
|
37
|
+
var sans = ["DNS:lattice", "DNS:localhost", "IP:127.0.0.1", "IP:::1"];
|
|
38
|
+
var ifaces = networkInterfaces();
|
|
39
|
+
for (var name in ifaces) {
|
|
40
|
+
var addrs = ifaces[name];
|
|
41
|
+
if (!addrs)
|
|
42
|
+
continue;
|
|
43
|
+
for (var i = 0; i < addrs.length; i++) {
|
|
44
|
+
if (!addrs[i].internal) {
|
|
45
|
+
sans.push("IP:" + addrs[i].address);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
var extFile = join(certsDir, "openssl-san.cnf");
|
|
50
|
+
writeFileSync(extFile, "[req]\ndistinguished_name=dn\nx509_extensions=v3\nprompt=no\n[dn]\nCN=lattice\n[v3]\nsubjectAltName=" + sans.join(",") + "\n");
|
|
36
51
|
var result = spawnSync("openssl", [
|
|
37
52
|
"req", "-x509",
|
|
38
53
|
"-newkey", "rsa:2048",
|
|
@@ -40,7 +55,7 @@ export function ensureCerts() {
|
|
|
40
55
|
"-out", certPath,
|
|
41
56
|
"-days", "365",
|
|
42
57
|
"-nodes",
|
|
43
|
-
"-
|
|
58
|
+
"-config", extFile,
|
|
44
59
|
], { encoding: "utf-8" });
|
|
45
60
|
if (result.status !== 0) {
|
|
46
61
|
throw new Error("[lattice] Failed to generate TLS certificates: " + (result.stderr || result.error?.message || "unknown error"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aaron Scherer <me@aaronscherer.me>",
|