@tiens.nguyen/gu-cli 1.0.789 → 1.0.791
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/client-expert-run.mjs +274 -47
- package/client-expert-setup.mjs +56 -7
- package/gonext_agent_chat.py +14 -252
- package/gu-cli.mjs +16 -3
- package/gu-repl.mjs +73 -311
- package/mongo-native.mjs +27 -2
- package/package.json +1 -2
- package/terminal-layout.mjs +2 -171
- package/clear-logs.mjs +0 -352
package/client-expert-run.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* paired, so writing it before the API is proven usable leaves a terminal aimed at something
|
|
14
14
|
* that does not work — and that state is much harder to get out of than an unfinished setup.
|
|
15
15
|
*/
|
|
16
|
-
import { execFile } from "node:child_process";
|
|
16
|
+
import { execFile, spawn } from "node:child_process";
|
|
17
17
|
import { readFile, writeFile, mkdir, rm, rename } from "node:fs/promises";
|
|
18
18
|
import { writeFileSync } from "node:fs";
|
|
19
19
|
import { homedir } from "node:os";
|
|
@@ -283,8 +283,9 @@ export async function probeDockerForMongo(name = MONGO_DOCKER_NAME) {
|
|
|
283
283
|
* macOS: make a native MongoDB answer on 27017 (task #304). → { ok, explained }.
|
|
284
284
|
* `explained` = it printed its own guidance, so the caller adds none.
|
|
285
285
|
*/
|
|
286
|
-
export async function ensureNativeMongoMac(ask, mongo, { log = (l) => console.log(l) } = {}) {
|
|
286
|
+
export async function ensureNativeMongoMac(ask, mongo, { log = (l) => console.log(l), brewHasTrust = null } = {}) {
|
|
287
287
|
const mn = await import("./mongo-native.mjs");
|
|
288
|
+
brewHasTrust ??= mn.brewHasTrust;
|
|
288
289
|
const brew = mn.findBrew();
|
|
289
290
|
const { action, holder } = mn.macMongoAction({
|
|
290
291
|
state: mongo.silentPort ? "silent" : "closed",
|
|
@@ -299,22 +300,23 @@ export async function ensureNativeMongoMac(ask, mongo, { log = (l) => console.lo
|
|
|
299
300
|
}
|
|
300
301
|
if (action === "no-brew") {
|
|
301
302
|
log("");
|
|
302
|
-
for (const l of mn.noBrewLines()) log(
|
|
303
|
+
for (const l of mn.noBrewLines()) log(" " + (l.startsWith(" ") ? l : dim(l)));
|
|
303
304
|
return { ok: false, explained: true };
|
|
304
305
|
}
|
|
305
306
|
const installing = action === "brew-install";
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
);
|
|
307
|
+
const steps = installing ? mn.brewInstallSteps({ trust: await brewHasTrust(brew) }) : mn.BREW_START_STEPS;
|
|
308
|
+
// THE COMMANDS FIRST, THEN THE QUESTION (task #329): "install now?" with nothing shown asks for
|
|
309
|
+
// a yes to something unseen. These are exactly the lines a yes runs.
|
|
310
|
+
log(dim(installing ? "\n It is not installed here. This installs it (Homebrew):\n" : "\n It is installed but not running. This starts it:\n"));
|
|
311
|
+
for (const s of steps) log(` brew ${s.join(" ")}`);
|
|
312
|
+
log("");
|
|
313
|
+
const yes = await ask(installing ? " Install MongoDB now? [Y/n]: " : " Start MongoDB now? [Y/n]: ");
|
|
311
314
|
if (String(yes ?? "").trim().toLowerCase().startsWith("n")) {
|
|
312
|
-
log(dim(
|
|
313
|
-
for (const s of installing ? mn.BREW_INSTALL_STEPS : mn.BREW_START_STEPS) log(dim(` brew ${s.join(" ")}`));
|
|
315
|
+
log(dim(`\n Not ${installing ? "installed" : "started"} — run the commands above yourself.`));
|
|
314
316
|
return { ok: false, explained: true };
|
|
315
317
|
}
|
|
316
318
|
log("\n " + bold(installing ? "Installing MongoDB Community" : "Starting MongoDB") + dim(" Homebrew, a minute or two"));
|
|
317
|
-
const done = await mn.runBrewSteps(brew,
|
|
319
|
+
const done = await mn.runBrewSteps(brew, steps, {
|
|
318
320
|
onLine: (l) => l.length < 200 && log(dim(" " + l)),
|
|
319
321
|
});
|
|
320
322
|
if (!done) {
|
|
@@ -331,6 +333,241 @@ export async function ensureNativeMongoMac(ask, mongo, { log = (l) => console.lo
|
|
|
331
333
|
return { ok: true, explained: true };
|
|
332
334
|
}
|
|
333
335
|
|
|
336
|
+
/** /etc/os-release → { id, codename, versionId }, or null where there is none (not Linux). */
|
|
337
|
+
export function readOsRelease({ read = (f) => readFileSync(f, "utf8") } = {}) {
|
|
338
|
+
let text = "";
|
|
339
|
+
try {
|
|
340
|
+
text = read("/etc/os-release");
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const kv = {};
|
|
345
|
+
for (const line of String(text).split(/\r?\n/)) {
|
|
346
|
+
const m = /^([A-Z_]+)=(.*)$/.exec(line.trim());
|
|
347
|
+
if (m) kv[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
id: String(kv.ID ?? "").toLowerCase(),
|
|
351
|
+
codename: String(kv.VERSION_CODENAME ?? kv.UBUNTU_CODENAME ?? "").toLowerCase(),
|
|
352
|
+
versionId: String(kv.VERSION_ID ?? ""),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Run one install command with THE TERMINAL ITSELF as its input and output. → exit code, or null
|
|
358
|
+
* when it could not start.
|
|
359
|
+
*
|
|
360
|
+
* `sudo` and winget ask their own questions (a password, a licence), so they get the real
|
|
361
|
+
* terminal rather than a pipe. gu's own reader is PAUSED for the duration: two readers on one
|
|
362
|
+
* terminal split the keystrokes between them, and half a sudo password typed into gu is both a
|
|
363
|
+
* failed install and a password in the wrong place.
|
|
364
|
+
*/
|
|
365
|
+
export function runInTerminal(cmd, { platform = process.platform, spawnFn = spawn, stdin = process.stdin } = {}) {
|
|
366
|
+
return new Promise((resolve) => {
|
|
367
|
+
const wasRaw = Boolean(stdin.isTTY && stdin.isRaw);
|
|
368
|
+
const wasPaused = typeof stdin.isPaused === "function" ? stdin.isPaused() : true;
|
|
369
|
+
try {
|
|
370
|
+
if (wasRaw) stdin.setRawMode(false);
|
|
371
|
+
stdin.pause?.();
|
|
372
|
+
} catch { /* not a terminal — nothing to hand over */ }
|
|
373
|
+
const restore = (code) => {
|
|
374
|
+
try {
|
|
375
|
+
if (wasRaw) stdin.setRawMode(true);
|
|
376
|
+
if (!wasPaused) stdin.resume?.();
|
|
377
|
+
} catch { /* same */ }
|
|
378
|
+
resolve(code);
|
|
379
|
+
};
|
|
380
|
+
let child;
|
|
381
|
+
try {
|
|
382
|
+
child = platform === "win32"
|
|
383
|
+
? spawnFn("cmd.exe", ["/d", "/s", "/c", cmd], { stdio: "inherit", windowsVerbatimArguments: true })
|
|
384
|
+
: spawnFn("sh", ["-c", cmd], { stdio: "inherit" });
|
|
385
|
+
} catch {
|
|
386
|
+
restore(null);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
child.on("error", () => restore(null));
|
|
390
|
+
child.on("close", (code) => restore(code));
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Make a native MongoDB answer on this machine — SHOW the commands, ASK, then run them (#329).
|
|
396
|
+
* → { ok, explained }. macOS goes through Homebrew (ensureNativeMongoMac); Ubuntu through
|
|
397
|
+
* MongoDB's apt repo; Windows 11 through winget.
|
|
398
|
+
*
|
|
399
|
+
* WHY IT ASKS NOW. This step used to print the commands and stop — a deliberate choice, after an
|
|
400
|
+
* earlier "Install MongoDB now?" whose yes and no did the same thing. The answer to a question
|
|
401
|
+
* that changes nothing is to make it change something, not to stop asking: a first install that
|
|
402
|
+
* ends in "go and run these five lines, then come back" loses the people it was written for.
|
|
403
|
+
* So yes RUNS them, in the user's own terminal where sudo can ask for its password, and no
|
|
404
|
+
* leaves the same lines on screen to run by hand.
|
|
405
|
+
*/
|
|
406
|
+
export async function ensureNativeMongo(ask, mongo, {
|
|
407
|
+
platform = process.platform,
|
|
408
|
+
log = (l) => console.log(l),
|
|
409
|
+
os = platform === "linux" ? readOsRelease() : null,
|
|
410
|
+
isRoot = typeof process.getuid === "function" && process.getuid() === 0,
|
|
411
|
+
runCmd = runInTerminal,
|
|
412
|
+
waitFor = null,
|
|
413
|
+
ensureMac = ensureNativeMongoMac,
|
|
414
|
+
} = {}) {
|
|
415
|
+
if (platform === "darwin") return ensureMac(ask, mongo, { log });
|
|
416
|
+
if (mongo.silentPort) {
|
|
417
|
+
const mn = await import("./mongo-native.mjs");
|
|
418
|
+
log("");
|
|
419
|
+
for (const l of mn.silentPortLines({ action: mn.isDockerHolder(mongo.holder) ? "docker-stuck" : "port-silent", holder: mongo.holder })) {
|
|
420
|
+
log(" " + (l.startsWith(" ") ? l : dim(l)));
|
|
421
|
+
}
|
|
422
|
+
return { ok: false, explained: true };
|
|
423
|
+
}
|
|
424
|
+
const plan = mongoInstallPlan(platform, { os, isRoot });
|
|
425
|
+
if (!plan) return { ok: false, explained: false };
|
|
426
|
+
const installing = !mongo.installed;
|
|
427
|
+
const commands = installing ? plan.commands : [plan.start];
|
|
428
|
+
if (!commands.length) {
|
|
429
|
+
// A Linux that is not Ubuntu: its package manager is not ours to guess at.
|
|
430
|
+
log(dim(`\n It is not installed here, and gu only knows the install commands for Ubuntu`));
|
|
431
|
+
log(dim(` (this is ${plan.distro || "another Linux"}). MongoDB's own instructions:`));
|
|
432
|
+
log(" " + plan.docs);
|
|
433
|
+
return { ok: false, explained: true };
|
|
434
|
+
}
|
|
435
|
+
log(dim(installing ? "\n It is not installed here. This installs it:\n" : "\n It is installed but not running. This starts it:\n"));
|
|
436
|
+
for (const c of commands) log(" " + c);
|
|
437
|
+
log("");
|
|
438
|
+
const needs = platform === "win32"
|
|
439
|
+
? "Windows may ask for administrator permission"
|
|
440
|
+
: isRoot ? "" : "sudo will ask for your password";
|
|
441
|
+
const yes = await ask(` ${installing ? "Install" : "Start"} MongoDB now?${needs ? ` (${needs})` : ""} [Y/n]: `);
|
|
442
|
+
if (String(yes ?? "").trim().toLowerCase().startsWith("n")) {
|
|
443
|
+
log(dim(`\n Not ${installing ? "installed" : "started"} — run the commands above yourself.`));
|
|
444
|
+
return { ok: false, explained: true };
|
|
445
|
+
}
|
|
446
|
+
log("\n " + bold(installing ? "Installing MongoDB" : "Starting MongoDB") + dim(installing ? " a few minutes" : ""));
|
|
447
|
+
for (const c of commands) {
|
|
448
|
+
log(dim(` $ ${c}`));
|
|
449
|
+
const code = await runCmd(c, { platform });
|
|
450
|
+
if (code !== 0) {
|
|
451
|
+
log("\n " + red("✗ ") + (code === null ? "that command could not be started" : `that command failed (exit ${code})`) + " — the lines above say why.");
|
|
452
|
+
if (platform === "win32" && code === null) log(dim(` No winget here? Download the installer: ${plan.docs}`));
|
|
453
|
+
log(dim(" Fix it, or run the remaining commands yourself."));
|
|
454
|
+
return { ok: false, explained: true };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const { host, port } = mongoHostPort(LOCAL_MONGO_URI);
|
|
458
|
+
const wait = waitFor ?? (await import("./mongo-native.mjs")).waitForMongo;
|
|
459
|
+
if (!(await wait(host, port, { timeoutMs: 60000 }))) {
|
|
460
|
+
log("\n " + red("✗ ") + `MongoDB was ${installing ? "installed" : "started"} but is not answering at ${host}:${port} yet.`);
|
|
461
|
+
log(dim(platform === "win32"
|
|
462
|
+
? " Check the service: sc query MongoDB — then run gu again."
|
|
463
|
+
: " Check the service: systemctl status mongod — then run gu again."));
|
|
464
|
+
return { ok: false, explained: true };
|
|
465
|
+
}
|
|
466
|
+
log(" " + cyan("✓ MongoDB is running") + dim(` at ${host}:${port} (a system service — it starts with the machine)`));
|
|
467
|
+
return { ok: true, explained: true };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* "" when the database at `uri` answers a MongoDB handshake, else the "host:port" that does not.
|
|
472
|
+
* mongodb+srv names a cluster rather than a host:port, so it is never reported down from here.
|
|
473
|
+
*/
|
|
474
|
+
export async function localMongoDown(uri = LOCAL_MONGO_URI, { check = null } = {}) {
|
|
475
|
+
if (/^mongodb\+srv:/i.test(String(uri))) return "";
|
|
476
|
+
const { host, port } = mongoHostPort(uri);
|
|
477
|
+
const answers = check ?? (await import("./mongo-native.mjs")).mongoAnswers;
|
|
478
|
+
return (await answers(host, port)) === "answers" ? "" : `${host}:${port}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* A FINISHED Local-mode machine whose MongoDB has stopped answering (task #329).
|
|
483
|
+
* → { ok, fixed, installed }: ok = it answers now; installed = it had to be INSTALLED, so the
|
|
484
|
+
* database is new and empty.
|
|
485
|
+
*
|
|
486
|
+
* Setup only looks at MongoDB while setup is running. Once every step is "done" nothing looked
|
|
487
|
+
* again, and the API's /api/health answers "up, mongo, persistence on" without touching the
|
|
488
|
+
* database — so a machine whose mongod was gone (seen 2026-10-02: Homebrew reinstalled, MongoDB
|
|
489
|
+
* with it) got "(cannot access local mongodb…)", then a 15s wait, then "the API is running and
|
|
490
|
+
* stuck — is mongod running?". The question it asked the reader is one it can answer itself, in
|
|
491
|
+
* milliseconds, with the same handshake setup uses.
|
|
492
|
+
*/
|
|
493
|
+
export async function preflightLocalMongo({
|
|
494
|
+
ask,
|
|
495
|
+
isTty = false,
|
|
496
|
+
platform = process.platform,
|
|
497
|
+
uri = LOCAL_MONGO_URI,
|
|
498
|
+
check = null,
|
|
499
|
+
probe = probeMongo,
|
|
500
|
+
ensure = ensureNativeMongo,
|
|
501
|
+
brewHasTrust = async (brew) => (await import("./mongo-native.mjs")).brewHasTrust(brew),
|
|
502
|
+
os = platform === "linux" ? readOsRelease() : null,
|
|
503
|
+
isRoot = typeof process.getuid === "function" && process.getuid() === 0,
|
|
504
|
+
log = (l) => console.log(l),
|
|
505
|
+
} = {}) {
|
|
506
|
+
if (!(await localMongoDown(uri, { check }))) return { ok: true, fixed: false, installed: false };
|
|
507
|
+
const { host, port } = mongoHostPort(uri);
|
|
508
|
+
|
|
509
|
+
const local = /^(127\.|localhost$|\[?::1\]?$)/i.test(host);
|
|
510
|
+
const mongo = local ? await probe() : { running: false, installed: false };
|
|
511
|
+
// The cheap handshake can lose a race with a mongod that is still starting; the full probe
|
|
512
|
+
// (mongosh ping, then the socket again) is the second opinion before anything is said.
|
|
513
|
+
if (mongo.running) return { ok: true, fixed: false, installed: false };
|
|
514
|
+
|
|
515
|
+
log("\n " + red("gu: MongoDB is not answering") + dim(` at ${host}:${port}`) +
|
|
516
|
+
red(mongo.installed || mongo.silentPort || !local ? "." : " — it is not installed on this machine."));
|
|
517
|
+
log(dim(" Local mode keeps your conversations, models and keys in that database, so"));
|
|
518
|
+
log(dim(" nothing can start without it. The API on :4100 is fine — it is waiting on this."));
|
|
519
|
+
if (!local) {
|
|
520
|
+
log(dim(`\n That database is on another machine (${host}). Start it there, then run gu again.\n`));
|
|
521
|
+
return { ok: false, fixed: false, installed: false };
|
|
522
|
+
}
|
|
523
|
+
if (isTty && ask) {
|
|
524
|
+
const r = await ensure(ask, mongo, { platform, log, os, isRoot });
|
|
525
|
+
if (r.ok || r.explained) {
|
|
526
|
+
if (!r.ok) log("\n " + dim("Then run `gu` again.") + "\n");
|
|
527
|
+
return { ok: r.ok, fixed: r.ok, installed: r.ok && !mongo.installed };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// NOBODY TO ASK (a pipe, CI): the same commands, printed, and nothing is run.
|
|
531
|
+
const plan = mongoInstallPlan(platform, { os, isRoot });
|
|
532
|
+
if (mongo.silentPort) {
|
|
533
|
+
const mn = await import("./mongo-native.mjs");
|
|
534
|
+
log("");
|
|
535
|
+
for (const l of mn.silentPortLines({ action: mn.isDockerHolder(mongo.holder) ? "docker-stuck" : "port-silent", holder: mongo.holder, port })) {
|
|
536
|
+
log(" " + (l.startsWith(" ") ? l : dim(l)));
|
|
537
|
+
}
|
|
538
|
+
} else if (mongo.installed && plan?.start) {
|
|
539
|
+
log(dim("\n It is installed but not running. Start it with:\n"));
|
|
540
|
+
log(" " + plan.start);
|
|
541
|
+
} else if (plan?.commands.length) {
|
|
542
|
+
log(dim("\n Install it with:\n"));
|
|
543
|
+
let commands = plan.commands;
|
|
544
|
+
if (platform === "darwin") {
|
|
545
|
+
// The same steps a yes would run — including `brew trust`, where this Homebrew needs it.
|
|
546
|
+
const mn = await import("./mongo-native.mjs");
|
|
547
|
+
commands = mn.brewInstallSteps({ trust: await brewHasTrust(mn.findBrew()) }).map((s) => `brew ${s.join(" ")}`);
|
|
548
|
+
}
|
|
549
|
+
for (const c of commands) log(" " + c);
|
|
550
|
+
} else {
|
|
551
|
+
log(dim("\n Install it — MongoDB's own instructions:\n"));
|
|
552
|
+
log(" " + (plan?.docs ?? "https://www.mongodb.com/docs/manual/installation/"));
|
|
553
|
+
}
|
|
554
|
+
log("\n " + dim("Then run `gu` again.") + "\n");
|
|
555
|
+
return { ok: false, fixed: false, installed: false };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* A newly INSTALLED MongoDB is empty: the user, the worker key and the model settings this
|
|
560
|
+
* machine's setup wrote are not in it. Re-open exactly those steps, so the next thing `gu` does
|
|
561
|
+
* is its ordinary resume — which seeds the database under the SAME identity (seedDatabase reuses
|
|
562
|
+
* localUserId / localWorkerKey) and asks for the models again. Pure.
|
|
563
|
+
*/
|
|
564
|
+
export function reopenAfterEmptyDatabase(state) {
|
|
565
|
+
const why = "MongoDB was reinstalled — its database is new and empty";
|
|
566
|
+
let next = recordStep(state, "database", "interrupted", { reason: why });
|
|
567
|
+
next = recordStep(next, "models", "interrupted", { reason: why });
|
|
568
|
+
return next;
|
|
569
|
+
}
|
|
570
|
+
|
|
334
571
|
/**
|
|
335
572
|
* Start MongoDB in a container and wait for it to answer. → true when it is up.
|
|
336
573
|
*
|
|
@@ -734,53 +971,43 @@ export async function runClientExpertSetup({
|
|
|
734
971
|
}
|
|
735
972
|
|
|
736
973
|
if (plan.next === "mongo") {
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
//
|
|
740
|
-
// "Install MongoDB now? [y/N]"
|
|
741
|
-
// commands
|
|
742
|
-
//
|
|
743
|
-
|
|
974
|
+
// SHOW THE COMMANDS, ASK, THEN RUN THEM (task #329) — on macOS, Ubuntu and Windows 11 alike.
|
|
975
|
+
//
|
|
976
|
+
// This used to only check: "installing a database is a system-wide change needing sudo… the
|
|
977
|
+
// user can do better themselves", written after an earlier "Install MongoDB now? [y/N]" that
|
|
978
|
+
// printed the same commands whichever way it was answered. That was the right verdict on a
|
|
979
|
+
// question that changed nothing and the wrong fix for it — a first install that ends in "run
|
|
980
|
+
// these five lines and come back" is where people stop. So the question is back and a yes is
|
|
981
|
+
// honoured: the commands run in the user's own terminal, where sudo asks for its password.
|
|
982
|
+
console.log("\n " + bold(mongo.installed ? "MongoDB is not running on this machine." : "MongoDB is not installed on this machine."));
|
|
744
983
|
console.log(dim(" Local mode keeps every conversation and token count in a database"));
|
|
745
984
|
console.log(dim(" on THIS machine, so it needs one before setup can go further."));
|
|
746
985
|
// A MAC GETS NATIVE MONGODB, NOT DOCKER (task #304): Homebrew's mongodb-community as a brew
|
|
747
986
|
// service. Docker on a Mac puts every call through its VM, and a stuck Docker Desktop keeps
|
|
748
|
-
// 27017 open while nothing answers — the failure that stopped this project's own Mac.
|
|
749
|
-
//
|
|
987
|
+
// 27017 open while nothing answers — the failure that stopped this project's own Mac.
|
|
988
|
+
//
|
|
989
|
+
// ELSEWHERE, DOCKER IS OFFERED ONLY WHERE IT ALREADY ANSWERS — one container, no system
|
|
990
|
+
// service — and declining it, or not having it, leads to the native install rather than to
|
|
991
|
+
// "Docker is not available here": a machine without Docker is the ordinary case on a server
|
|
992
|
+
// and on Windows, not an error to open with.
|
|
750
993
|
let explained = false;
|
|
751
|
-
if (machine.platform
|
|
752
|
-
|
|
994
|
+
if (machine.platform !== "darwin" && !mongo.silentPort && (await probeDockerForMongo()).usable) {
|
|
995
|
+
if (await startMongoInDocker(ask)) {
|
|
996
|
+
mongo.running = true;
|
|
997
|
+
probe.mongoRunning = true;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (!probe.mongoRunning) {
|
|
1001
|
+
const r = await ensureNativeMongo(ask, mongo, { platform: machine.platform });
|
|
753
1002
|
if (r.ok) {
|
|
754
1003
|
mongo.running = true;
|
|
755
1004
|
probe.mongoRunning = true;
|
|
756
1005
|
}
|
|
757
1006
|
explained = r.explained;
|
|
758
|
-
} else
|
|
759
|
-
// DOCKER FIRST, WHERE IT EXISTS. This is the one branch here that can actually finish the
|
|
760
|
-
// job rather than print homework: one container, no tap, no system service, and the same
|
|
761
|
-
// mongo:8 the project's own end-to-end tests run against. Only offered when `docker ps`
|
|
762
|
-
// answers, so a machine with Docker installed-but-not-running is not sent down a dead end.
|
|
763
|
-
// On success setup CONTINUES in this same run instead of asking the user to start over.
|
|
764
|
-
if (await startMongoInDocker(ask)) {
|
|
765
|
-
mongo.running = true;
|
|
766
|
-
probe.mongoRunning = true;
|
|
767
1007
|
}
|
|
768
|
-
if (probe.mongoRunning
|
|
769
|
-
//
|
|
770
|
-
|
|
771
|
-
console.log(dim("\n It looks installed but is not answering. Start it with:\n"));
|
|
772
|
-
console.log(
|
|
773
|
-
" " + (machine.platform === "darwin"
|
|
774
|
-
? "brew services start mongodb-community"
|
|
775
|
-
: machine.platform === "win32"
|
|
776
|
-
? "net start MongoDB"
|
|
777
|
-
: "sudo systemctl enable --now mongod")
|
|
778
|
-
);
|
|
779
|
-
} else {
|
|
780
|
-
const install = mongoInstallPlan(machine.platform);
|
|
781
|
-
console.log(dim("\n Install it however you prefer — for reference, the usual way here is:\n"));
|
|
782
|
-
for (const c of install.commands) console.log(dim(" " + c));
|
|
783
|
-
console.log("\n " + dim(`then check it answers: ${install.verify}`));
|
|
1008
|
+
if (!probe.mongoRunning && !explained) {
|
|
1009
|
+
// A platform with no plan at all — unreachable past the gate, kept so it is never silent.
|
|
1010
|
+
console.log(dim("\n Install MongoDB Community, then run gu again: https://www.mongodb.com/docs/manual/installation/"));
|
|
784
1011
|
}
|
|
785
1012
|
// STOP ONLY IF IT IS STILL NOT RUNNING. When Docker started one above there is nothing to
|
|
786
1013
|
// wait for, so setup carries straight on in this run — sending someone away to re-run
|
package/client-expert-setup.mjs
CHANGED
|
@@ -446,7 +446,39 @@ export function mongoDockerPlan({ exists = false, port = 27017 } = {}) {
|
|
|
446
446
|
}
|
|
447
447
|
|
|
448
448
|
/** The install commands for a platform, or null when we do not support it. */
|
|
449
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Which MongoDB apt repo an Ubuntu release uses → "focal" | "jammy" | "noble" | "" (no repo).
|
|
451
|
+
*
|
|
452
|
+
* THE CODENAME IS PART OF THE COMMAND (task #329). The plan used to say `jammy/mongodb-org/7.0`
|
|
453
|
+
* to every Linux machine, and that repo does not exist for Ubuntu 24.04 (noble has no 7.0 —
|
|
454
|
+
* checked: HTTP 404), so on the commonest server release `apt-get update` failed on the line
|
|
455
|
+
* setup had just printed. 8.0 is published for focal, jammy and noble. A release between two
|
|
456
|
+
* LTSes takes the LTS below it, which is what MongoDB's own instructions say to do.
|
|
457
|
+
*/
|
|
458
|
+
export function ubuntuMongoRepo({ codename = "", versionId = "" } = {}) {
|
|
459
|
+
const c = String(codename).trim().toLowerCase();
|
|
460
|
+
if (MONGO_APT_CODENAMES.includes(c)) return c;
|
|
461
|
+
const v = parseFloat(String(versionId));
|
|
462
|
+
if (!Number.isFinite(v)) return "";
|
|
463
|
+
if (v >= 24.04) return "noble";
|
|
464
|
+
if (v >= 22.04) return "jammy";
|
|
465
|
+
if (v >= 20.04) return "focal";
|
|
466
|
+
return "";
|
|
467
|
+
}
|
|
468
|
+
export const MONGO_APT_CODENAMES = ["focal", "jammy", "noble"];
|
|
469
|
+
export const MONGO_APT_SERIES = "8.0";
|
|
470
|
+
export const MONGO_LINUX_DOCS = "https://www.mongodb.com/docs/manual/administration/install-on-linux/";
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The commands that install MongoDB here, the one that starts an installed one, and how to check.
|
|
474
|
+
*
|
|
475
|
+
* `os` is /etc/os-release on Linux ({ id, codename, versionId }) and `isRoot` drops `sudo` — a
|
|
476
|
+
* root shell on a server often has no sudo at all, and a pasted `sudo …` then fails on the word.
|
|
477
|
+
* With no `os` given the Linux commands read the codename from the machine themselves, so they
|
|
478
|
+
* are still right when copied. A Linux that is not Ubuntu gets no commands, only `docs`: its
|
|
479
|
+
* package manager is not one this plan can speak for.
|
|
480
|
+
*/
|
|
481
|
+
export function mongoInstallPlan(platform, { os = null, isRoot = false } = {}) {
|
|
450
482
|
if (platform === "darwin") {
|
|
451
483
|
return {
|
|
452
484
|
platform,
|
|
@@ -455,6 +487,7 @@ export function mongoInstallPlan(platform) {
|
|
|
455
487
|
"brew install mongodb-community",
|
|
456
488
|
"brew services start mongodb-community",
|
|
457
489
|
],
|
|
490
|
+
start: "brew services start mongodb-community",
|
|
458
491
|
verify: "mongosh --eval 'db.runCommand({ ping: 1 })'",
|
|
459
492
|
};
|
|
460
493
|
}
|
|
@@ -467,20 +500,36 @@ export function mongoInstallPlan(platform) {
|
|
|
467
500
|
"winget install --id MongoDB.Server --accept-package-agreements --accept-source-agreements",
|
|
468
501
|
"winget install --id MongoDB.Shell --accept-package-agreements --accept-source-agreements",
|
|
469
502
|
],
|
|
503
|
+
start: "net start MongoDB",
|
|
504
|
+
docs: "https://www.mongodb.com/try/download/community",
|
|
470
505
|
verify: "mongosh --eval \"db.runCommand({ ping: 1 })\"",
|
|
471
506
|
};
|
|
472
507
|
}
|
|
473
508
|
if (platform === "linux") {
|
|
509
|
+
const verify = "mongosh --eval 'db.runCommand({ ping: 1 })'";
|
|
510
|
+
const sudo = isRoot ? "" : "sudo ";
|
|
511
|
+
const start = `${sudo}systemctl enable --now mongod`;
|
|
512
|
+
const id = String(os?.id ?? "").trim().toLowerCase();
|
|
513
|
+
if (os && id && id !== "ubuntu") {
|
|
514
|
+
return { platform, commands: [], start, docs: MONGO_LINUX_DOCS, verify, distro: id };
|
|
515
|
+
}
|
|
516
|
+
const repo = os ? ubuntuMongoRepo(os) : '$(. /etc/os-release && echo "$VERSION_CODENAME")';
|
|
517
|
+
if (!repo) return { platform, commands: [], start, docs: MONGO_LINUX_DOCS, verify, distro: id || "ubuntu" };
|
|
518
|
+
const v = MONGO_APT_SERIES;
|
|
519
|
+
const key = `/usr/share/keyrings/mongodb-server-${v}.gpg`;
|
|
474
520
|
return {
|
|
475
521
|
platform,
|
|
476
522
|
commands: [
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
"
|
|
481
|
-
|
|
523
|
+
`${sudo}apt-get install -y gnupg curl`,
|
|
524
|
+
// --yes: a second run must overwrite the key, not stop to ask whether it may.
|
|
525
|
+
`curl -fsSL https://pgp.mongodb.com/server-${v}.asc | ${sudo}gpg --dearmor --yes -o ${key}`,
|
|
526
|
+
`echo "deb [ arch=amd64,arm64 signed-by=${key} ] https://repo.mongodb.org/apt/ubuntu ${repo}/mongodb-org/${v} multiverse" | ${sudo}tee /etc/apt/sources.list.d/mongodb-org-${v}.list`,
|
|
527
|
+
`${sudo}apt-get update && ${sudo}apt-get install -y mongodb-org`,
|
|
528
|
+
start,
|
|
482
529
|
],
|
|
483
|
-
|
|
530
|
+
start,
|
|
531
|
+
docs: MONGO_LINUX_DOCS,
|
|
532
|
+
verify,
|
|
484
533
|
};
|
|
485
534
|
}
|
|
486
535
|
return null;
|