From cb309e31abafb890f8a739746dd13be4a4abf694 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Thu, 17 Sep 2026 06:43:04 +0000 Subject: [PATCH] dns ca: escalate itself for the system store, like dns enable; never advise sudo moshcode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bonita: sudo moshcode dns ca → sudo: 'moshcode': command not found, because moshcode lives in the operator's PATH, not root's. The verb now escalates this one command from inside (escalateSelf, which carries the path and lets operatorHome find the person behind sudo) and falls back to the browser store alone with a note when no sudo is on offer; --user-only skips the prompt on purpose. The two skipped-step hints that said sudo moshcode dns ca now say to run moshcode dns ca from a terminal. Co-Authored-By: Claude Fable 5.1 --- src/cli-schema.mjs | 3 ++- src/dns.mjs | 17 +++++++++++++++++ src/trust.mjs | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/cli-schema.mjs b/src/cli-schema.mjs index 52cff6c3..b284075a 100644 --- a/src/cli-schema.mjs +++ b/src/cli-schema.mjs @@ -1148,7 +1148,8 @@ export const DNS_VERBS = [ name: "ca", description: "trust the registry's root here, so every Moshpit name is trusted at once", synopsis: [ - ["moshcode dns ca", "fetch the registry's root, check it, install it (NSS without root, the system store with sudo)"], + ["moshcode dns ca", "fetch the registry's root, check it, install it everywhere (asks for sudo for the system store)"], + ["moshcode dns ca --user-only", "the browser store only; no prompt"], ["moshcode dns ca --remove", "take it back out of every store"], ], }, diff --git a/src/dns.mjs b/src/dns.mjs index 1a87524a..1fec6493 100644 --- a/src/dns.mjs +++ b/src/dns.mjs @@ -2625,6 +2625,23 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) { // bridge, DoH in the browser, TronBrowser's own resolver) but its clients // still refuse the certificates. `--remove` takes it back out. if (sub === "ca") { + // Half of this needs root (the system store) and half must not run as + // root's own user (the operator's NSS database), which is exactly the + // shape `dns enable` already handles: escalate this one command, and let + // operatorHome() find the person behind sudo. `sudo moshcode dns ca` from + // a shell is what bonita hit: moshcode lives in the operator's PATH, not + // root's, so "command not found". Escalating from inside carries the path. + // --user-only keeps it to the browser store, no prompt, for a machine + // where sudo is not on offer. + if (uid !== 0 && !rest.includes("--user-only")) { + const escalated = escalate({ + args: ["dns", "ca", ...rest], + what: "dns ca", + out, + }); + if (escalated.ran) return escalated.code; + out("(no sudo here — installing into the browser store only; the system store needs root)"); + } if (rest.includes("--remove")) { const r = await removeRegistryTrust(out, deps); return r.ok ? 0 : 1; diff --git a/src/trust.mjs b/src/trust.mjs index 685b54c9..00ce5bd2 100644 --- a/src/trust.mjs +++ b/src/trust.mjs @@ -1167,7 +1167,7 @@ export async function applyRegistryTrust(out, deps = {}) { } for (const step of plan.skipped) { out(` -- ${step.label} — ${step.why}`); - if (step.needsRoot) out(" re-run with root to cover it: sudo moshcode dns ca"); + if (step.needsRoot) out(" run `moshcode dns ca` from a terminal to cover it — it asks for sudo itself"); } return { ok: true, installed, skipped: plan.skipped.length, file }; } @@ -1225,7 +1225,7 @@ export async function removeRegistryTrust(out, deps = {}) { if (haveFile) await runner("rm", ["-f", file]); for (const step of plan.skipped) { out(` -- ${step.label} — ${step.why}`); - if (step.needsRoot && uid !== 0) out(" re-run with root to cover it: sudo moshcode dns ca --remove"); + if (step.needsRoot && uid !== 0) out(" run `moshcode dns ca --remove` from a terminal to cover it — it asks for sudo itself"); } return { ok: true, removed, skipped: plan.skipped.length }; }