buildwithnexus 0.5.13 → 0.5.15
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 +183 -232
- package/dist/nexus-release.tar.gz +0 -0
- package/dist/templates/cloud-init.yaml.ejs +2 -5
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -23,12 +23,12 @@ import { dirname, join } from "path";
|
|
|
23
23
|
import { fileURLToPath } from "url";
|
|
24
24
|
function getVersion() {
|
|
25
25
|
try {
|
|
26
|
-
const
|
|
27
|
-
const packagePath = join(
|
|
28
|
-
const
|
|
29
|
-
return
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const packagePath = join(__dirname, "..", "..", "package.json");
|
|
28
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
29
|
+
return packageJson.version;
|
|
30
30
|
} catch (e) {
|
|
31
|
-
return "0.5.
|
|
31
|
+
return "0.5.15";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
function showBanner() {
|
|
@@ -296,26 +296,26 @@ __export(secrets_exports, {
|
|
|
296
296
|
saveConfig: () => saveConfig,
|
|
297
297
|
saveKeys: () => saveKeys
|
|
298
298
|
});
|
|
299
|
-
import
|
|
300
|
-
import
|
|
299
|
+
import fs2 from "fs";
|
|
300
|
+
import path2 from "path";
|
|
301
301
|
import crypto2 from "crypto";
|
|
302
302
|
function ensureHome() {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
303
|
+
fs2.mkdirSync(NEXUS_HOME2, { recursive: true, mode: 448 });
|
|
304
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "images"), { recursive: true, mode: 448 });
|
|
305
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "configs"), { recursive: true, mode: 448 });
|
|
306
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "vm", "logs"), { recursive: true, mode: 448 });
|
|
307
|
+
fs2.mkdirSync(path2.join(NEXUS_HOME2, "ssh"), { recursive: true, mode: 448 });
|
|
308
308
|
}
|
|
309
309
|
function generateMasterSecret() {
|
|
310
310
|
return crypto2.randomBytes(32).toString("base64url");
|
|
311
311
|
}
|
|
312
312
|
function saveConfig(config) {
|
|
313
313
|
const { masterSecret: _secret, ...safeConfig } = config;
|
|
314
|
-
|
|
314
|
+
fs2.writeFileSync(CONFIG_PATH, JSON.stringify(safeConfig, null, 2), { mode: 384 });
|
|
315
315
|
}
|
|
316
316
|
function loadConfig() {
|
|
317
|
-
if (!
|
|
318
|
-
return JSON.parse(
|
|
317
|
+
if (!fs2.existsSync(CONFIG_PATH)) return null;
|
|
318
|
+
return JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf-8"));
|
|
319
319
|
}
|
|
320
320
|
function saveKeys(keys) {
|
|
321
321
|
const violations = validateAllKeys(keys);
|
|
@@ -323,13 +323,13 @@ function saveKeys(keys) {
|
|
|
323
323
|
throw new DlpViolation(`Key validation failed: ${violations.join("; ")}`);
|
|
324
324
|
}
|
|
325
325
|
const lines = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`);
|
|
326
|
-
|
|
326
|
+
fs2.writeFileSync(KEYS_PATH, lines.join("\n") + "\n", { mode: 384 });
|
|
327
327
|
sealKeysFile(KEYS_PATH, keys.NEXUS_MASTER_SECRET);
|
|
328
328
|
audit("keys_saved", `${Object.keys(keys).filter((k) => keys[k]).length} keys saved`);
|
|
329
329
|
}
|
|
330
330
|
function loadKeys() {
|
|
331
|
-
if (!
|
|
332
|
-
const content =
|
|
331
|
+
if (!fs2.existsSync(KEYS_PATH)) return null;
|
|
332
|
+
const content = fs2.readFileSync(KEYS_PATH, "utf-8");
|
|
333
333
|
const keys = {};
|
|
334
334
|
for (const line of content.split("\n")) {
|
|
335
335
|
const eq = line.indexOf("=");
|
|
@@ -355,9 +355,9 @@ var init_secrets = __esm({
|
|
|
355
355
|
"src/core/secrets.ts"() {
|
|
356
356
|
"use strict";
|
|
357
357
|
init_dlp();
|
|
358
|
-
NEXUS_HOME2 = process.env.NEXUS_HOME ||
|
|
359
|
-
CONFIG_PATH = process.env.NEXUS_CONFIG_PATH ||
|
|
360
|
-
KEYS_PATH = process.env.NEXUS_KEYS_PATH ||
|
|
358
|
+
NEXUS_HOME2 = process.env.NEXUS_HOME || path2.join(process.env.HOME || "~", ".buildwithnexus");
|
|
359
|
+
CONFIG_PATH = process.env.NEXUS_CONFIG_PATH || path2.join(NEXUS_HOME2, "config.json");
|
|
360
|
+
KEYS_PATH = process.env.NEXUS_KEYS_PATH || path2.join(NEXUS_HOME2, ".env.keys");
|
|
361
361
|
}
|
|
362
362
|
});
|
|
363
363
|
|
|
@@ -374,9 +374,9 @@ __export(qemu_exports, {
|
|
|
374
374
|
resolvePortConflicts: () => resolvePortConflicts,
|
|
375
375
|
stopVm: () => stopVm
|
|
376
376
|
});
|
|
377
|
-
import
|
|
377
|
+
import fs3 from "fs";
|
|
378
378
|
import net from "net";
|
|
379
|
-
import
|
|
379
|
+
import path3 from "path";
|
|
380
380
|
import { execa, execaSync } from "execa";
|
|
381
381
|
import { select } from "@inquirer/prompts";
|
|
382
382
|
import chalk5 from "chalk";
|
|
@@ -409,15 +409,15 @@ async function installQemu(platform) {
|
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
async function downloadImage(platform) {
|
|
412
|
-
const imagePath =
|
|
413
|
-
if (
|
|
412
|
+
const imagePath = path3.join(IMAGES_DIR, platform.ubuntuImage);
|
|
413
|
+
if (fs3.existsSync(imagePath)) return imagePath;
|
|
414
414
|
const url = `${UBUNTU_BASE_URL}/${platform.ubuntuImage}`;
|
|
415
415
|
await execa("curl", ["-L", "-C", "-", "-o", imagePath, "--progress-bar", url], { stdio: "inherit", env: scrubEnv() });
|
|
416
416
|
return imagePath;
|
|
417
417
|
}
|
|
418
418
|
async function createDisk(basePath, sizeGb) {
|
|
419
|
-
const diskPath =
|
|
420
|
-
if (
|
|
419
|
+
const diskPath = path3.join(IMAGES_DIR, "nexus-vm-disk.qcow2");
|
|
420
|
+
if (fs3.existsSync(diskPath)) return diskPath;
|
|
421
421
|
await execa("qemu-img", ["create", "-f", "qcow2", "-b", basePath, "-F", "qcow2", diskPath, `${sizeGb}G`], { env: scrubEnv() });
|
|
422
422
|
return diskPath;
|
|
423
423
|
}
|
|
@@ -511,36 +511,31 @@ async function resolvePortConflicts(ports) {
|
|
|
511
511
|
}
|
|
512
512
|
async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
513
513
|
const machineArgs = platform.os === "mac" ? ["-machine", "virt,gic-version=3"] : ["-machine", "pc"];
|
|
514
|
-
const biosArgs =
|
|
515
|
-
const buildArgs = (cpuArgs) =>
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
"-pidfile",
|
|
540
|
-
PID_FILE,
|
|
541
|
-
"-daemonize"
|
|
542
|
-
];
|
|
543
|
-
};
|
|
514
|
+
const biosArgs = fs3.existsSync(platform.biosPath) ? ["-bios", platform.biosPath] : [];
|
|
515
|
+
const buildArgs = (cpuArgs) => [
|
|
516
|
+
...machineArgs,
|
|
517
|
+
...cpuArgs,
|
|
518
|
+
"-m",
|
|
519
|
+
`${ram}G`,
|
|
520
|
+
"-smp",
|
|
521
|
+
`${cpus}`,
|
|
522
|
+
"-drive",
|
|
523
|
+
`file=${diskPath},if=virtio,cache=writethrough`,
|
|
524
|
+
"-drive",
|
|
525
|
+
`file=${initIsoPath},if=virtio,format=raw,cache=writethrough`,
|
|
526
|
+
"-display",
|
|
527
|
+
"none",
|
|
528
|
+
"-serial",
|
|
529
|
+
"none",
|
|
530
|
+
"-net",
|
|
531
|
+
"nic,model=virtio",
|
|
532
|
+
"-net",
|
|
533
|
+
`user,hostfwd=tcp::${ports.ssh}-:22,hostfwd=tcp::${ports.http}-:4200,hostfwd=tcp::${ports.https}-:443`,
|
|
534
|
+
...biosArgs,
|
|
535
|
+
"-pidfile",
|
|
536
|
+
PID_FILE,
|
|
537
|
+
"-daemonize"
|
|
538
|
+
];
|
|
544
539
|
try {
|
|
545
540
|
await execa(platform.qemuBinary, buildArgs(platform.qemuCpuFlag.split(" ")), { env: scrubEnv() });
|
|
546
541
|
} catch {
|
|
@@ -550,8 +545,8 @@ async function launchVm(platform, diskPath, initIsoPath, ram, cpus, ports) {
|
|
|
550
545
|
return ports;
|
|
551
546
|
}
|
|
552
547
|
function readValidPid() {
|
|
553
|
-
if (!
|
|
554
|
-
const raw =
|
|
548
|
+
if (!fs3.existsSync(PID_FILE)) return null;
|
|
549
|
+
const raw = fs3.readFileSync(PID_FILE, "utf-8").trim();
|
|
555
550
|
const pid = parseInt(raw, 10);
|
|
556
551
|
if (!Number.isInteger(pid) || pid <= 1 || pid > 4194304) return null;
|
|
557
552
|
return pid;
|
|
@@ -579,7 +574,7 @@ function stopVm() {
|
|
|
579
574
|
if (!pid) return;
|
|
580
575
|
if (!isQemuPid(pid)) {
|
|
581
576
|
try {
|
|
582
|
-
|
|
577
|
+
fs3.unlinkSync(PID_FILE);
|
|
583
578
|
} catch {
|
|
584
579
|
}
|
|
585
580
|
return;
|
|
@@ -589,7 +584,7 @@ function stopVm() {
|
|
|
589
584
|
} catch {
|
|
590
585
|
}
|
|
591
586
|
try {
|
|
592
|
-
|
|
587
|
+
fs3.unlinkSync(PID_FILE);
|
|
593
588
|
} catch {
|
|
594
589
|
}
|
|
595
590
|
}
|
|
@@ -602,9 +597,9 @@ var init_qemu = __esm({
|
|
|
602
597
|
"use strict";
|
|
603
598
|
init_secrets();
|
|
604
599
|
init_dlp();
|
|
605
|
-
VM_DIR =
|
|
606
|
-
IMAGES_DIR =
|
|
607
|
-
PID_FILE =
|
|
600
|
+
VM_DIR = path3.join(NEXUS_HOME2, "vm");
|
|
601
|
+
IMAGES_DIR = path3.join(VM_DIR, "images");
|
|
602
|
+
PID_FILE = path3.join(VM_DIR, "qemu.pid");
|
|
608
603
|
UBUNTU_BASE_URL = "https://cloud-images.ubuntu.com/jammy/current";
|
|
609
604
|
}
|
|
610
605
|
});
|
|
@@ -621,21 +616,21 @@ __export(ssh_exports, {
|
|
|
621
616
|
sshUploadFile: () => sshUploadFile,
|
|
622
617
|
waitForSsh: () => waitForSsh
|
|
623
618
|
});
|
|
624
|
-
import
|
|
625
|
-
import
|
|
619
|
+
import fs4 from "fs";
|
|
620
|
+
import path4 from "path";
|
|
626
621
|
import crypto3 from "crypto";
|
|
627
622
|
import { execa as execa2 } from "execa";
|
|
628
623
|
import { NodeSSH } from "node-ssh";
|
|
629
624
|
function getHostVerifier() {
|
|
630
|
-
if (!
|
|
625
|
+
if (!fs4.existsSync(PINNED_HOST_KEY)) {
|
|
631
626
|
return (key) => {
|
|
632
627
|
const fp = crypto3.createHash("sha256").update(key).digest("base64");
|
|
633
|
-
|
|
628
|
+
fs4.writeFileSync(PINNED_HOST_KEY, fp, { mode: 384 });
|
|
634
629
|
audit("ssh_exec", `host key pinned: SHA256:${fp}`);
|
|
635
630
|
return true;
|
|
636
631
|
};
|
|
637
632
|
}
|
|
638
|
-
const pinned =
|
|
633
|
+
const pinned = fs4.readFileSync(PINNED_HOST_KEY, "utf-8").trim();
|
|
639
634
|
return (key) => {
|
|
640
635
|
const fp = crypto3.createHash("sha256").update(key).digest("base64");
|
|
641
636
|
const match = fp === pinned;
|
|
@@ -647,11 +642,11 @@ function getKeyPath() {
|
|
|
647
642
|
return SSH_KEY;
|
|
648
643
|
}
|
|
649
644
|
function getPubKey() {
|
|
650
|
-
return
|
|
645
|
+
return fs4.readFileSync(SSH_PUB_KEY, "utf-8").trim();
|
|
651
646
|
}
|
|
652
647
|
async function generateSshKey() {
|
|
653
|
-
if (
|
|
654
|
-
|
|
648
|
+
if (fs4.existsSync(SSH_KEY)) return;
|
|
649
|
+
fs4.mkdirSync(SSH_DIR, { recursive: true });
|
|
655
650
|
await execa2("ssh-keygen", [
|
|
656
651
|
"-t",
|
|
657
652
|
"ed25519",
|
|
@@ -663,13 +658,13 @@ async function generateSshKey() {
|
|
|
663
658
|
"buildwithnexus@localhost",
|
|
664
659
|
"-q"
|
|
665
660
|
], { env: scrubEnv() });
|
|
666
|
-
|
|
667
|
-
|
|
661
|
+
fs4.chmodSync(SSH_KEY, 384);
|
|
662
|
+
fs4.chmodSync(SSH_PUB_KEY, 420);
|
|
668
663
|
}
|
|
669
664
|
function addSshConfig(port) {
|
|
670
|
-
const sshConfigPath =
|
|
671
|
-
const sshDir =
|
|
672
|
-
|
|
665
|
+
const sshConfigPath = path4.join(process.env.HOME || "~", ".ssh", "config");
|
|
666
|
+
const sshDir = path4.dirname(sshConfigPath);
|
|
667
|
+
fs4.mkdirSync(sshDir, { recursive: true });
|
|
673
668
|
const block = [
|
|
674
669
|
"",
|
|
675
670
|
"Host nexus-vm",
|
|
@@ -682,21 +677,19 @@ function addSshConfig(port) {
|
|
|
682
677
|
" ServerAliveInterval 60",
|
|
683
678
|
""
|
|
684
679
|
].join("\n");
|
|
685
|
-
if (
|
|
686
|
-
const existing =
|
|
680
|
+
if (fs4.existsSync(sshConfigPath)) {
|
|
681
|
+
const existing = fs4.readFileSync(sshConfigPath, "utf-8");
|
|
687
682
|
if (existing.includes("Host nexus-vm")) return;
|
|
688
|
-
|
|
683
|
+
fs4.appendFileSync(sshConfigPath, block);
|
|
689
684
|
} else {
|
|
690
|
-
|
|
685
|
+
fs4.writeFileSync(sshConfigPath, block, { mode: 384 });
|
|
691
686
|
}
|
|
692
687
|
}
|
|
693
|
-
async function waitForSsh(port, timeoutMs =
|
|
688
|
+
async function waitForSsh(port, timeoutMs = 9e5) {
|
|
694
689
|
const start = Date.now();
|
|
695
|
-
let attempt = 0;
|
|
696
|
-
const backoffMs = (n) => Math.min(3e3 * Math.pow(2, n), 3e4);
|
|
697
690
|
while (Date.now() - start < timeoutMs) {
|
|
698
|
-
const ssh = new NodeSSH();
|
|
699
691
|
try {
|
|
692
|
+
const ssh = new NodeSSH();
|
|
700
693
|
await ssh.connect({
|
|
701
694
|
host: "localhost",
|
|
702
695
|
port,
|
|
@@ -708,14 +701,7 @@ async function waitForSsh(port, timeoutMs = 6e5) {
|
|
|
708
701
|
ssh.dispose();
|
|
709
702
|
return true;
|
|
710
703
|
} catch {
|
|
711
|
-
|
|
712
|
-
ssh.dispose();
|
|
713
|
-
} catch {
|
|
714
|
-
}
|
|
715
|
-
const delay = backoffMs(attempt++);
|
|
716
|
-
const remaining = timeoutMs - (Date.now() - start);
|
|
717
|
-
if (remaining <= 0) break;
|
|
718
|
-
await new Promise((r) => setTimeout(r, Math.min(delay, remaining)));
|
|
704
|
+
await new Promise((r) => setTimeout(r, 1e4));
|
|
719
705
|
}
|
|
720
706
|
}
|
|
721
707
|
return false;
|
|
@@ -728,6 +714,7 @@ async function sshExec(port, command) {
|
|
|
728
714
|
port,
|
|
729
715
|
username: "nexus",
|
|
730
716
|
privateKeyPath: SSH_KEY,
|
|
717
|
+
readyTimeout: 3e4,
|
|
731
718
|
hostVerifier: getHostVerifier()
|
|
732
719
|
});
|
|
733
720
|
const result = await ssh.execCommand(command);
|
|
@@ -755,19 +742,16 @@ var init_ssh = __esm({
|
|
|
755
742
|
"use strict";
|
|
756
743
|
init_secrets();
|
|
757
744
|
init_dlp();
|
|
758
|
-
SSH_DIR =
|
|
759
|
-
SSH_KEY =
|
|
760
|
-
SSH_PUB_KEY =
|
|
761
|
-
KNOWN_HOSTS =
|
|
762
|
-
PINNED_HOST_KEY =
|
|
745
|
+
SSH_DIR = path4.join(NEXUS_HOME2, "ssh");
|
|
746
|
+
SSH_KEY = path4.join(SSH_DIR, "id_nexus_vm");
|
|
747
|
+
SSH_PUB_KEY = path4.join(SSH_DIR, "id_nexus_vm.pub");
|
|
748
|
+
KNOWN_HOSTS = path4.join(SSH_DIR, "known_hosts_nexus_vm");
|
|
749
|
+
PINNED_HOST_KEY = path4.join(SSH_DIR, "vm_host_key.pin");
|
|
763
750
|
}
|
|
764
751
|
});
|
|
765
752
|
|
|
766
753
|
// src/cli.ts
|
|
767
754
|
import { Command as Command14 } from "commander";
|
|
768
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
769
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
770
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
771
755
|
|
|
772
756
|
// src/commands/init.ts
|
|
773
757
|
init_banner();
|
|
@@ -821,39 +805,6 @@ var log = {
|
|
|
821
805
|
init_dlp();
|
|
822
806
|
import { input, confirm, password } from "@inquirer/prompts";
|
|
823
807
|
import chalk4 from "chalk";
|
|
824
|
-
import fs2 from "fs";
|
|
825
|
-
import path2 from "path";
|
|
826
|
-
import os from "os";
|
|
827
|
-
function loadInitConfigFromDisk() {
|
|
828
|
-
const homeDir = os.homedir();
|
|
829
|
-
const configPath = path2.join(homeDir, ".buildwithnexus", "config.json");
|
|
830
|
-
const keysPath = path2.join(homeDir, ".buildwithnexus", ".env.keys");
|
|
831
|
-
if (!fs2.existsSync(configPath)) return null;
|
|
832
|
-
try {
|
|
833
|
-
const configData = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
834
|
-
const keysData = {};
|
|
835
|
-
if (fs2.existsSync(keysPath)) {
|
|
836
|
-
const keysContent = fs2.readFileSync(keysPath, "utf-8");
|
|
837
|
-
keysContent.split("\n").forEach((line) => {
|
|
838
|
-
const [key, ...valueParts] = line.split("=");
|
|
839
|
-
if (key && valueParts.length > 0) {
|
|
840
|
-
keysData[key] = valueParts.join("=");
|
|
841
|
-
}
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
return {
|
|
845
|
-
anthropicKey: keysData["ANTHROPIC_API_KEY"] || "",
|
|
846
|
-
openaiKey: keysData["OPENAI_API_KEY"] || "",
|
|
847
|
-
googleKey: keysData["GOOGLE_API_KEY"] || "",
|
|
848
|
-
vmRam: configData.vmRam || 4,
|
|
849
|
-
vmCpus: configData.vmCpus || 2,
|
|
850
|
-
vmDisk: configData.vmDisk || 10,
|
|
851
|
-
enableTunnel: configData.enableTunnel !== false
|
|
852
|
-
};
|
|
853
|
-
} catch {
|
|
854
|
-
return null;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
808
|
async function promptInitConfig() {
|
|
858
809
|
console.log(chalk4.bold("\n API Keys\n"));
|
|
859
810
|
const anthropicKey = await password({
|
|
@@ -947,10 +898,10 @@ async function promptInitConfig() {
|
|
|
947
898
|
}
|
|
948
899
|
|
|
949
900
|
// src/core/platform.ts
|
|
950
|
-
import
|
|
901
|
+
import os from "os";
|
|
951
902
|
function detectPlatform() {
|
|
952
|
-
const platform =
|
|
953
|
-
const arch =
|
|
903
|
+
const platform = os.platform();
|
|
904
|
+
const arch = os.arch();
|
|
954
905
|
if (platform === "darwin") {
|
|
955
906
|
return {
|
|
956
907
|
os: "mac",
|
|
@@ -992,12 +943,12 @@ init_ssh();
|
|
|
992
943
|
// src/core/cloudinit.ts
|
|
993
944
|
init_secrets();
|
|
994
945
|
init_dlp();
|
|
995
|
-
import
|
|
996
|
-
import
|
|
946
|
+
import fs5 from "fs";
|
|
947
|
+
import path5 from "path";
|
|
997
948
|
import ejs from "ejs";
|
|
998
949
|
import { execa as execa3 } from "execa";
|
|
999
|
-
var CONFIGS_DIR =
|
|
1000
|
-
var IMAGES_DIR2 =
|
|
950
|
+
var CONFIGS_DIR = path5.join(NEXUS_HOME2, "vm", "configs");
|
|
951
|
+
var IMAGES_DIR2 = path5.join(NEXUS_HOME2, "vm", "images");
|
|
1001
952
|
async function renderCloudInit(data, templateContent) {
|
|
1002
953
|
const trimmedPubKey = data.sshPubKey.trim();
|
|
1003
954
|
if (!/^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp\d+) [A-Za-z0-9+/=]+ ?\S*$/.test(trimmedPubKey)) {
|
|
@@ -1009,22 +960,17 @@ async function renderCloudInit(data, templateContent) {
|
|
|
1009
960
|
}
|
|
1010
961
|
const safeData = { ...data, sshPubKey: yamlEscape(trimmedPubKey), keys: safeKeys };
|
|
1011
962
|
const rendered = ejs.render(templateContent, safeData);
|
|
1012
|
-
const outputPath =
|
|
1013
|
-
|
|
963
|
+
const outputPath = path5.join(CONFIGS_DIR, "user-data.yaml");
|
|
964
|
+
fs5.writeFileSync(outputPath, rendered, { mode: 384 });
|
|
1014
965
|
audit("cloudinit_rendered", "user-data.yaml written");
|
|
1015
966
|
return outputPath;
|
|
1016
967
|
}
|
|
1017
968
|
async function createCloudInitIso(userDataPath) {
|
|
1018
|
-
const metaDataPath =
|
|
1019
|
-
|
|
1020
|
-
const isoPath =
|
|
969
|
+
const metaDataPath = path5.join(CONFIGS_DIR, "meta-data.yaml");
|
|
970
|
+
fs5.writeFileSync(metaDataPath, "instance-id: nexus-vm-1\nlocal-hostname: nexus-vm\n", { mode: 384 });
|
|
971
|
+
const isoPath = path5.join(IMAGES_DIR2, "init.iso");
|
|
1021
972
|
const env = scrubEnv();
|
|
1022
973
|
try {
|
|
1023
|
-
const stagingDir = path6.join(CONFIGS_DIR, "cidata-staging");
|
|
1024
|
-
fs6.rmSync(stagingDir, { recursive: true, force: true });
|
|
1025
|
-
fs6.mkdirSync(stagingDir, { recursive: true, mode: 448 });
|
|
1026
|
-
fs6.copyFileSync(userDataPath, path6.join(stagingDir, "user-data"));
|
|
1027
|
-
fs6.copyFileSync(metaDataPath, path6.join(stagingDir, "meta-data"));
|
|
1028
974
|
let created = false;
|
|
1029
975
|
for (const tool of ["mkisofs", "genisoimage"]) {
|
|
1030
976
|
if (created) break;
|
|
@@ -1036,7 +982,8 @@ async function createCloudInitIso(userDataPath) {
|
|
|
1036
982
|
"cidata",
|
|
1037
983
|
"-joliet",
|
|
1038
984
|
"-rock",
|
|
1039
|
-
|
|
985
|
+
userDataPath,
|
|
986
|
+
metaDataPath
|
|
1040
987
|
], { env });
|
|
1041
988
|
created = true;
|
|
1042
989
|
} catch {
|
|
@@ -1044,10 +991,10 @@ async function createCloudInitIso(userDataPath) {
|
|
|
1044
991
|
}
|
|
1045
992
|
if (!created) {
|
|
1046
993
|
try {
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
994
|
+
const stagingDir = path5.join(CONFIGS_DIR, "cidata-staging");
|
|
995
|
+
fs5.mkdirSync(stagingDir, { recursive: true, mode: 448 });
|
|
996
|
+
fs5.copyFileSync(userDataPath, path5.join(stagingDir, "user-data"));
|
|
997
|
+
fs5.copyFileSync(metaDataPath, path5.join(stagingDir, "meta-data"));
|
|
1051
998
|
await execa3("hdiutil", [
|
|
1052
999
|
"makehybrid",
|
|
1053
1000
|
"-o",
|
|
@@ -1056,9 +1003,9 @@ async function createCloudInitIso(userDataPath) {
|
|
|
1056
1003
|
"-iso",
|
|
1057
1004
|
"-default-volume-name",
|
|
1058
1005
|
"cidata",
|
|
1059
|
-
|
|
1006
|
+
stagingDir
|
|
1060
1007
|
], { env });
|
|
1061
|
-
|
|
1008
|
+
fs5.rmSync(stagingDir, { recursive: true, force: true });
|
|
1062
1009
|
created = true;
|
|
1063
1010
|
} catch {
|
|
1064
1011
|
}
|
|
@@ -1068,16 +1015,16 @@ async function createCloudInitIso(userDataPath) {
|
|
|
1068
1015
|
"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"
|
|
1069
1016
|
);
|
|
1070
1017
|
}
|
|
1071
|
-
|
|
1018
|
+
fs5.chmodSync(isoPath, 384);
|
|
1072
1019
|
audit("cloudinit_iso_created", "init.iso created");
|
|
1073
1020
|
return isoPath;
|
|
1074
1021
|
} finally {
|
|
1075
1022
|
try {
|
|
1076
|
-
|
|
1023
|
+
fs5.unlinkSync(userDataPath);
|
|
1077
1024
|
} catch {
|
|
1078
1025
|
}
|
|
1079
1026
|
try {
|
|
1080
|
-
|
|
1027
|
+
fs5.unlinkSync(metaDataPath);
|
|
1081
1028
|
} catch {
|
|
1082
1029
|
}
|
|
1083
1030
|
audit("cloudinit_plaintext_deleted", "user-data.yaml and meta-data.yaml removed");
|
|
@@ -1225,14 +1172,12 @@ async function installCloudflared(sshPort, arch) {
|
|
|
1225
1172
|
].join(" && "));
|
|
1226
1173
|
}
|
|
1227
1174
|
async function startTunnel(sshPort) {
|
|
1228
|
-
await sshExec(
|
|
1229
|
-
|
|
1230
|
-
"&& nohup cloudflared tunnel --no-autoupdate --url http://localhost:4200"
|
|
1231
|
-
|
|
1232
|
-
"disown"
|
|
1233
|
-
].join(" "));
|
|
1175
|
+
await sshExec(
|
|
1176
|
+
sshPort,
|
|
1177
|
+
"install -m 600 /dev/null /home/nexus/.nexus/tunnel.log && bash -c 'nohup cloudflared tunnel --no-autoupdate --url http://localhost:4200 > /home/nexus/.nexus/tunnel.log 2>&1 &'"
|
|
1178
|
+
);
|
|
1234
1179
|
const start = Date.now();
|
|
1235
|
-
while (Date.now() - start <
|
|
1180
|
+
while (Date.now() - start < 12e4) {
|
|
1236
1181
|
try {
|
|
1237
1182
|
const { stdout } = await sshExec(sshPort, "grep -oE 'https://[a-z0-9-]+\\.trycloudflare\\.com' /home/nexus/.nexus/tunnel.log 2>/dev/null | head -1");
|
|
1238
1183
|
if (stdout.includes("https://")) {
|
|
@@ -1258,25 +1203,32 @@ async function stopTunnel(sshPort) {
|
|
|
1258
1203
|
// src/commands/init.ts
|
|
1259
1204
|
init_dlp();
|
|
1260
1205
|
init_ssh();
|
|
1261
|
-
import
|
|
1262
|
-
import
|
|
1263
|
-
import
|
|
1206
|
+
import fs6 from "fs";
|
|
1207
|
+
import path6 from "path";
|
|
1208
|
+
import os2 from "os";
|
|
1264
1209
|
import crypto4 from "crypto";
|
|
1265
1210
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1266
1211
|
function getReleaseTarball() {
|
|
1267
|
-
const dir =
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1212
|
+
const dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1213
|
+
const possiblePaths = [
|
|
1214
|
+
// Current directory (dev)
|
|
1215
|
+
path6.join(dir, "nexus-release.tar.gz"),
|
|
1216
|
+
// dist folder (when built)
|
|
1217
|
+
path6.resolve(dir, "..", "dist", "nexus-release.tar.gz"),
|
|
1218
|
+
// node_modules location (when installed from npm)
|
|
1219
|
+
path6.resolve(dir, "..", "nexus-release.tar.gz")
|
|
1220
|
+
];
|
|
1221
|
+
for (const tarballPath of possiblePaths) {
|
|
1222
|
+
if (fs6.existsSync(tarballPath)) return tarballPath;
|
|
1223
|
+
}
|
|
1224
|
+
throw new Error("nexus-release.tar.gz not found. Run: npm install buildwithnexus@latest");
|
|
1273
1225
|
}
|
|
1274
1226
|
function getCloudInitTemplate() {
|
|
1275
|
-
const dir =
|
|
1276
|
-
const templatePath =
|
|
1277
|
-
if (
|
|
1278
|
-
const srcPath =
|
|
1279
|
-
return
|
|
1227
|
+
const dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1228
|
+
const templatePath = path6.join(dir, "templates", "cloud-init.yaml.ejs");
|
|
1229
|
+
if (fs6.existsSync(templatePath)) return fs6.readFileSync(templatePath, "utf-8");
|
|
1230
|
+
const srcPath = path6.resolve(dir, "..", "src", "templates", "cloud-init.yaml.ejs");
|
|
1231
|
+
return fs6.readFileSync(srcPath, "utf-8");
|
|
1280
1232
|
}
|
|
1281
1233
|
async function withSpinner(spinner, label, fn) {
|
|
1282
1234
|
spinner.text = label;
|
|
@@ -1294,11 +1246,7 @@ var phases = [
|
|
|
1294
1246
|
const platform = detectPlatform();
|
|
1295
1247
|
log.detail("Platform", `${platform.os} ${platform.arch}`);
|
|
1296
1248
|
log.detail("QEMU", platform.qemuBinary);
|
|
1297
|
-
const
|
|
1298
|
-
let userConfig = isNonInteractive ? loadInitConfigFromDisk() : null;
|
|
1299
|
-
if (!userConfig) {
|
|
1300
|
-
userConfig = await promptInitConfig();
|
|
1301
|
-
}
|
|
1249
|
+
const userConfig = await promptInitConfig();
|
|
1302
1250
|
ensureHome();
|
|
1303
1251
|
const masterSecret = generateMasterSecret();
|
|
1304
1252
|
const config = {
|
|
@@ -1352,6 +1300,11 @@ var phases = [
|
|
|
1352
1300
|
const { config } = ctx;
|
|
1353
1301
|
await withSpinner(spinner, "Generating SSH key...", async () => {
|
|
1354
1302
|
await generateSshKey();
|
|
1303
|
+
const pinFile = path6.join(path6.dirname(getKeyPath()), "vm_host_key.pin");
|
|
1304
|
+
try {
|
|
1305
|
+
fs6.unlinkSync(pinFile);
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1355
1308
|
addSshConfig(config.sshPort);
|
|
1356
1309
|
});
|
|
1357
1310
|
}
|
|
@@ -1434,15 +1387,15 @@ var phases = [
|
|
|
1434
1387
|
);
|
|
1435
1388
|
await withSpinner(spinner, "Staging API keys...", async () => {
|
|
1436
1389
|
const keysContent = Object.entries(keys).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
1437
|
-
const tmpKeysPath =
|
|
1438
|
-
|
|
1390
|
+
const tmpKeysPath = path6.join(os2.tmpdir(), `.nexus-keys-${crypto4.randomBytes(8).toString("hex")}`);
|
|
1391
|
+
fs6.writeFileSync(tmpKeysPath, keysContent, { mode: 384 });
|
|
1439
1392
|
try {
|
|
1440
1393
|
await sshUploadFile(config.sshPort, tmpKeysPath, "/tmp/.nexus-env-keys");
|
|
1441
1394
|
await sshExec(config.sshPort, "chmod 600 /tmp/.nexus-env-keys");
|
|
1442
1395
|
} finally {
|
|
1443
1396
|
try {
|
|
1444
|
-
|
|
1445
|
-
|
|
1397
|
+
fs6.writeFileSync(tmpKeysPath, "0".repeat(keysContent.length));
|
|
1398
|
+
fs6.unlinkSync(tmpKeysPath);
|
|
1446
1399
|
} catch {
|
|
1447
1400
|
}
|
|
1448
1401
|
}
|
|
@@ -1563,7 +1516,7 @@ init_secrets();
|
|
|
1563
1516
|
init_qemu();
|
|
1564
1517
|
init_ssh();
|
|
1565
1518
|
init_secrets();
|
|
1566
|
-
import
|
|
1519
|
+
import path7 from "path";
|
|
1567
1520
|
var startCommand = new Command2("start").description("Start the NEXUS runtime").action(async () => {
|
|
1568
1521
|
const config = loadConfig();
|
|
1569
1522
|
if (!config) {
|
|
@@ -1575,8 +1528,8 @@ var startCommand = new Command2("start").description("Start the NEXUS runtime").
|
|
|
1575
1528
|
return;
|
|
1576
1529
|
}
|
|
1577
1530
|
const platform = detectPlatform();
|
|
1578
|
-
const diskPath =
|
|
1579
|
-
const isoPath =
|
|
1531
|
+
const diskPath = path7.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2");
|
|
1532
|
+
const isoPath = path7.join(NEXUS_HOME2, "vm", "images", "init.iso");
|
|
1580
1533
|
let spinner = createSpinner("Starting VM...");
|
|
1581
1534
|
spinner.start();
|
|
1582
1535
|
await launchVm(platform, diskPath, isoPath, config.vmRam, config.vmCpus, {
|
|
@@ -1692,10 +1645,10 @@ var statusCommand = new Command4("status").description("Check NEXUS runtime heal
|
|
|
1692
1645
|
// src/commands/doctor.ts
|
|
1693
1646
|
import { Command as Command5 } from "commander";
|
|
1694
1647
|
import chalk8 from "chalk";
|
|
1695
|
-
import
|
|
1648
|
+
import fs7 from "fs";
|
|
1696
1649
|
init_qemu();
|
|
1697
1650
|
init_secrets();
|
|
1698
|
-
import
|
|
1651
|
+
import path8 from "path";
|
|
1699
1652
|
import { execa as execa4 } from "execa";
|
|
1700
1653
|
var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime environment").action(async () => {
|
|
1701
1654
|
const platform = detectPlatform();
|
|
@@ -1725,11 +1678,11 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1725
1678
|
} catch {
|
|
1726
1679
|
}
|
|
1727
1680
|
console.log(` ${check(isoTool)} ISO tool (mkisofs/genisoimage)`);
|
|
1728
|
-
const keyExists =
|
|
1681
|
+
const keyExists = fs7.existsSync(path8.join(NEXUS_HOME2, "ssh", "id_nexus_vm"));
|
|
1729
1682
|
console.log(` ${check(keyExists)} SSH key`);
|
|
1730
1683
|
const config = loadConfig();
|
|
1731
1684
|
console.log(` ${check(!!config)} Configuration`);
|
|
1732
|
-
const diskExists =
|
|
1685
|
+
const diskExists = fs7.existsSync(path8.join(NEXUS_HOME2, "vm", "images", "nexus-vm-disk.qcow2"));
|
|
1733
1686
|
console.log(` ${check(diskExists)} VM disk image`);
|
|
1734
1687
|
if (config) {
|
|
1735
1688
|
for (const [name, port] of [["SSH", config.sshPort], ["HTTP", config.httpPort], ["HTTPS", config.httpsPort]]) {
|
|
@@ -1750,7 +1703,7 @@ var doctorCommand = new Command5("doctor").description("Diagnose NEXUS runtime e
|
|
|
1750
1703
|
}
|
|
1751
1704
|
}
|
|
1752
1705
|
}
|
|
1753
|
-
const biosOk =
|
|
1706
|
+
const biosOk = fs7.existsSync(platform.biosPath);
|
|
1754
1707
|
console.log(` ${check(biosOk)} UEFI firmware ${biosOk ? "" : chalk8.dim(platform.biosPath)}`);
|
|
1755
1708
|
console.log("");
|
|
1756
1709
|
if (qemuOk && isoTool && biosOk) {
|
|
@@ -1801,18 +1754,18 @@ var logsCommand = new Command6("logs").description("View NEXUS server logs").opt
|
|
|
1801
1754
|
|
|
1802
1755
|
// src/commands/update.ts
|
|
1803
1756
|
import { Command as Command7 } from "commander";
|
|
1804
|
-
import
|
|
1805
|
-
import
|
|
1757
|
+
import path9 from "path";
|
|
1758
|
+
import fs8 from "fs";
|
|
1806
1759
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1807
1760
|
init_secrets();
|
|
1808
1761
|
init_qemu();
|
|
1809
1762
|
init_ssh();
|
|
1810
1763
|
function getReleaseTarball2() {
|
|
1811
|
-
const dir =
|
|
1812
|
-
const tarballPath =
|
|
1813
|
-
if (
|
|
1814
|
-
const rootPath =
|
|
1815
|
-
if (
|
|
1764
|
+
const dir = path9.dirname(fileURLToPath3(import.meta.url));
|
|
1765
|
+
const tarballPath = path9.join(dir, "nexus-release.tar.gz");
|
|
1766
|
+
if (fs8.existsSync(tarballPath)) return tarballPath;
|
|
1767
|
+
const rootPath = path9.resolve(dir, "..", "dist", "nexus-release.tar.gz");
|
|
1768
|
+
if (fs8.existsSync(rootPath)) return rootPath;
|
|
1816
1769
|
throw new Error("nexus-release.tar.gz not found. Reinstall buildwithnexus to get the latest release.");
|
|
1817
1770
|
}
|
|
1818
1771
|
var updateCommand = new Command7("update").description("Update NEXUS to the latest bundled release and restart").action(async () => {
|
|
@@ -1863,11 +1816,11 @@ var updateCommand = new Command7("update").description("Update NEXUS to the late
|
|
|
1863
1816
|
// src/commands/destroy.ts
|
|
1864
1817
|
import { Command as Command8 } from "commander";
|
|
1865
1818
|
import chalk9 from "chalk";
|
|
1866
|
-
import
|
|
1819
|
+
import fs9 from "fs";
|
|
1867
1820
|
import { input as input2 } from "@inquirer/prompts";
|
|
1868
1821
|
init_secrets();
|
|
1869
1822
|
init_qemu();
|
|
1870
|
-
import
|
|
1823
|
+
import path10 from "path";
|
|
1871
1824
|
var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and all data").option("--force", "Skip confirmation").action(async (opts) => {
|
|
1872
1825
|
const config = loadConfig();
|
|
1873
1826
|
if (!opts.force) {
|
|
@@ -1895,9 +1848,9 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1895
1848
|
}
|
|
1896
1849
|
stopVm();
|
|
1897
1850
|
}
|
|
1898
|
-
const sshConfigPath =
|
|
1899
|
-
if (
|
|
1900
|
-
const content =
|
|
1851
|
+
const sshConfigPath = path10.join(process.env.HOME || "~", ".ssh", "config");
|
|
1852
|
+
if (fs9.existsSync(sshConfigPath)) {
|
|
1853
|
+
const content = fs9.readFileSync(sshConfigPath, "utf-8");
|
|
1901
1854
|
const lines = content.split("\n");
|
|
1902
1855
|
const filtered = [];
|
|
1903
1856
|
let skip = false;
|
|
@@ -1910,9 +1863,9 @@ var destroyCommand = new Command8("destroy").description("Remove NEXUS VM and al
|
|
|
1910
1863
|
skip = false;
|
|
1911
1864
|
filtered.push(line);
|
|
1912
1865
|
}
|
|
1913
|
-
|
|
1866
|
+
fs9.writeFileSync(sshConfigPath, filtered.join("\n"));
|
|
1914
1867
|
}
|
|
1915
|
-
|
|
1868
|
+
fs9.rmSync(NEXUS_HOME2, { recursive: true, force: true });
|
|
1916
1869
|
succeed(spinner, "NEXUS runtime destroyed");
|
|
1917
1870
|
log.dim("Run 'buildwithnexus init' to set up again");
|
|
1918
1871
|
});
|
|
@@ -2325,10 +2278,10 @@ init_dlp();
|
|
|
2325
2278
|
// src/ui/repl.ts
|
|
2326
2279
|
init_secrets();
|
|
2327
2280
|
import readline from "readline";
|
|
2328
|
-
import
|
|
2329
|
-
import
|
|
2281
|
+
import fs10 from "fs";
|
|
2282
|
+
import path11 from "path";
|
|
2330
2283
|
import chalk13 from "chalk";
|
|
2331
|
-
var HISTORY_FILE =
|
|
2284
|
+
var HISTORY_FILE = path11.join(NEXUS_HOME2, "shell_history");
|
|
2332
2285
|
var MAX_HISTORY = 1e3;
|
|
2333
2286
|
var Repl = class {
|
|
2334
2287
|
rl = null;
|
|
@@ -2344,17 +2297,17 @@ var Repl = class {
|
|
|
2344
2297
|
}
|
|
2345
2298
|
loadHistory() {
|
|
2346
2299
|
try {
|
|
2347
|
-
if (
|
|
2348
|
-
this.history =
|
|
2300
|
+
if (fs10.existsSync(HISTORY_FILE)) {
|
|
2301
|
+
this.history = fs10.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean).slice(-MAX_HISTORY);
|
|
2349
2302
|
}
|
|
2350
2303
|
} catch {
|
|
2351
2304
|
}
|
|
2352
2305
|
}
|
|
2353
2306
|
saveHistory() {
|
|
2354
2307
|
try {
|
|
2355
|
-
const dir =
|
|
2356
|
-
|
|
2357
|
-
|
|
2308
|
+
const dir = path11.dirname(HISTORY_FILE);
|
|
2309
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
2310
|
+
fs10.writeFileSync(HISTORY_FILE, this.history.slice(-MAX_HISTORY).join("\n") + "\n", { mode: 384 });
|
|
2358
2311
|
} catch {
|
|
2359
2312
|
}
|
|
2360
2313
|
}
|
|
@@ -2969,9 +2922,7 @@ var shellCommand2 = new Command13("shell").description("Launch the interactive N
|
|
|
2969
2922
|
});
|
|
2970
2923
|
|
|
2971
2924
|
// src/cli.ts
|
|
2972
|
-
var
|
|
2973
|
-
var packageJson = JSON.parse(readFileSync2(join2(__dirname, "..", "package.json"), "utf-8"));
|
|
2974
|
-
var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version(packageJson.version);
|
|
2925
|
+
var cli = new Command14().name("buildwithnexus").description("Auto-scaffold and launch a fully autonomous NEXUS runtime").version("0.3.1");
|
|
2975
2926
|
cli.addCommand(initCommand);
|
|
2976
2927
|
cli.addCommand(startCommand);
|
|
2977
2928
|
cli.addCommand(stopCommand);
|
|
@@ -3000,18 +2951,18 @@ cli.action(async () => {
|
|
|
3000
2951
|
});
|
|
3001
2952
|
|
|
3002
2953
|
// src/core/update-notifier.ts
|
|
3003
|
-
import
|
|
3004
|
-
import
|
|
3005
|
-
import
|
|
2954
|
+
import fs11 from "fs";
|
|
2955
|
+
import path12 from "path";
|
|
2956
|
+
import os3 from "os";
|
|
3006
2957
|
import https from "https";
|
|
3007
2958
|
import chalk16 from "chalk";
|
|
3008
2959
|
var PACKAGE_NAME = "buildwithnexus";
|
|
3009
2960
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
3010
|
-
var STATE_DIR =
|
|
3011
|
-
var STATE_FILE =
|
|
2961
|
+
var STATE_DIR = path12.join(os3.homedir(), ".buildwithnexus");
|
|
2962
|
+
var STATE_FILE = path12.join(STATE_DIR, ".update-check.json");
|
|
3012
2963
|
function readState() {
|
|
3013
2964
|
try {
|
|
3014
|
-
const raw =
|
|
2965
|
+
const raw = fs11.readFileSync(STATE_FILE, "utf-8");
|
|
3015
2966
|
return JSON.parse(raw);
|
|
3016
2967
|
} catch {
|
|
3017
2968
|
return { lastCheck: 0, latestVersion: null };
|
|
@@ -3019,8 +2970,8 @@ function readState() {
|
|
|
3019
2970
|
}
|
|
3020
2971
|
function writeState(state) {
|
|
3021
2972
|
try {
|
|
3022
|
-
|
|
3023
|
-
|
|
2973
|
+
fs11.mkdirSync(STATE_DIR, { recursive: true, mode: 448 });
|
|
2974
|
+
fs11.writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 384 });
|
|
3024
2975
|
} catch {
|
|
3025
2976
|
}
|
|
3026
2977
|
}
|
|
Binary file
|
|
@@ -10,7 +10,7 @@ users:
|
|
|
10
10
|
runcmd:
|
|
11
11
|
# Wait for network + install packages (QEMU SLIRP DNS is slow at boot)
|
|
12
12
|
- |
|
|
13
|
-
PACKAGES="
|
|
13
|
+
PACKAGES="docker.io docker-compose software-properties-common git curl wget jq tmux auditd ufw libvirt-daemon libvirt-daemon-system libvirt-clients"
|
|
14
14
|
for attempt in $(seq 1 10); do
|
|
15
15
|
apt-get update && \
|
|
16
16
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --fix-missing $PACKAGES && \
|
|
@@ -36,13 +36,10 @@ runcmd:
|
|
|
36
36
|
- systemctl enable auditd
|
|
37
37
|
- systemctl start auditd
|
|
38
38
|
|
|
39
|
-
# SSH hardening
|
|
39
|
+
# SSH hardening
|
|
40
40
|
- sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
|
|
41
41
|
- sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
|
|
42
|
-
- systemctl enable ssh
|
|
43
42
|
- systemctl restart ssh
|
|
44
|
-
- sleep 2
|
|
45
|
-
- systemctl is-active ssh || systemctl start ssh
|
|
46
43
|
|
|
47
44
|
# Docker setup
|
|
48
45
|
- usermod -aG docker nexus
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buildwithnexus",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
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 && npm run bundle"
|
|
22
|
+
"prepublishOnly": "npm run build && NEXUS_SRC=/Users/garretteaglin/Projects/nexus npm run bundle"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=18"
|