Files
mesh-catalog/modules/zsh/tools/index.ts
T
jochen cfe1dffa45
mesh/merge-gate pass: builds zsh → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group withhold-login-shell-execute delivered: every member is delivered
zsh: let a machine withhold node-login-shell's execute
execute runs any command as the operator account, which can become root
without a person. The operator withholds it on the control-node until a
call needs a person's approval (hq ADR 0268); the other machines keep it
for now. The setting execute (serve by default) is written into a
root-owned file the tools bundle reads at start, and anything but serve
withholds the verb, so the machine's runtime does not offer it.
2026-10-09 00:10:28 +02:00

73 lines
3.7 KiB
TypeScript

// zsh's tools: its implementation of node-login-shell's one verb, `execute`, and its own
// `zsh_config` (novox/hq ADR 0176, ADR 0204). The node tools runtime launches this bundle as a
// process of its own and serves what it registers (ADR 0188, ADR 0193); it runs as the operator
// account, so the command runs as that account with nothing switched (shell.ts).
//
// `execute` is registered only where this machine's setting serves it (novox/hq ADR 0268): the
// control-node withholds it, so its runtime offers no such verb and a call to it finds nothing.
import { userInfo } from "node:os";
import { join } from "node:path";
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, commandEnv, execute, homeOf, readExecuteSetting, timeoutOf, zshFile } from "../shell.js";
/** The operator account on this machine, as the mesh told the runtime. */
function account(env: NodeJS.ProcessEnv): string {
return env.MESH_OPERATOR_ACCOUNT?.trim() || userInfo().username;
}
function seatVerbs(env: NodeJS.ProcessEnv): ToolDefinition[] {
const setting = readExecuteSetting(env);
if (!setting.served) {
console.error(`[zsh] node-login-shell's execute is not served on this machine: ${setting.why}`);
return [];
}
return [
{
name: "execute",
description:
`Run one command on this machine as the operator account, in a zsh login shell in the account's home; answers with what it printed (each stream cut at 256 KiB, said in truncated) and how it exited. Ended, with everything it started, after timeout_seconds (default ${DEFAULT_TIMEOUT_SECONDS}, at most ${MAX_TIMEOUT_SECONDS}).`,
input: {
type: "object",
properties: {
command: { type: "string", description: "the command line, as you would type it" },
timeout_seconds: { type: "number", description: `give up after this long (default ${DEFAULT_TIMEOUT_SECONDS}, at most ${MAX_TIMEOUT_SECONDS})` },
},
required: ["command"],
},
run: async (args) => {
const command = String(args.command ?? "").trim();
if (!command) throw new Error("execute: a command is required");
const who = account(env);
const self = userInfo().username;
if (who !== self) {
// The runtime is the account; anything else is a runtime this was not written for, and
// a command run as the wrong user is worse than one not run.
throw new Error(`execute runs as ${who}, and this runtime runs as ${self}`);
}
return execute(command, { cwd: homeOf(env), env: commandEnv(env), timeoutSeconds: timeoutOf(args.timeout_seconds), account: who });
},
},
];
}
function ownTools(env: NodeJS.ProcessEnv): ToolDefinition[] {
return [
{
name: "zsh_config",
description: "The operator account's ~/.zshenv and ~/.zshrc on this machine as they are now: each file's lines, how many are the mesh's block, and the content; and whether this machine serves node-login-shell's execute, and why.",
input: { type: "object", properties: {} },
run: async () => {
const home = homeOf(env);
const setting = readExecuteSetting(env);
return { account: account(env), execute: { served: setting.served, why: setting.why }, zshenv: await zshFile(join(home, ".zshenv")), zshrc: await zshFile(join(home, ".zshrc")) };
},
},
];
}
// The seat's verb is registered under the seat's name (what the runtime serves on the seat's
// subject while this module holds it) and the module's own tool under the module's.
registerModuleTools("node-login-shell", seatVerbs);
registerModuleTools("zsh", ownTools);