buildwithnexus 0.5.2 → 0.5.4
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/bin.js +167 -130
- package/dist/nexus-release.tar.gz +0 -0
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -20,7 +20,7 @@ __export(banner_exports, {
|
|
|
20
20
|
import chalk from "chalk";
|
|
21
21
|
function showBanner() {
|
|
22
22
|
console.log(BANNER);
|
|
23
|
-
console.log(chalk.dim(" v0.5.
|
|
23
|
+
console.log(chalk.dim(" v0.5.4 \xB7 buildwithnexus.dev\n"));
|
|
24
24
|
}
|
|
25
25
|
function showPhase(phase, total, description) {
|
|
26
26
|
const progress = chalk.cyan(`[${phase}/${total}]`);
|
|
@@ -282,26 +282,26 @@ __export(secrets_exports, {
|
|
|
282
282
|
saveConfig: () => saveConfig,
|
|
283
283
|
saveKeys: () => saveKeys
|
|
284
284
|
});
|
|
285
|
-
import
|
|
286
|
-
import
|
|
285
|
+
import fs3 from "fs";
|
|
286
|
+
import path3 from "path";
|
|
287
287
|
import crypto2 from "crypto";
|
|
288
288
|
function ensureHome() {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
289
|
+
fs3.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
|
|
290
|
+
fs3.mkdirSync(path3.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
|
|
291
|
+
fs3.mkdirSync(path3.join(NEXUS_HOME2, "vm", "configs"), { recursive: true, mode: 448 });
|
|
292
|
+
fs3.mkdirSync(path3.join(NEXUS_HOME2, "vm", "logs"), { recursive: true, mode: 448 });
|
|
293
|
+
fs3.mkdirSync(path3.join(NEXUS_HOME2, "ssh"), { recursive: true, mode: 448 });
|
|
294
294
|
}
|
|
295
295
|
function generateMasterSecret() {
|
|
296
296
|
return crypto2.randomBytes(32).toString("base64url");
|
|
297
297
|
}
|
|
298
298
|
function saveConfig(config) {
|
|
299
299
|
const { masterSecret: _secret, ...safeConfig } = config;
|
|
300
|
-
|
|
300
|
+
fs3.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2), { mode: 384 });
|
|
301
301
|
}
|
|
302
302
|
function loadConfig() {
|
|
303
|
-
if (!
|
|
304
|
-
return JSON.parse(
|
|
303
|
+
if (!fs3.existsSync(CONFIG_PATH)) return null;
|
|
304
|
+
return JSON.parse(fs3.readFileSync(CONFIG_PATH, "utf-8"));
|
|
305
305
|
}
|
|
306
306
|
function saveKeys(keys) {
|
|
307
307
|
const violations = validateAllKeys(keys);
|
|
@@ -309,13 +309,13 @@ function saveKeys(keys) {
|
|
|
309
309
|
throw new DlpViolation(`Key validation failed: ${violations.join("; ")}`);
|
|
310
310
|
}
|
|
311
311
|
const lines = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`);
|
|
312
|
-
|
|
312
|
+
fs3.writeFileSync(KEYS_PATH, lines.join("\n") + "\n", { mode: 384 });
|
|
313
313
|
sealKeysFile(KEYS_PATH, keys.NEXUS_MASTER_SECRET);
|
|
314
314
|
audit("keys_saved", `${Object.keys(keys).filter((k) => keys[k]).length} keys saved`);
|
|
315
315
|
}
|
|
316
316
|
function loadKeys() {
|
|
317
|
-
if (!
|
|
318
|
-
const content =
|
|
317
|
+
if (!fs3.existsSync(KEYS_PATH)) return null;
|
|
318
|
+
const content = fs3.readFileSync(KEYS_PATH, "utf-8");
|
|
319
319
|
const keys = {};
|
|
320
320
|
for (const line of content.split("\n")) {
|
|
321
321
|
const eq = line.indexOf("=");
|
|
@@ -341,9 +341,9 @@ var init_secrets = __esm({
|
|
|
341
341
|
"src/core/secrets.ts"() {
|
|
342
342
|
"use strict";
|
|
343
343
|
init_dlp();
|
|
344
|
-
NEXUS_HOME2 = process.env.NEXUS_HOME ||
|
|
345
|
-
CONFIG_PATH = process.env.NEXUS_CONFIG_PATH ||
|
|
346
|
-
KEYS_PATH = process.env.NEXUS_KEYS_PATH ||
|
|
344
|
+
NEXUS_HOME2 = process.env.NEXUS_HOME || path3.join(process.env.HOME || "~", ".buildwithnexus");
|
|
345
|
+
CONFIG_PATH = process.env.NEXUS_CONFIG_PATH || path3.join(NEXUS_HOME2, "config.json");
|
|
346
|
+
KEYS_PATH = process.env.NEXUS_KEYS_PATH || path3.join(NEXUS_HOME2, ".env.keys");
|
|
347
347
|
}
|
|
348
348
|
});
|
|
349
349
|
|
|
@@ -360,9 +360,9 @@ __export(qemu_exports, {
|
|
|
360
360
|
resolvePortConflicts: () => resolvePortConflicts,
|
|
361
361
|
stopVm: () => stopVm
|
|
362
362
|
});
|
|
363
|
-
import
|
|
363
|
+
import fs4 from "fs";
|
|
364
364
|
import net from "net";
|
|
365
|
-
import
|
|
365
|
+
import path4 from "path";
|
|
366
366
|
import { execa, execaSync } from "execa";
|
|
367
367
|
import { select } from "@inquirer/prompts";
|
|
368
368
|
import chalk5 from "chalk";
|
|
@@ -395,15 +395,15 @@ async function installQemu(platform) {
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
async function downloadImage(platform) {
|
|
398
|
-
const imagePath =
|
|
399
|
-
if (
|
|
398
|
+
const imagePath = path4.join(IMAGES_DIR, platform.ubuntuImage);
|
|
399
|
+
if (fs4.existsSync(imagePath)) return imagePath;
|
|
400
400
|
const url = `${UBUNTU_BASE_URL}/${platform.ubuntuImage}`;
|
|
401
401
|
await execa("curl", ["-L", "-C", "-", "-o", imagePath, "--progress-bar", url], { stdio: "inherit", env: scrubEnv() });
|
|
402
402
|
return imagePath;
|
|
403
403
|
}
|
|
404
404
|
async function createDisk(basePath, sizeGb) {
|
|
405
|
-
const diskPath =
|
|
406
|
-
if (
|
|
405
|
+
const diskPath = path4.join(IMAGES_DIR, "nexus-vm-disk.qcow2");
|
|
406
|
+
if (fs4.existsSync(diskPath)) return diskPath;
|
|
407
407
|
await execa("qemu-img", ["create", "-f", "qcow2", "-b", basePath, "-F", "qcow2", diskPath, `${sizeGb}G`], { env: scrubEnv() });
|
|
408
408
|
return diskPath;
|
|
409
409
|
}
|
|
@@ -497,7 +497,7 @@ async function resolvePortConflicts(ports) {
|
|
|
497
497
|
}
|
|
498
498
|
async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
499
499
|
const machineArgs = platform.os === "mac" ? ["-machine", "virt,gic-version=3"] : ["-machine", "pc"];
|
|
500
|
-
const biosArgs =
|
|
500
|
+
const biosArgs = fs4.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
|
|
501
501
|
const buildArgs = (cpuArgs) => [
|
|
502
502
|
...machineArgs,
|
|
503
503
|
...cpuArgs,
|
|
@@ -531,8 +531,8 @@ async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
|
531
531
|
return ports;
|
|
532
532
|
}
|
|
533
533
|
function readValidPid() {
|
|
534
|
-
if (!
|
|
535
|
-
const raw =
|
|
534
|
+
if (!fs4.existsSync(PID_FILE)) return null;
|
|
535
|
+
const raw = fs4.readFileSync(PID_FILE, "utf-8").trim();
|
|
536
536
|
const pid = parseInt(raw, 10);
|
|
537
537
|
if (!Number.isInteger(pid) || pid <= 1 || pid > 4194304) return null;
|
|
538
538
|
return pid;
|
|
@@ -560,7 +560,7 @@ function stopVm() {
|
|
|
560
560
|
if (!pid) return;
|
|
561
561
|
if (!isQemuPid(pid)) {
|
|
562
562
|
try {
|
|
563
|
-
|
|
563
|
+
fs4.unlinkSync(PID_FILE);
|
|
564
564
|
} catch {
|
|
565
565
|
}
|
|
566
566
|
return;
|
|
@@ -570,7 +570,7 @@ function stopVm() {
|
|
|
570
570
|
} catch {
|
|
571
571
|
}
|
|
572
572
|
try {
|
|
573
|
-
|
|
573
|
+
fs4.unlinkSync(PID_FILE);
|
|
574
574
|
} catch {
|
|
575
575
|
}
|
|
576
576
|
}
|
|
@@ -583,9 +583,9 @@ var init_qemu = __esm({
|
|
|
583
583
|
"use strict";
|
|
584
584
|
init_secrets();
|
|
585
585
|
init_dlp();
|
|
586
|
-
VM_DIR =
|
|
587
|
-
IMAGES_DIR =
|
|
588
|
-
PID_FILE =
|
|
586
|
+
VM_DIR = path4.join(NEXUS_HOME2, "vm");
|
|
587
|
+
IMAGES_DIR = path4.join(VM_DIR, "images");
|
|
588
|
+
PID_FILE = path4.join(VM_DIR, "qemu.pid");
|
|
589
589
|
UBUNTU_BASE_URL = "https://cloud-images.ubuntu.com/jammy/current";
|
|
590
590
|
}
|
|
591
591
|
});
|
|
@@ -602,21 +602,21 @@ __export(ssh_exports, {
|
|
|
602
602
|
sshUploadFile: () => sshUploadFile,
|
|
603
603
|
waitForSsh: () => waitForSsh
|
|
604
604
|
});
|
|
605
|
-
import
|
|
606
|
-
import
|
|
605
|
+
import fs5 from "fs";
|
|
606
|
+
import path5 from "path";
|
|
607
607
|
import crypto3 from "crypto";
|
|
608
608
|
import { execa as execa2 } from "execa";
|
|
609
609
|
import { NodeSSH } from "node-ssh";
|
|
610
610
|
function getHostVerifier() {
|
|
611
|
-
if (!
|
|
611
|
+
if (!fs5.existsSync(PINNED_HOST_KEY)) {
|
|
612
612
|
return (key) => {
|
|
613
613
|
const fp = crypto3.createHash("sha256").update(key).digest("base64");
|
|
614
|
-
|
|
614
|
+
fs5.writeFileSync(PINNED_HOST_KEY, fp, { mode: 384 });
|
|
615
615
|
audit("ssh_exec", `host key pinned: SHA256:${fp}`);
|
|
616
616
|
return true;
|
|
617
617
|
};
|
|
618
618
|
}
|
|
619
|
-
const pinned =
|
|
619
|
+
const pinned = fs5.readFileSync(PINNED_HOST_KEY, "utf-8").trim();
|
|
620
620
|
return (key) => {
|
|
621
621
|
const fp = crypto3.createHash("sha256").update(key).digest("base64");
|
|
622
622
|
const match = fp === pinned;
|
|
@@ -628,11 +628,11 @@ function getKeyPath() {
|
|
|
628
628
|
return SSH_KEY;
|
|
629
629
|
}
|
|
630
630
|
function getPubKey() {
|
|
631
|
-
return
|
|
631
|
+
return fs5.readFileSync(SSH_PUB_KEY, "utf-8").trim();
|
|
632
632
|
}
|
|
633
633
|
async function generateSshKey() {
|
|
634
|
-
if (
|
|
635
|
-
|
|
634
|
+
if (fs5.existsSync(SSH_KEY)) return;
|
|
635
|
+
fs5.mkdirSync(SSH_DIR, { recursive: true });
|
|
636
636
|
await execa2("ssh-keygen", [
|
|
637
637
|
"-t",
|
|
638
638
|
"ed25519",
|
|
@@ -644,13 +644,13 @@ async function generateSshKey() {
|
|
|
644
644
|
"buildwithnexus@localhost",
|
|
645
645
|
"-q"
|
|
646
646
|
], { env: scrubEnv() });
|
|
647
|
-
|
|
648
|
-
|
|
647
|
+
fs5.chmodSync(SSH_KEY, 384);
|
|
648
|
+
fs5.chmodSync(SSH_PUB_KEY, 420);
|
|
649
649
|
}
|
|
650
650
|
function addSshConfig(port) {
|
|
651
|
-
const sshConfigPath =
|
|
652
|
-
const sshDir =
|
|
653
|
-
|
|
651
|
+
const sshConfigPath = path5.join(process.env.HOME || "~", ".ssh", "config");
|
|
652
|
+
const sshDir = path5.dirname(sshConfigPath);
|
|
653
|
+
fs5.mkdirSync(sshDir, { recursive: true });
|
|
654
654
|
const block = [
|
|
655
655
|
"",
|
|
656
656
|
"Host nexus-vm",
|
|
@@ -663,12 +663,12 @@ function addSshConfig(port) {
|
|
|
663
663
|
" ServerAliveInterval 60",
|
|
664
664
|
""
|
|
665
665
|
].join("\n");
|
|
666
|
-
if (
|
|
667
|
-
const existing =
|
|
666
|
+
if (fs5.existsSync(sshConfigPath)) {
|
|
667
|
+
const existing = fs5.readFileSync(sshConfigPath, "utf-8");
|
|
668
668
|
if (existing.includes("Host nexus-vm")) return;
|
|
669
|
-
|
|
669
|
+
fs5.appendFileSync(sshConfigPath, block);
|
|
670
670
|
} else {
|
|
671
|
-
|
|
671
|
+
fs5.writeFileSync(sshConfigPath, block, { mode: 384 });
|
|
672
672
|
}
|
|
673
673
|
}
|
|
674
674
|
async function waitForSsh(port, timeoutMs = 3e5) {
|
|
@@ -736,11 +736,11 @@ var init_ssh = __esm({
|
|
|
736
736
|
"use strict";
|
|
737
737
|
init_secrets();
|
|
738
738
|
init_dlp();
|
|
739
|
-
SSH_DIR =
|
|
740
|
-
SSH_KEY =
|
|
741
|
-
SSH_PUB_KEY =
|
|
742
|
-
KNOWN_HOSTS =
|
|
743
|
-
PINNED_HOST_KEY =
|
|
739
|
+
SSH_DIR = path5.join(NEXUS_HOME2, "ssh");
|
|
740
|
+
SSH_KEY = path5.join(SSH_DIR, "id_nexus_vm");
|
|
741
|
+
SSH_PUB_KEY = path5.join(SSH_DIR, "id_nexus_vm.pub");
|
|
742
|
+
KNOWN_HOSTS = path5.join(SSH_DIR, "known_hosts_nexus_vm");
|
|
743
|
+
PINNED_HOST_KEY = path5.join(SSH_DIR, "vm_host_key.pin");
|
|
744
744
|
}
|
|
745
745
|
});
|
|
746
746
|
|
|
@@ -799,6 +799,39 @@ var log = {
|
|
|
799
799
|
init_dlp();
|
|
800
800
|
import { input, confirm, password } from "@inquirer/prompts";
|
|
801
801
|
import chalk4 from "chalk";
|
|
802
|
+
import fs2 from "fs";
|
|
803
|
+
import path2 from "path";
|
|
804
|
+
import os from "os";
|
|
805
|
+
function loadInitConfigFromDisk() {
|
|
806
|
+
const homeDir = os.homedir();
|
|
807
|
+
const configPath = path2.join(homeDir, ".buildwithnexus", "config.json");
|
|
808
|
+
const keysPath = path2.join(homeDir, ".buildwithnexus", ".env.keys");
|
|
809
|
+
if (!fs2.existsSync(configPath)) return null;
|
|
810
|
+
try {
|
|
811
|
+
const configData = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
812
|
+
const keysData = {};
|
|
813
|
+
if (fs2.existsSync(keysPath)) {
|
|
814
|
+
const keysContent = fs2.readFileSync(keysPath, "utf-8");
|
|
815
|
+
keysContent.split("\n").forEach((line) => {
|
|
816
|
+
const [key, ...valueParts] = line.split("=");
|
|
817
|
+
if (key && valueParts.length > 0) {
|
|
818
|
+
keysData[key] = valueParts.join("=");
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
anthropicKey: keysData["ANTHROPIC_API_KEY"] || "",
|
|
824
|
+
openaiKey: keysData["OPENAI_API_KEY"] || "",
|
|
825
|
+
googleKey: keysData["GOOGLE_API_KEY"] || "",
|
|
826
|
+
vmRam: configData.vmRam || 4,
|
|
827
|
+
vmCpus: configData.vmCpus || 2,
|
|
828
|
+
vmDisk: configData.vmDisk || 10,
|
|
829
|
+
enableTunnel: configData.enableTunnel !== false
|
|
830
|
+
};
|
|
831
|
+
} catch {
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
802
835
|
async function promptInitConfig() {
|
|
803
836
|
console.log(chalk4.bold("\n API Keys\n"));
|
|
804
837
|
const anthropicKey = await password({
|
|
@@ -892,10 +925,10 @@ async function promptInitConfig() {
|
|
|
892
925
|
}
|
|
893
926
|
|
|
894
927
|
// src/core/platform.ts
|
|
895
|
-
import
|
|
928
|
+
import os2 from "os";
|
|
896
929
|
function detectPlatform() {
|
|
897
|
-
const platform =
|
|
898
|
-
const arch =
|
|
930
|
+
const platform = os2.platform();
|
|
931
|
+
const arch = os2.arch();
|
|
899
932
|
if (platform === "darwin") {
|
|
900
933
|
return {
|
|
901
934
|
os: "mac",
|
|
@@ -937,12 +970,12 @@ init_ssh();
|
|
|
937
970
|
// src/core/cloudinit.ts
|
|
938
971
|
init_secrets();
|
|
939
972
|
init_dlp();
|
|
940
|
-
import
|
|
941
|
-
import
|
|
973
|
+
import fs6 from "fs";
|
|
974
|
+
import path6 from "path";
|
|
942
975
|
import ejs from "ejs";
|
|
943
976
|
import { execa as execa3 } from "execa";
|
|
944
|
-
var CONFIGS_DIR =
|
|
945
|
-
var IMAGES_DIR2 =
|
|
977
|
+
var CONFIGS_DIR = path6.join(NEXUS_HOME2, "vm", "configs");
|
|
978
|
+
var IMAGES_DIR2 = path6.join(NEXUS_HOME2, "vm", "images");
|
|
946
979
|
async function renderCloudInit(data, templateContent) {
|
|
947
980
|
const trimmedPubKey = data.sshPubKey.trim();
|
|
948
981
|
if (!/^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp\d+) [A-Za-z0-9+/=]+ ?\S*$/.test(trimmedPubKey)) {
|
|
@@ -954,15 +987,15 @@ async function renderCloudInit(data, templateContent) {
|
|
|
954
987
|
}
|
|
955
988
|
const safeData = { ...data, sshPubKey: yamlEscape(trimmedPubKey), keys: safeKeys };
|
|
956
989
|
const rendered = ejs.render(templateContent, safeData);
|
|
957
|
-
const outputPath =
|
|
958
|
-
|
|
990
|
+
const outputPath = path6.join(CONFIGS_DIR, "user-data.yaml");
|
|
991
|
+
fs6.writeFileSync(outputPath, rendered, { mode: 384 });
|
|
959
992
|
audit("cloudinit_rendered", "user-data.yaml written");
|
|
960
993
|
return outputPath;
|
|
961
994
|
}
|
|
962
995
|
async function createCloudInitIso(userDataPath) {
|
|
963
|
-
const metaDataPath =
|
|
964
|
-
|
|
965
|
-
const isoPath =
|
|
996
|
+
const metaDataPath = path6.join(CONFIGS_DIR, "meta-data.yaml");
|
|
997
|
+
fs6.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
|
|
998
|
+
const isoPath = path6.join(IMAGES_DIR2, "init.iso");
|
|
966
999
|
const env = scrubEnv();
|
|
967
1000
|
try {
|
|
968
1001
|
let created = false;
|
|
@@ -985,10 +1018,10 @@ async function createCloudInitIso(userDataPath) {
|
|
|
985
1018
|
}
|
|
986
1019
|
if (!created) {
|
|
987
1020
|
try {
|
|
988
|
-
const stagingDir =
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1021
|
+
const stagingDir = path6.join(CONFIGS_DIR, "cidata-staging");
|
|
1022
|
+
fs6.mkdirSync(stagingDir, { recursive: true, mode: 448 });
|
|
1023
|
+
fs6.copyFileSync(userDataPath, path6.join(stagingDir, "user-data"));
|
|
1024
|
+
fs6.copyFileSync(metaDataPath, path6.join(stagingDir, "meta-data"));
|
|
992
1025
|
await execa3("hdiutil", [
|
|
993
1026
|
"makehybrid",
|
|
994
1027
|
"-o",
|
|
@@ -999,7 +1032,7 @@ async function createCloudInitIso(userDataPath) {
|
|
|
999
1032
|
"cidata",
|
|
1000
1033
|
stagingDir
|
|
1001
1034
|
], { env });
|
|
1002
|
-
|
|
1035
|
+
fs6.rmSync(stagingDir, { recursive: true, force: true });
|
|
1003
1036
|
created = true;
|
|
1004
1037
|
} catch {
|
|
1005
1038
|
}
|
|
@@ -1009,16 +1042,16 @@ async function createCloudInitIso(userDataPath) {
|
|
|
1009
1042
|
"Cannot create cloud-init ISO: none of mkisofs, genisoimage, or hdiutil are available. On macOS, install cdrtools: brew install cdrtools. On Linux: sudo apt install genisoimage"
|
|
1010
1043
|
);
|
|
1011
1044
|
}
|
|
1012
|
-
|
|
1045
|
+
fs6.chmodSync(isoPath, 384);
|
|
1013
1046
|
audit("cloudinit_iso_created", "init.iso created");
|
|
1014
1047
|
return isoPath;
|
|
1015
1048
|
} finally {
|
|
1016
1049
|
try {
|
|
1017
|
-
|
|
1050
|
+
fs6.unlinkSync(userDataPath);
|
|
1018
1051
|
} catch {
|
|
1019
1052
|
}
|
|
1020
1053
|
try {
|
|
1021
|
-
|
|
1054
|
+
fs6.unlinkSync(metaDataPath);
|
|
1022
1055
|
} catch {
|
|
1023
1056
|
}
|
|
1024
1057
|
audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
|
|
@@ -1199,25 +1232,25 @@ async function stopTunnel(sshPort) {
|
|
|
1199
1232
|
// src/commands/init.ts
|
|
1200
1233
|
init_dlp();
|
|
1201
1234
|
init_ssh();
|
|
1202
|
-
import
|
|
1203
|
-
import
|
|
1204
|
-
import
|
|
1235
|
+
import fs7 from "fs";
|
|
1236
|
+
import path7 from "path";
|
|
1237
|
+
import os3 from "os";
|
|
1205
1238
|
import crypto4 from "crypto";
|
|
1206
1239
|
import { fileURLToPath } from "url";
|
|
1207
1240
|
function getReleaseTarball() {
|
|
1208
|
-
const dir =
|
|
1209
|
-
const tarballPath =
|
|
1210
|
-
if (
|
|
1211
|
-
const rootPath =
|
|
1212
|
-
if (
|
|
1241
|
+
const dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
1242
|
+
const tarballPath = path7.join(dir, "nexus-release.tar.gz");
|
|
1243
|
+
if (fs7.existsSync(tarballPath)) return tarballPath;
|
|
1244
|
+
const rootPath = path7.resolve(dir, "..", "dist", "nexus-release.tar.gz");
|
|
1245
|
+
if (fs7.existsSync(rootPath)) return rootPath;
|
|
1213
1246
|
throw new Error("nexus-release.tar.gz not found. Run: npm run bundle");
|
|
1214
1247
|
}
|
|
1215
1248
|
function getCloudInitTemplate() {
|
|
1216
|
-
const dir =
|
|
1217
|
-
const templatePath =
|
|
1218
|
-
if (
|
|
1219
|
-
const srcPath =
|
|
1220
|
-
return
|
|
1249
|
+
const dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
1250
|
+
const templatePath = path7.join(dir, "templates", "cloud-init.yaml.ejs");
|
|
1251
|
+
if (fs7.existsSync(templatePath)) return fs7.readFileSync(templatePath, "utf-8");
|
|
1252
|
+
const srcPath = path7.resolve(dir, "..", "src", "templates", "cloud-init.yaml.ejs");
|
|
1253
|
+
return fs7.readFileSync(srcPath, "utf-8");
|
|
1221
1254
|
}
|
|
1222
1255
|
async function withSpinner(spinner, label, fn) {
|
|
1223
1256
|
spinner.text = label;
|
|
@@ -1235,7 +1268,11 @@ var phases = [
|
|
|
1235
1268
|
const platform = detectPlatform();
|
|
1236
1269
|
log.detail("Platform", `${platform.os} ${platform.arch}`);
|
|
1237
1270
|
log.detail("QEMU", platform.qemuBinary);
|
|
1238
|
-
const
|
|
1271
|
+
const isNonInteractive = process.env.BUILDWITHNEXUS_NONINTERACTIVE === "1" || process.env.CI === "true";
|
|
1272
|
+
let userConfig = isNonInteractive ? loadInitConfigFromDisk() : null;
|
|
1273
|
+
if (!userConfig) {
|
|
1274
|
+
userConfig = await promptInitConfig();
|
|
1275
|
+
}
|
|
1239
1276
|
ensureHome();
|
|
1240
1277
|
const masterSecret = generateMasterSecret();
|
|
1241
1278
|
const config = {
|
|
@@ -1371,15 +1408,15 @@ var phases = [
|
|
|
1371
1408
|
);
|
|
1372
1409
|
await withSpinner(spinner, "Staging API keys...", async () => {
|
|
1373
1410
|
const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
1374
|
-
const tmpKeysPath =
|
|
1375
|
-
|
|
1411
|
+
const tmpKeysPath = path7.join(os3.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
|
|
1412
|
+
fs7.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
|
|
1376
1413
|
try {
|
|
1377
1414
|
await sshUploadFile(config.sshPort, tmpKeysPath, "/tmp/.nexus-env-keys");
|
|
1378
1415
|
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1379
1416
|
} finally {
|
|
1380
1417
|
try {
|
|
1381
|
-
|
|
1382
|
-
|
|
1418
|
+
fs7.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
1419
|
+
fs7.unlinkSync(tmpKeysPath);
|
|
1383
1420
|
} catch {
|
|
1384
1421
|
}
|
|
1385
1422
|
}
|
|
@@ -1500,7 +1537,7 @@ init_secrets();
|
|
|
1500
1537
|
init_qemu();
|
|
1501
1538
|
init_ssh();
|
|
1502
1539
|
init_secrets();
|
|
1503
|
-
import
|
|
1540
|
+
import path8 from "path";
|
|
1504
1541
|
var startCommand = new Command2("start").description("Start the NEXUS runtime").action(async () => {
|
|
1505
1542
|
const config = loadConfig();
|
|
1506
1543
|
if (!config) {
|
|
@@ -1512,8 +1549,8 @@ var startCommand = new Command2("start").description("Start the NEXUS runtime").
|
|
|
1512
1549
|
return;
|
|
1513
1550
|
}
|
|
1514
1551
|
const platform = detectPlatform();
|
|
1515
|
-
const diskPath =
|
|
1516
|
-
const isoPath =
|
|
1552
|
+
const diskPath = path8.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2");
|
|
1553
|
+
const isoPath = path8.join(NEXUS_HOME2, "vm", "images", "init.iso");
|
|
1517
1554
|
let spinner = createSpinner("Starting VM...");
|
|
1518
1555
|
spinner.start();
|
|
1519
1556
|
await launchVm(platform, diskPath, isoPath, config.vmRam, config.vmCpus, {
|
|
@@ -1629,10 +1666,10 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1629
1666
|
// src/commands/doctor.ts
|
|
1630
1667
|
import { Command as Command5 } from "commander";
|
|
1631
1668
|
import chalk8 from "chalk";
|
|
1632
|
-
import
|
|
1669
|
+
import fs8 from "fs";
|
|
1633
1670
|
init_qemu();
|
|
1634
1671
|
init_secrets();
|
|
1635
|
-
import
|
|
1672
|
+
import path9 from "path";
|
|
1636
1673
|
import { execa as execa4 } from "execa";
|
|
1637
1674
|
var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
1638
1675
|
const platform = detectPlatform();
|
|
@@ -1662,11 +1699,11 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1662
1699
|
} catch {
|
|
1663
1700
|
}
|
|
1664
1701
|
console.log(` ${check(isoTool)} ISO tool (mkisofs/genisoimage)`);
|
|
1665
|
-
const keyExists =
|
|
1702
|
+
const keyExists = fs8.existsSync(path9.join(NEXUS_HOME2, "ssh", "id_nexus_vm"));
|
|
1666
1703
|
console.log(` ${check(keyExists)} SSH key`);
|
|
1667
1704
|
const config = loadConfig();
|
|
1668
1705
|
console.log(` ${check(!!config)} Configuration`);
|
|
1669
|
-
const diskExists =
|
|
1706
|
+
const diskExists = fs8.existsSync(path9.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2"));
|
|
1670
1707
|
console.log(` ${check(diskExists)} VM disk image`);
|
|
1671
1708
|
if (config) {
|
|
1672
1709
|
for (const [name, port] of [["SSH", config.sshPort], ["HTTP", config.httpPort], ["HTTPS", config.httpsPort]]) {
|
|
@@ -1687,7 +1724,7 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1687
1724
|
}
|
|
1688
1725
|
}
|
|
1689
1726
|
}
|
|
1690
|
-
const biosOk =
|
|
1727
|
+
const biosOk = fs8.existsSync(platform.biosPath);
|
|
1691
1728
|
console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" : chalk8.dim(platform.biosPath)}`);
|
|
1692
1729
|
console.log("");
|
|
1693
1730
|
if (qemuOk && isoTool && biosOk) {
|
|
@@ -1738,18 +1775,18 @@ var logsCommand = new Command6("logs").description("View NEXUS server logs").opt
|
|
|
1738
1775
|
|
|
1739
1776
|
// src/commands/update.ts
|
|
1740
1777
|
import { Command as Command7 } from "commander";
|
|
1741
|
-
import
|
|
1742
|
-
import
|
|
1778
|
+
import path10 from "path";
|
|
1779
|
+
import fs9 from "fs";
|
|
1743
1780
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1744
1781
|
init_secrets();
|
|
1745
1782
|
init_qemu();
|
|
1746
1783
|
init_ssh();
|
|
1747
1784
|
function getReleaseTarball2() {
|
|
1748
|
-
const dir =
|
|
1749
|
-
const tarballPath =
|
|
1750
|
-
if (
|
|
1751
|
-
const rootPath =
|
|
1752
|
-
if (
|
|
1785
|
+
const dir = path10.dirname(fileURLToPath2(import.meta.url));
|
|
1786
|
+
const tarballPath = path10.join(dir, "nexus-release.tar.gz");
|
|
1787
|
+
if (fs9.existsSync(tarballPath)) return tarballPath;
|
|
1788
|
+
const rootPath = path10.resolve(dir, "..", "dist", "nexus-release.tar.gz");
|
|
1789
|
+
if (fs9.existsSync(rootPath)) return rootPath;
|
|
1753
1790
|
throw new Error("nexus-release.tar.gz not found. Reinstall buildwithnexus to get the latest release.");
|
|
1754
1791
|
}
|
|
1755
1792
|
var updateCommand = new Command7("update").description("Update NEXUS to the latest bundled release and restart").action(async () => {
|
|
@@ -1800,11 +1837,11 @@ var updateCommand = new Command7("update").description("Update NEXUS to the late
|
|
|
1800
1837
|
// src/commands/destroy.ts
|
|
1801
1838
|
import { Command as Command8 } from "commander";
|
|
1802
1839
|
import chalk9 from "chalk";
|
|
1803
|
-
import
|
|
1840
|
+
import fs10 from "fs";
|
|
1804
1841
|
import { input as input2 } from "@inquirer/prompts";
|
|
1805
1842
|
init_secrets();
|
|
1806
1843
|
init_qemu();
|
|
1807
|
-
import
|
|
1844
|
+
import path11 from "path";
|
|
1808
1845
|
var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
|
|
1809
1846
|
const config = loadConfig();
|
|
1810
1847
|
if (!opts.force) {
|
|
@@ -1832,9 +1869,9 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1832
1869
|
}
|
|
1833
1870
|
stopVm();
|
|
1834
1871
|
}
|
|
1835
|
-
const sshConfigPath =
|
|
1836
|
-
if (
|
|
1837
|
-
const content =
|
|
1872
|
+
const sshConfigPath = path11.join(process.env.HOME || "~", ".ssh", "config");
|
|
1873
|
+
if (fs10.existsSync(sshConfigPath)) {
|
|
1874
|
+
const content = fs10.readFileSync(sshConfigPath, "utf-8");
|
|
1838
1875
|
const lines = content.split("\n");
|
|
1839
1876
|
const filtered = [];
|
|
1840
1877
|
let skip = false;
|
|
@@ -1847,9 +1884,9 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1847
1884
|
skip = false;
|
|
1848
1885
|
filtered.push(line);
|
|
1849
1886
|
}
|
|
1850
|
-
|
|
1887
|
+
fs10.writeFileSync(sshConfigPath, filtered.join("\n"));
|
|
1851
1888
|
}
|
|
1852
|
-
|
|
1889
|
+
fs10.rmSync(NEXUS_HOME2, { recursive: true, force: true });
|
|
1853
1890
|
succeed(spinner, "NEXUS runtime destroyed");
|
|
1854
1891
|
log.dim("Run 'buildwithnexus init' to set up again");
|
|
1855
1892
|
});
|
|
@@ -2262,10 +2299,10 @@ init_dlp();
|
|
|
2262
2299
|
// src/ui/repl.ts
|
|
2263
2300
|
init_secrets();
|
|
2264
2301
|
import readline from "readline";
|
|
2265
|
-
import
|
|
2266
|
-
import
|
|
2302
|
+
import fs11 from "fs";
|
|
2303
|
+
import path12 from "path";
|
|
2267
2304
|
import chalk13 from "chalk";
|
|
2268
|
-
var HISTORY_FILE =
|
|
2305
|
+
var HISTORY_FILE = path12.join(NEXUS_HOME2, "shell_history");
|
|
2269
2306
|
var MAX_HISTORY = 1e3;
|
|
2270
2307
|
var Repl = class {
|
|
2271
2308
|
rl = null;
|
|
@@ -2281,17 +2318,17 @@ var Repl = class {
|
|
|
2281
2318
|
}
|
|
2282
2319
|
loadHistory() {
|
|
2283
2320
|
try {
|
|
2284
|
-
if (
|
|
2285
|
-
this.history =
|
|
2321
|
+
if (fs11.existsSync(HISTORY_FILE)) {
|
|
2322
|
+
this.history = fs11.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
2286
2323
|
}
|
|
2287
2324
|
} catch {
|
|
2288
2325
|
}
|
|
2289
2326
|
}
|
|
2290
2327
|
saveHistory() {
|
|
2291
2328
|
try {
|
|
2292
|
-
const dir =
|
|
2293
|
-
|
|
2294
|
-
|
|
2329
|
+
const dir = path12.dirname(HISTORY_FILE);
|
|
2330
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
2331
|
+
fs11.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
|
|
2295
2332
|
} catch {
|
|
2296
2333
|
}
|
|
2297
2334
|
}
|
|
@@ -2906,7 +2943,7 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2906
2943
|
});
|
|
2907
2944
|
|
|
2908
2945
|
// src/cli.ts
|
|
2909
|
-
var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.5.
|
|
2946
|
+
var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.5.4");
|
|
2910
2947
|
cli.addCommand(initCommand);
|
|
2911
2948
|
cli.addCommand(startCommand);
|
|
2912
2949
|
cli.addCommand(stopCommand);
|
|
@@ -2935,18 +2972,18 @@ cli.action(async () => {
|
|
|
2935
2972
|
});
|
|
2936
2973
|
|
|
2937
2974
|
// src/core/update-notifier.ts
|
|
2938
|
-
import
|
|
2939
|
-
import
|
|
2940
|
-
import
|
|
2975
|
+
import fs12 from "fs";
|
|
2976
|
+
import path13 from "path";
|
|
2977
|
+
import os4 from "os";
|
|
2941
2978
|
import https from "https";
|
|
2942
2979
|
import chalk16 from "chalk";
|
|
2943
2980
|
var PACKAGE_NAME = "buildwithnexus";
|
|
2944
2981
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
2945
|
-
var STATE_DIR =
|
|
2946
|
-
var STATE_FILE =
|
|
2982
|
+
var STATE_DIR = path13.join(os4.homedir(), ".buildwithnexus");
|
|
2983
|
+
var STATE_FILE = path13.join(STATE_DIR, ".update-check.json");
|
|
2947
2984
|
function readState() {
|
|
2948
2985
|
try {
|
|
2949
|
-
const raw =
|
|
2986
|
+
const raw = fs12.readFileSync(STATE_FILE, "utf-8");
|
|
2950
2987
|
return JSON.parse(raw);
|
|
2951
2988
|
} catch {
|
|
2952
2989
|
return { lastCheck: 0, latestVersion: null };
|
|
@@ -2954,8 +2991,8 @@ function readState() {
|
|
|
2954
2991
|
}
|
|
2955
2992
|
function writeState(state) {
|
|
2956
2993
|
try {
|
|
2957
|
-
|
|
2958
|
-
|
|
2994
|
+
fs12.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
|
|
2995
|
+
fs12.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
|
|
2959
2996
|
} catch {
|
|
2960
2997
|
}
|
|
2961
2998
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buildwithnexus",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Launch an autonomous AI runtime with triple-nested VM isolation in one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "vitest run",
|
|
20
20
|
"test:watch": "vitest",
|
|
21
21
|
"test:coverage": "vitest run --coverage",
|
|
22
|
-
"prepublishOnly": "npm run build"
|
|
22
|
+
"prepublishOnly": "npm run build && npm run bundle"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=18"
|