traderclaw-cli 1.0.124 → 1.0.127
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/bin/installer-step-engine.mjs +100 -5
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@ import { homedir, tmpdir } from "os";
|
|
|
5
5
|
import { basename, dirname, join } from "path";
|
|
6
6
|
import { resolvePluginPackageRoot } from "./resolve-plugin-root.mjs";
|
|
7
7
|
import { choosePreferredProviderModel } from "./llm-model-preference.mjs";
|
|
8
|
-
import { getLinuxGatewayPersistenceSnapshot } from "./gateway-persistence-linux.mjs";
|
|
8
|
+
import { getLinuxGatewayPersistenceSnapshot, isLinuxGatewayPersistenceEligible } from "./gateway-persistence-linux.mjs";
|
|
9
9
|
|
|
10
10
|
const CONFIG_DIR = join(homedir(), ".openclaw");
|
|
11
11
|
const CONFIG_FILE = join(CONFIG_DIR, "openclaw.json");
|
|
@@ -342,6 +342,7 @@ function buildCommandString(cmd, args = []) {
|
|
|
342
342
|
|
|
343
343
|
function isPrivilegeError(err) {
|
|
344
344
|
const text = `${err?.message || ""}\n${err?.stderr || ""}\n${err?.stdout || ""}`.toLowerCase();
|
|
345
|
+
// Do not match bare "sudo" — OpenClaw prints "try sudo loginctl" in hints; that is not a priv-denied signal.
|
|
345
346
|
return (
|
|
346
347
|
text.includes("permission denied")
|
|
347
348
|
|| text.includes("eacces")
|
|
@@ -349,7 +350,8 @@ function isPrivilegeError(err) {
|
|
|
349
350
|
|| text.includes("operation not permitted")
|
|
350
351
|
|| text.includes("must be root")
|
|
351
352
|
|| text.includes("requires root")
|
|
352
|
-
|| text.includes("sudo")
|
|
353
|
+
|| text.includes("sudo: a password is required")
|
|
354
|
+
|| text.includes("a terminal is required to read the password")
|
|
353
355
|
|| text.includes("authentication is required")
|
|
354
356
|
);
|
|
355
357
|
}
|
|
@@ -367,6 +369,16 @@ function canUseSudoWithoutPrompt() {
|
|
|
367
369
|
}
|
|
368
370
|
}
|
|
369
371
|
|
|
372
|
+
/** User systemd (openclaw gateway) expects XDG_RUNTIME_DIR under /run/user/<uid> once linger is on. */
|
|
373
|
+
function primeLinuxUserRuntimeEnv() {
|
|
374
|
+
if (process.platform !== "linux" || typeof process.getuid !== "function") return;
|
|
375
|
+
const uid = process.getuid();
|
|
376
|
+
if (uid === 0) return;
|
|
377
|
+
if (!process.env.XDG_RUNTIME_DIR) {
|
|
378
|
+
process.env.XDG_RUNTIME_DIR = `/run/user/${uid}`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
370
382
|
function tailscalePermissionRemediation() {
|
|
371
383
|
return [
|
|
372
384
|
"Tailscale requires elevated permissions on this host.",
|
|
@@ -2307,11 +2319,33 @@ export class InstallerStepEngine {
|
|
|
2307
2319
|
}
|
|
2308
2320
|
|
|
2309
2321
|
async runWithPrivilegeGuidance(stepId, cmd, args = [], customLines = []) {
|
|
2322
|
+
primeLinuxUserRuntimeEnv();
|
|
2323
|
+
const onEvent = (evt) =>
|
|
2324
|
+
this.emitLog(stepId, evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []);
|
|
2325
|
+
const tryRun = (c, a) => runCommandWithEvents(c, a, { onEvent });
|
|
2326
|
+
|
|
2310
2327
|
try {
|
|
2311
|
-
return await
|
|
2312
|
-
onEvent: (evt) => this.emitLog(stepId, evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
2313
|
-
});
|
|
2328
|
+
return await tryRun(cmd, args);
|
|
2314
2329
|
} catch (err) {
|
|
2330
|
+
const sudoOpenclawGateway =
|
|
2331
|
+
cmd === "openclaw" &&
|
|
2332
|
+
Array.isArray(args) &&
|
|
2333
|
+
args[0] === "gateway" &&
|
|
2334
|
+
!isRootUser() &&
|
|
2335
|
+
canUseSudoWithoutPrompt();
|
|
2336
|
+
if (sudoOpenclawGateway) {
|
|
2337
|
+
const bin = getCommandOutput("command -v openclaw")?.trim();
|
|
2338
|
+
if (bin) {
|
|
2339
|
+
try {
|
|
2340
|
+
return await tryRun("sudo", ["-n", bin, ...args]);
|
|
2341
|
+
} catch (e2) {
|
|
2342
|
+
if (isPrivilegeError(e2)) {
|
|
2343
|
+
throw new Error(privilegeRemediationMessage(cmd, args, customLines));
|
|
2344
|
+
}
|
|
2345
|
+
throw e2;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2315
2349
|
if (isPrivilegeError(err)) {
|
|
2316
2350
|
throw new Error(privilegeRemediationMessage(cmd, args, customLines));
|
|
2317
2351
|
}
|
|
@@ -2481,6 +2515,28 @@ export class InstallerStepEngine {
|
|
|
2481
2515
|
this.state.autoRecovery.backupPath = backupPath;
|
|
2482
2516
|
this.emitLog(stepId, "warn", `Auto-recovery: applied ${changed.join(", ")} with backup at ${backupPath}`);
|
|
2483
2517
|
|
|
2518
|
+
if (isLinuxGatewayPersistenceEligible()) {
|
|
2519
|
+
const lingerSnap = getLinuxGatewayPersistenceSnapshot();
|
|
2520
|
+
if (lingerSnap.linger !== true) {
|
|
2521
|
+
try {
|
|
2522
|
+
await this.runWithPrivilegeGuidance(stepId, "sudo", ["loginctl", "enable-linger", lingerSnap.username]);
|
|
2523
|
+
} catch {
|
|
2524
|
+
// best effort; gateway install may still surface a clearer error
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
if (!isRootUser() && canUseSudoWithoutPrompt() && typeof process.getuid === "function") {
|
|
2528
|
+
const uid = process.getuid();
|
|
2529
|
+
try {
|
|
2530
|
+
await runCommandWithEvents("sudo", ["-n", "systemctl", "start", `user@${uid}.service`], {
|
|
2531
|
+
onEvent: (evt) => this.emitLog(stepId, evt.type === "stderr" ? "warn" : "info", evt.text, evt.urls || []),
|
|
2532
|
+
});
|
|
2533
|
+
} catch {
|
|
2534
|
+
// best effort
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
primeLinuxUserRuntimeEnv();
|
|
2539
|
+
|
|
2484
2540
|
try {
|
|
2485
2541
|
await this.runWithPrivilegeGuidance(stepId, "openclaw", ["gateway", "stop"]);
|
|
2486
2542
|
} catch {
|
|
@@ -2835,6 +2891,45 @@ export class InstallerStepEngine {
|
|
|
2835
2891
|
ensureGatewayBootstrapDefaults(CONFIG_FILE, (msg) =>
|
|
2836
2892
|
this.emitLog("gateway_bootstrap", "info", msg),
|
|
2837
2893
|
);
|
|
2894
|
+
// `openclaw gateway install` uses user systemd; linger must be enabled before install
|
|
2895
|
+
// (gateway_persistence runs later — too late). VPS userdata may already do this as root.
|
|
2896
|
+
if (isLinuxGatewayPersistenceEligible()) {
|
|
2897
|
+
const lingerSnap = getLinuxGatewayPersistenceSnapshot();
|
|
2898
|
+
if (lingerSnap.linger !== true) {
|
|
2899
|
+
this.emitLog(
|
|
2900
|
+
"gateway_bootstrap",
|
|
2901
|
+
"info",
|
|
2902
|
+
"Enabling systemd user linger before openclaw gateway install (required for headless/SSH).",
|
|
2903
|
+
);
|
|
2904
|
+
await this.runWithPrivilegeGuidance("gateway_bootstrap", "sudo", [
|
|
2905
|
+
"loginctl",
|
|
2906
|
+
"enable-linger",
|
|
2907
|
+
lingerSnap.username,
|
|
2908
|
+
]);
|
|
2909
|
+
}
|
|
2910
|
+
// Linger alone does not always create /run/user/<uid> until user@uid.service runs (SSH-less bootstrap).
|
|
2911
|
+
if (!isRootUser() && canUseSudoWithoutPrompt() && typeof process.getuid === "function") {
|
|
2912
|
+
const uid = process.getuid();
|
|
2913
|
+
try {
|
|
2914
|
+
await runCommandWithEvents("sudo", ["-n", "systemctl", "start", `user@${uid}.service`], {
|
|
2915
|
+
onEvent: (evt) =>
|
|
2916
|
+
this.emitLog(
|
|
2917
|
+
"gateway_bootstrap",
|
|
2918
|
+
evt.type === "stderr" ? "warn" : "info",
|
|
2919
|
+
evt.text,
|
|
2920
|
+
evt.urls || [],
|
|
2921
|
+
),
|
|
2922
|
+
});
|
|
2923
|
+
} catch {
|
|
2924
|
+
this.emitLog(
|
|
2925
|
+
"gateway_bootstrap",
|
|
2926
|
+
"warn",
|
|
2927
|
+
`Could not start user@${uid}.service (non-fatal); gateway install may still work if session exists.`,
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
primeLinuxUserRuntimeEnv();
|
|
2838
2933
|
await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "install"]);
|
|
2839
2934
|
await this.runWithPrivilegeGuidance("gateway_bootstrap", "openclaw", ["gateway", "restart"]);
|
|
2840
2935
|
if (this.options.skipFunnel) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "traderclaw-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.127",
|
|
4
4
|
"description": "Global TraderClaw CLI (install --wizard, setup, precheck). Installs solana-traderclaw as a dependency for OpenClaw plugin files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"node": ">=22"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"solana-traderclaw": "^1.0.
|
|
20
|
+
"solana-traderclaw": "^1.0.127"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"traderclaw",
|