@agenshield/integrations 0.7.0
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/homebrew.d.ts +31 -0
- package/homebrew.d.ts.map +1 -0
- package/index.d.ts +13 -0
- package/index.d.ts.map +1 -0
- package/index.js +917 -0
- package/openclaw-install.d.ts +135 -0
- package/openclaw-install.d.ts.map +1 -0
- package/openclaw-launchdaemon.d.ts +91 -0
- package/openclaw-launchdaemon.d.ts.map +1 -0
- package/package.json +21 -0
package/homebrew.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent User Homebrew Installation
|
|
3
|
+
*
|
|
4
|
+
* Installs a user-local Homebrew in the agent's $HOME/homebrew directory.
|
|
5
|
+
* This gives the sandboxed agent access to brew without relying on the
|
|
6
|
+
* host system's Homebrew installation.
|
|
7
|
+
*/
|
|
8
|
+
export interface HomebrewInstallResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
brewPath: string;
|
|
11
|
+
message: string;
|
|
12
|
+
error?: Error;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Install a user-local Homebrew for the agent user.
|
|
16
|
+
*
|
|
17
|
+
* Creates $HOME/homebrew and downloads the Homebrew tarball into it.
|
|
18
|
+
* Runs as the agent user via `sudo -u`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function installAgentHomebrew(options: {
|
|
21
|
+
agentHome: string;
|
|
22
|
+
agentUsername: string;
|
|
23
|
+
socketGroupName: string;
|
|
24
|
+
verbose?: boolean;
|
|
25
|
+
onLog?: (msg: string) => void;
|
|
26
|
+
}): Promise<HomebrewInstallResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Check if agent-local Homebrew is installed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isAgentHomebrewInstalled(agentHome: string): Promise<boolean>;
|
|
31
|
+
//# sourceMappingURL=homebrew.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homebrew.d.ts","sourceRoot":"","sources":["../src/homebrew.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkFH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyDjC;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlF"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgenShield Integrations
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw and third-party integration utilities.
|
|
5
|
+
* Extracted from @agenshield/sandbox to break circular dependency
|
|
6
|
+
* with @agenshield/broker.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
export { installAgentHomebrew, isAgentHomebrewInstalled, type HomebrewInstallResult, } from './homebrew';
|
|
11
|
+
export { detectHostOpenClawVersion, installAgentOpenClaw, copyOpenClawConfig, stopHostOpenClaw, getOriginalUser, getHostOpenClawConfigPath, onboardAgentOpenClaw, startAgentOpenClawGateway, startAgentOpenClawDashboard, type OpenClawInstallResult, type OpenClawConfigCopyResult, type StopHostOpenClawResult, } from './openclaw-install';
|
|
12
|
+
export { generateOpenClawGatewayPlist, installOpenClawLauncher, installOpenClawLaunchDaemons, startOpenClawServices, stopOpenClawServices, restartOpenClawServices, getOpenClawStatus, getOpenClawStatusSync, getOpenClawDashboardUrl, isOpenClawInstalled, uninstallOpenClawLaunchDaemons, OPENCLAW_GATEWAY_LABEL, OPENCLAW_DAEMON_PLIST, OPENCLAW_GATEWAY_PLIST, OPENCLAW_LAUNCHER_PATH, type OpenClawLaunchConfig, type OpenClawProcessStatus, type OpenClawStatus, type OpenClawDaemonResult, } from './openclaw-launchdaemon';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,8BAA8B,EAC9B,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,yBAAyB,CAAC"}
|
package/index.js
ADDED
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
// libs/shield-integrations/src/homebrew.ts
|
|
2
|
+
import { exec, spawn } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
var execAsync = promisify(exec);
|
|
6
|
+
function isNoiseLine(line) {
|
|
7
|
+
if (/^\s*%\s+Total/.test(line)) return true;
|
|
8
|
+
if (/^\s*Dload\s+Upload/.test(line)) return true;
|
|
9
|
+
if (/^[\d\s.kMG:\-/|]+$/.test(line)) return true;
|
|
10
|
+
if (/^=>?\s*$/.test(line)) return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
async function execWithProgress(command, log, opts) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = spawn("/bin/bash", ["-c", command], {
|
|
16
|
+
cwd: opts?.cwd || "/",
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18
|
+
});
|
|
19
|
+
let stdout = "";
|
|
20
|
+
let stderr = "";
|
|
21
|
+
let timer;
|
|
22
|
+
if (opts?.timeout) {
|
|
23
|
+
timer = setTimeout(() => {
|
|
24
|
+
child.kill("SIGTERM");
|
|
25
|
+
reject(new Error(`Command timed out after ${opts.timeout}ms`));
|
|
26
|
+
}, opts.timeout);
|
|
27
|
+
}
|
|
28
|
+
child.stdout?.on("data", (data) => {
|
|
29
|
+
const text = data.toString();
|
|
30
|
+
stdout += text;
|
|
31
|
+
for (const line of text.split("\n")) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (trimmed && !isNoiseLine(trimmed)) log(trimmed);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
child.stderr?.on("data", (data) => {
|
|
37
|
+
const text = data.toString();
|
|
38
|
+
stderr += text;
|
|
39
|
+
for (const line of text.split("\n")) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (trimmed && !isNoiseLine(trimmed)) log(trimmed);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
child.on("close", (code) => {
|
|
45
|
+
if (timer) clearTimeout(timer);
|
|
46
|
+
if (code === 0) {
|
|
47
|
+
resolve({ stdout, stderr });
|
|
48
|
+
} else {
|
|
49
|
+
const err = new Error(`Command failed with exit code ${code}: ${stderr.slice(0, 500)}`);
|
|
50
|
+
err.stdout = stdout;
|
|
51
|
+
err.stderr = stderr;
|
|
52
|
+
reject(err);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
child.on("error", (err) => {
|
|
56
|
+
if (timer) clearTimeout(timer);
|
|
57
|
+
reject(err);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
var HOMEBREW_TARBALL_URL = "https://github.com/Homebrew/brew/tarball/master";
|
|
62
|
+
async function installAgentHomebrew(options) {
|
|
63
|
+
const { agentHome, agentUsername, socketGroupName, verbose, onLog } = options;
|
|
64
|
+
const brewDir = `${agentHome}/homebrew`;
|
|
65
|
+
const brewPath = `${brewDir}/bin/brew`;
|
|
66
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
67
|
+
`));
|
|
68
|
+
try {
|
|
69
|
+
log(`Creating homebrew directory at ${brewDir}`);
|
|
70
|
+
await execAsync(`sudo mkdir -p "${brewDir}"`);
|
|
71
|
+
await execAsync(`sudo chown ${agentUsername}:${socketGroupName} "${brewDir}"`);
|
|
72
|
+
log("Downloading and extracting Homebrew");
|
|
73
|
+
const installCmd = [
|
|
74
|
+
`export HOME="${agentHome}"`,
|
|
75
|
+
`/usr/bin/curl -fsSL "${HOMEBREW_TARBALL_URL}" | /usr/bin/tar xz --strip 1 -C homebrew`
|
|
76
|
+
].join(" && ");
|
|
77
|
+
await execWithProgress(
|
|
78
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${installCmd}'`,
|
|
79
|
+
log,
|
|
80
|
+
{ cwd: agentHome, timeout: 12e4 }
|
|
81
|
+
);
|
|
82
|
+
try {
|
|
83
|
+
await fs.access(brewPath);
|
|
84
|
+
} catch {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
brewPath,
|
|
88
|
+
message: "Homebrew downloaded but brew binary not found"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
log("Setting ownership for homebrew directory");
|
|
92
|
+
await execAsync(`sudo chown -R ${agentUsername}:${socketGroupName} "${brewDir}"`);
|
|
93
|
+
log(`Homebrew installed at ${brewDir}`);
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
brewPath,
|
|
97
|
+
message: `Homebrew installed at ${brewDir}`
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
brewPath,
|
|
103
|
+
message: `Homebrew installation failed: ${error.message}`,
|
|
104
|
+
error
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function isAgentHomebrewInstalled(agentHome) {
|
|
109
|
+
try {
|
|
110
|
+
await fs.access(`${agentHome}/homebrew/bin/brew`);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// libs/shield-integrations/src/openclaw-install.ts
|
|
118
|
+
import * as fs2 from "node:fs";
|
|
119
|
+
import * as os from "node:os";
|
|
120
|
+
import * as path from "node:path";
|
|
121
|
+
import { exec as exec2, execSync, spawn as spawn2 } from "node:child_process";
|
|
122
|
+
import { promisify as promisify2 } from "node:util";
|
|
123
|
+
var execAsync2 = promisify2(exec2);
|
|
124
|
+
function isNoiseLine2(line) {
|
|
125
|
+
if (/^\s*%\s+Total/.test(line)) return true;
|
|
126
|
+
if (/^\s*Dload\s+Upload/.test(line)) return true;
|
|
127
|
+
if (/^[\d\s.kMG:\-/|]+$/.test(line)) return true;
|
|
128
|
+
if (/^=>?\s*$/.test(line)) return true;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
async function execWithProgress2(command, log, opts) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const child = spawn2("/bin/bash", ["-c", command], {
|
|
134
|
+
cwd: opts?.cwd || "/",
|
|
135
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
136
|
+
});
|
|
137
|
+
let stdout = "";
|
|
138
|
+
let stderr = "";
|
|
139
|
+
let timer;
|
|
140
|
+
if (opts?.timeout) {
|
|
141
|
+
timer = setTimeout(() => {
|
|
142
|
+
child.kill("SIGTERM");
|
|
143
|
+
reject(new Error(`Command timed out after ${opts.timeout}ms`));
|
|
144
|
+
}, opts.timeout);
|
|
145
|
+
}
|
|
146
|
+
child.stdout?.on("data", (data) => {
|
|
147
|
+
const text = data.toString();
|
|
148
|
+
stdout += text;
|
|
149
|
+
for (const line of text.split("\n")) {
|
|
150
|
+
const trimmed = line.trim();
|
|
151
|
+
if (trimmed && !isNoiseLine2(trimmed)) log(trimmed);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
child.stderr?.on("data", (data) => {
|
|
155
|
+
const text = data.toString();
|
|
156
|
+
stderr += text;
|
|
157
|
+
for (const line of text.split("\n")) {
|
|
158
|
+
const trimmed = line.trim();
|
|
159
|
+
if (trimmed && !isNoiseLine2(trimmed)) log(trimmed);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
child.on("close", (code) => {
|
|
163
|
+
if (timer) clearTimeout(timer);
|
|
164
|
+
if (code === 0) {
|
|
165
|
+
resolve({ stdout, stderr });
|
|
166
|
+
} else {
|
|
167
|
+
const err = new Error(`Command failed with exit code ${code}: ${stderr.slice(0, 500)}`);
|
|
168
|
+
err.stdout = stdout;
|
|
169
|
+
err.stderr = stderr;
|
|
170
|
+
reject(err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
child.on("error", (err) => {
|
|
174
|
+
if (timer) clearTimeout(timer);
|
|
175
|
+
reject(err);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function sudoExec(cmd) {
|
|
180
|
+
try {
|
|
181
|
+
const output = execSync(`sudo ${cmd}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
182
|
+
return { success: true, output: output.trim() };
|
|
183
|
+
} catch (err) {
|
|
184
|
+
const error = err;
|
|
185
|
+
return { success: false, error: error.stderr || error.message || "Unknown error" };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function detectHostOpenClawVersion() {
|
|
189
|
+
try {
|
|
190
|
+
const output = execSync("openclaw --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
191
|
+
const match = output.match(/(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)/);
|
|
192
|
+
return match ? match[1] : output;
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function installAgentOpenClaw(options) {
|
|
198
|
+
const { agentHome, agentUsername, socketGroupName, verbose, onLog } = options;
|
|
199
|
+
const targetVersion = options.targetVersion || "latest";
|
|
200
|
+
const nvmDir = `${agentHome}/.nvm`;
|
|
201
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
202
|
+
`));
|
|
203
|
+
const empty = {
|
|
204
|
+
success: false,
|
|
205
|
+
version: "",
|
|
206
|
+
binaryPath: "",
|
|
207
|
+
message: ""
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
const versionSpec = targetVersion === "latest" ? "openclaw" : `openclaw@${targetVersion}`;
|
|
211
|
+
log(`Installing ${versionSpec} for agent user via NVM npm`);
|
|
212
|
+
const installCmd = [
|
|
213
|
+
`export HOME="${agentHome}"`,
|
|
214
|
+
`export NVM_DIR="${nvmDir}"`,
|
|
215
|
+
`source "${nvmDir}/nvm.sh"`,
|
|
216
|
+
`npm install -g ${versionSpec}`
|
|
217
|
+
].join(" && ");
|
|
218
|
+
await execWithProgress2(
|
|
219
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${installCmd}'`,
|
|
220
|
+
log,
|
|
221
|
+
{ cwd: "/", timeout: 18e4 }
|
|
222
|
+
);
|
|
223
|
+
log("Resolving installed openclaw binary path");
|
|
224
|
+
const whichCmd = [
|
|
225
|
+
`export HOME="${agentHome}"`,
|
|
226
|
+
`export NVM_DIR="${nvmDir}"`,
|
|
227
|
+
`source "${nvmDir}/nvm.sh"`,
|
|
228
|
+
`which openclaw`
|
|
229
|
+
].join(" && ");
|
|
230
|
+
const { stdout: binaryPath } = await execAsync2(
|
|
231
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${whichCmd}'`,
|
|
232
|
+
{ cwd: "/" }
|
|
233
|
+
);
|
|
234
|
+
const resolvedPath = binaryPath.trim();
|
|
235
|
+
if (!resolvedPath) {
|
|
236
|
+
return { ...empty, message: "OpenClaw installed but binary path could not be resolved" };
|
|
237
|
+
}
|
|
238
|
+
log("Verifying OpenClaw installation");
|
|
239
|
+
const verifyCmd = [
|
|
240
|
+
`export HOME="${agentHome}"`,
|
|
241
|
+
`export NVM_DIR="${nvmDir}"`,
|
|
242
|
+
`source "${nvmDir}/nvm.sh"`,
|
|
243
|
+
`openclaw --version`
|
|
244
|
+
].join(" && ");
|
|
245
|
+
const { stdout: versionOut } = await execAsync2(
|
|
246
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${verifyCmd}'`,
|
|
247
|
+
{ cwd: "/" }
|
|
248
|
+
);
|
|
249
|
+
const installedVersion = versionOut.trim();
|
|
250
|
+
log(`OpenClaw ${installedVersion} installed at ${resolvedPath}`);
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
version: installedVersion,
|
|
254
|
+
binaryPath: resolvedPath,
|
|
255
|
+
message: `OpenClaw ${installedVersion} installed at ${resolvedPath}`
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
...empty,
|
|
260
|
+
message: `OpenClaw installation failed: ${error.message}`,
|
|
261
|
+
error
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function copyOpenClawConfig(options) {
|
|
266
|
+
const { sourceConfigPath, agentHome, agentUsername, socketGroup, verbose, onLog } = options;
|
|
267
|
+
const targetConfigDir = path.join(agentHome, ".openclaw");
|
|
268
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
269
|
+
`));
|
|
270
|
+
try {
|
|
271
|
+
if (!fs2.existsSync(sourceConfigPath)) {
|
|
272
|
+
log("Source config path does not exist, creating empty .openclaw");
|
|
273
|
+
sudoExec(`mkdir -p "${path.join(targetConfigDir, "skills")}" "${path.join(targetConfigDir, "canvas")}"`);
|
|
274
|
+
sudoExec(`chown -R ${agentUsername}:${socketGroup} "${targetConfigDir}"`);
|
|
275
|
+
sudoExec(`chmod 2775 "${targetConfigDir}"`);
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
configDir: targetConfigDir,
|
|
279
|
+
sanitized: false,
|
|
280
|
+
message: "Created empty .openclaw directory (source not found)"
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (fs2.existsSync(targetConfigDir)) {
|
|
284
|
+
sudoExec(`rm -rf "${targetConfigDir}"`);
|
|
285
|
+
}
|
|
286
|
+
log(`Copying ${sourceConfigPath} \u2192 ${targetConfigDir}`);
|
|
287
|
+
const cpResult = sudoExec(`cp -a "${sourceConfigPath}" "${targetConfigDir}"`);
|
|
288
|
+
if (!cpResult.success) {
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
configDir: targetConfigDir,
|
|
292
|
+
sanitized: false,
|
|
293
|
+
message: `cp -a failed: ${cpResult.error}`
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
log(`Setting ownership: ${agentUsername}:${socketGroup}`);
|
|
297
|
+
sudoExec(`chown -R ${agentUsername}:${socketGroup} "${targetConfigDir}"`);
|
|
298
|
+
const originalHome = path.dirname(sourceConfigPath);
|
|
299
|
+
if (originalHome !== agentHome) {
|
|
300
|
+
log(`Rewriting paths: ${originalHome} \u2192 ${agentHome}`);
|
|
301
|
+
sudoExec(
|
|
302
|
+
`find "${targetConfigDir}" -type f -exec sed -i '' "s|${originalHome}|${agentHome}|g" {} +`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
sudoExec(`mkdir -p "${path.join(targetConfigDir, "skills")}"`);
|
|
306
|
+
sudoExec(`chmod 2775 "${path.join(targetConfigDir, "skills")}"`);
|
|
307
|
+
sudoExec(`mkdir -p "${path.join(targetConfigDir, "canvas")}"`);
|
|
308
|
+
sudoExec(`chmod 2775 "${path.join(targetConfigDir, "canvas")}"`);
|
|
309
|
+
sudoExec(`chown ${agentUsername}:${socketGroup} "${path.join(targetConfigDir, "canvas")}"`);
|
|
310
|
+
sudoExec(`chmod 2775 "${targetConfigDir}"`);
|
|
311
|
+
return {
|
|
312
|
+
success: true,
|
|
313
|
+
configDir: targetConfigDir,
|
|
314
|
+
sanitized: originalHome !== agentHome,
|
|
315
|
+
message: `Copied .openclaw config to ${targetConfigDir}${originalHome !== agentHome ? " (paths rewritten)" : ""}`
|
|
316
|
+
};
|
|
317
|
+
} catch (error) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
configDir: targetConfigDir,
|
|
321
|
+
sanitized: false,
|
|
322
|
+
message: `Failed to copy OpenClaw config: ${error.message}`,
|
|
323
|
+
error
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function findOpenClawProcesses(username) {
|
|
328
|
+
const result = { daemon: [], gateway: [], other: [] };
|
|
329
|
+
try {
|
|
330
|
+
const { output } = sudoExec(`ps -u ${username} -o pid,command`);
|
|
331
|
+
if (!output) return result;
|
|
332
|
+
for (const line of output.split("\n")) {
|
|
333
|
+
if (!line.includes("openclaw")) continue;
|
|
334
|
+
const pidMatch = line.match(/^\s*(\d+)/);
|
|
335
|
+
if (!pidMatch) continue;
|
|
336
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
337
|
+
if (line.includes("daemon")) {
|
|
338
|
+
result.daemon.push(pid);
|
|
339
|
+
} else if (line.includes("gateway")) {
|
|
340
|
+
result.gateway.push(pid);
|
|
341
|
+
} else {
|
|
342
|
+
result.other.push(pid);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
async function stopHostOpenClaw(options) {
|
|
350
|
+
const { originalUser, verbose, onLog } = options;
|
|
351
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
352
|
+
`));
|
|
353
|
+
let daemonStopped = false;
|
|
354
|
+
let gatewayStopped = false;
|
|
355
|
+
const procs = findOpenClawProcesses(originalUser);
|
|
356
|
+
const totalProcs = procs.daemon.length + procs.gateway.length + procs.other.length;
|
|
357
|
+
log(`Found ${totalProcs} OpenClaw process(es) for ${originalUser} (daemon: ${procs.daemon.length}, gateway: ${procs.gateway.length}, other: ${procs.other.length})`);
|
|
358
|
+
if (totalProcs === 0) {
|
|
359
|
+
log("No OpenClaw processes running \u2014 nothing to stop");
|
|
360
|
+
return {
|
|
361
|
+
success: true,
|
|
362
|
+
daemonStopped: true,
|
|
363
|
+
gatewayStopped: true,
|
|
364
|
+
message: "No OpenClaw processes were running"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (procs.gateway.length > 0) {
|
|
368
|
+
log(`Stopping OpenClaw gateway for user: ${originalUser}`);
|
|
369
|
+
try {
|
|
370
|
+
await execAsync2(
|
|
371
|
+
`sudo -H -u ${originalUser} openclaw gateway stop`,
|
|
372
|
+
{ cwd: "/", timeout: 15e3 }
|
|
373
|
+
);
|
|
374
|
+
gatewayStopped = true;
|
|
375
|
+
log("OpenClaw gateway stopped gracefully");
|
|
376
|
+
} catch {
|
|
377
|
+
log("OpenClaw gateway stop command failed, will try kill");
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
gatewayStopped = true;
|
|
381
|
+
}
|
|
382
|
+
if (procs.daemon.length > 0) {
|
|
383
|
+
log(`Stopping OpenClaw daemon for user: ${originalUser}`);
|
|
384
|
+
try {
|
|
385
|
+
await execAsync2(
|
|
386
|
+
`sudo -H -u ${originalUser} openclaw daemon stop`,
|
|
387
|
+
{ cwd: "/", timeout: 15e3 }
|
|
388
|
+
);
|
|
389
|
+
daemonStopped = true;
|
|
390
|
+
log("OpenClaw daemon stopped gracefully");
|
|
391
|
+
} catch {
|
|
392
|
+
log("OpenClaw daemon stop command failed, will try kill");
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
daemonStopped = true;
|
|
396
|
+
}
|
|
397
|
+
const remaining = findOpenClawProcesses(originalUser);
|
|
398
|
+
const allPids = [...remaining.daemon, ...remaining.gateway, ...remaining.other];
|
|
399
|
+
if (allPids.length > 0) {
|
|
400
|
+
log(`${allPids.length} OpenClaw process(es) still running, sending SIGTERM`);
|
|
401
|
+
for (const pid of allPids) {
|
|
402
|
+
try {
|
|
403
|
+
sudoExec(`kill ${pid}`);
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
408
|
+
const stubborn = findOpenClawProcesses(originalUser);
|
|
409
|
+
const stubbornPids = [...stubborn.daemon, ...stubborn.gateway, ...stubborn.other];
|
|
410
|
+
if (stubbornPids.length > 0) {
|
|
411
|
+
log(`${stubbornPids.length} process(es) survived SIGTERM, sending SIGKILL`);
|
|
412
|
+
for (const pid of stubbornPids) {
|
|
413
|
+
try {
|
|
414
|
+
sudoExec(`kill -9 ${pid}`);
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
daemonStopped = true;
|
|
420
|
+
gatewayStopped = true;
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
success: true,
|
|
424
|
+
daemonStopped,
|
|
425
|
+
gatewayStopped,
|
|
426
|
+
message: `Daemon: stopped, Gateway: stopped`
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function getOriginalUser() {
|
|
430
|
+
return process.env["SUDO_USER"] || os.userInfo().username;
|
|
431
|
+
}
|
|
432
|
+
function getHostOpenClawConfigPath(username) {
|
|
433
|
+
const user = username || getOriginalUser();
|
|
434
|
+
const configPath = `/Users/${user}/.openclaw`;
|
|
435
|
+
if (fs2.existsSync(configPath)) {
|
|
436
|
+
return configPath;
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
async function onboardAgentOpenClaw(options) {
|
|
441
|
+
const { agentHome, agentUsername, verbose, onLog } = options;
|
|
442
|
+
const nvmDir = `${agentHome}/.nvm`;
|
|
443
|
+
const log = onLog || ((msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
444
|
+
`));
|
|
445
|
+
const onboardCmd = [
|
|
446
|
+
`export HOME="${agentHome}"`,
|
|
447
|
+
`export NVM_DIR="${nvmDir}"`,
|
|
448
|
+
`source "${nvmDir}/nvm.sh"`,
|
|
449
|
+
`openclaw onboard --non-interactive --accept-risk --flow quickstart --mode local --no-install-daemon --daemon-runtime node --skip-channels --skip-skills --skip-health --skip-ui --node-manager npm`
|
|
450
|
+
].join(" && ");
|
|
451
|
+
log("Running openclaw onboard as agent user");
|
|
452
|
+
try {
|
|
453
|
+
await execWithProgress2(
|
|
454
|
+
`sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${onboardCmd}'`,
|
|
455
|
+
log,
|
|
456
|
+
{ cwd: "/", timeout: 12e4 }
|
|
457
|
+
);
|
|
458
|
+
log("OpenClaw onboard completed");
|
|
459
|
+
return { success: true, message: "OpenClaw onboard completed" };
|
|
460
|
+
} catch (err) {
|
|
461
|
+
const msg = err.message;
|
|
462
|
+
log(`OpenClaw onboard failed (non-fatal): ${msg}`);
|
|
463
|
+
return { success: false, message: `OpenClaw onboard failed: ${msg}` };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async function startAgentOpenClawGateway(options) {
|
|
467
|
+
const { agentHome, agentUsername, socketGroupName, verbose } = options;
|
|
468
|
+
const nvmDir = `${agentHome}/.nvm`;
|
|
469
|
+
const log = (msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
470
|
+
`);
|
|
471
|
+
const gatewayCmd = [
|
|
472
|
+
`export HOME="${agentHome}"`,
|
|
473
|
+
`export NVM_DIR="${nvmDir}"`,
|
|
474
|
+
`source "${nvmDir}/nvm.sh"`,
|
|
475
|
+
`exec openclaw gateway run`
|
|
476
|
+
].join(" && ");
|
|
477
|
+
log("Starting openclaw gateway run in background");
|
|
478
|
+
try {
|
|
479
|
+
sudoExec("mkdir -p /var/log/agenshield");
|
|
480
|
+
sudoExec(`touch /var/log/agenshield/openclaw-gateway.log /var/log/agenshield/openclaw-gateway.error.log`);
|
|
481
|
+
sudoExec(`chown ${agentUsername}:${socketGroupName} /var/log/agenshield/openclaw-gateway.log /var/log/agenshield/openclaw-gateway.error.log`);
|
|
482
|
+
sudoExec(`chmod 666 /var/log/agenshield/openclaw-gateway.log /var/log/agenshield/openclaw-gateway.error.log`);
|
|
483
|
+
const outLog = fs2.openSync("/var/log/agenshield/openclaw-gateway.log", "a");
|
|
484
|
+
const errLog = fs2.openSync("/var/log/agenshield/openclaw-gateway.error.log", "a");
|
|
485
|
+
const child = spawn2(
|
|
486
|
+
"sudo",
|
|
487
|
+
["-H", "-u", agentUsername, "/bin/bash", "--norc", "--noprofile", "-c", gatewayCmd],
|
|
488
|
+
{
|
|
489
|
+
cwd: "/",
|
|
490
|
+
detached: true,
|
|
491
|
+
stdio: ["ignore", outLog, errLog]
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
const pid = child.pid;
|
|
495
|
+
child.unref();
|
|
496
|
+
fs2.closeSync(outLog);
|
|
497
|
+
fs2.closeSync(errLog);
|
|
498
|
+
if (!pid) {
|
|
499
|
+
return { success: false, message: "Failed to spawn openclaw gateway \u2014 no PID returned" };
|
|
500
|
+
}
|
|
501
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
502
|
+
try {
|
|
503
|
+
process.kill(pid, 0);
|
|
504
|
+
log(`OpenClaw gateway started (PID: ${pid})`);
|
|
505
|
+
return { success: true, pid, message: `OpenClaw gateway running (PID: ${pid})` };
|
|
506
|
+
} catch {
|
|
507
|
+
log("OpenClaw gateway process exited immediately \u2014 check logs");
|
|
508
|
+
return { success: false, message: "OpenClaw gateway exited immediately. Check /var/log/agenshield/openclaw-gateway.error.log" };
|
|
509
|
+
}
|
|
510
|
+
} catch (err) {
|
|
511
|
+
const msg = err.message;
|
|
512
|
+
log(`Failed to start openclaw gateway: ${msg}`);
|
|
513
|
+
return { success: false, message: `Failed to start openclaw gateway: ${msg}` };
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async function startAgentOpenClawDashboard(options) {
|
|
517
|
+
const { agentHome, agentUsername, socketGroupName, verbose } = options;
|
|
518
|
+
const nvmDir = `${agentHome}/.nvm`;
|
|
519
|
+
const log = (msg) => verbose && process.stderr.write(`[SETUP] ${msg}
|
|
520
|
+
`);
|
|
521
|
+
const dashboardCmd = [
|
|
522
|
+
`export HOME="${agentHome}"`,
|
|
523
|
+
`export NVM_DIR="${nvmDir}"`,
|
|
524
|
+
`source "${nvmDir}/nvm.sh"`,
|
|
525
|
+
`exec openclaw dashboard`
|
|
526
|
+
].join(" && ");
|
|
527
|
+
log("Starting openclaw dashboard in background");
|
|
528
|
+
try {
|
|
529
|
+
sudoExec(`touch /var/log/agenshield/openclaw-dashboard.log /var/log/agenshield/openclaw-dashboard.error.log`);
|
|
530
|
+
sudoExec(`chown ${agentUsername}:${socketGroupName} /var/log/agenshield/openclaw-dashboard.log /var/log/agenshield/openclaw-dashboard.error.log`);
|
|
531
|
+
sudoExec(`chmod 666 /var/log/agenshield/openclaw-dashboard.log /var/log/agenshield/openclaw-dashboard.error.log`);
|
|
532
|
+
const outLog = fs2.openSync("/var/log/agenshield/openclaw-dashboard.log", "a");
|
|
533
|
+
const errLog = fs2.openSync("/var/log/agenshield/openclaw-dashboard.error.log", "a");
|
|
534
|
+
const child = spawn2(
|
|
535
|
+
"sudo",
|
|
536
|
+
["-H", "-u", agentUsername, "/bin/bash", "--norc", "--noprofile", "-c", dashboardCmd],
|
|
537
|
+
{
|
|
538
|
+
cwd: "/",
|
|
539
|
+
detached: true,
|
|
540
|
+
stdio: ["ignore", outLog, errLog]
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
const pid = child.pid;
|
|
544
|
+
child.unref();
|
|
545
|
+
fs2.closeSync(outLog);
|
|
546
|
+
fs2.closeSync(errLog);
|
|
547
|
+
if (!pid) {
|
|
548
|
+
return { success: false, message: "Failed to spawn openclaw dashboard \u2014 no PID returned" };
|
|
549
|
+
}
|
|
550
|
+
log(`OpenClaw dashboard started (PID: ${pid})`);
|
|
551
|
+
return { success: true, pid, message: `OpenClaw dashboard running (PID: ${pid})` };
|
|
552
|
+
} catch (err) {
|
|
553
|
+
const msg = err.message;
|
|
554
|
+
log(`Failed to start openclaw dashboard: ${msg}`);
|
|
555
|
+
return { success: false, message: `Failed to start openclaw dashboard: ${msg}` };
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// libs/shield-integrations/src/openclaw-launchdaemon.ts
|
|
560
|
+
import * as fs3 from "node:fs/promises";
|
|
561
|
+
import * as path2 from "node:path";
|
|
562
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
563
|
+
import { exec as exec3 } from "node:child_process";
|
|
564
|
+
import { promisify as promisify3 } from "node:util";
|
|
565
|
+
var execAsync3 = promisify3(exec3);
|
|
566
|
+
var OPENCLAW_DAEMON_LABEL = "com.agenshield.openclaw.daemon";
|
|
567
|
+
var OPENCLAW_GATEWAY_LABEL = "com.agenshield.openclaw.gateway";
|
|
568
|
+
var OPENCLAW_DAEMON_PLIST = "/Library/LaunchDaemons/com.agenshield.openclaw.daemon.plist";
|
|
569
|
+
var OPENCLAW_GATEWAY_PLIST = "/Library/LaunchDaemons/com.agenshield.openclaw.gateway.plist";
|
|
570
|
+
var OPENCLAW_LAUNCHER_PATH = "/opt/agenshield/bin/openclaw-launcher.sh";
|
|
571
|
+
var BROKER_LABEL = "com.agenshield.broker";
|
|
572
|
+
function generateLauncherScript(config) {
|
|
573
|
+
const socketPath = config.socketPath || "/var/run/agenshield/agenshield.sock";
|
|
574
|
+
const interceptorPath = config.interceptorPath || "/opt/agenshield/lib/interceptor/register.cjs";
|
|
575
|
+
const httpPort = config.httpPort || 5201;
|
|
576
|
+
return `#!/bin/bash
|
|
577
|
+
# OpenClaw Launcher \u2014 runs openclaw with AgenShield interceptor
|
|
578
|
+
# Generated by AgenShield setup. Do not edit manually.
|
|
579
|
+
|
|
580
|
+
export HOME="${config.agentHome}"
|
|
581
|
+
export NVM_DIR="${config.agentHome}/.nvm"
|
|
582
|
+
|
|
583
|
+
# Load NVM to get correct node/npm/openclaw in PATH
|
|
584
|
+
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
|
585
|
+
source "$NVM_DIR/nvm.sh"
|
|
586
|
+
fi
|
|
587
|
+
|
|
588
|
+
# Load interceptor via NODE_OPTIONS
|
|
589
|
+
export NODE_OPTIONS="--require ${interceptorPath} \${NODE_OPTIONS:-}"
|
|
590
|
+
|
|
591
|
+
# AgenShield environment
|
|
592
|
+
export AGENSHIELD_SOCKET="${socketPath}"
|
|
593
|
+
export AGENSHIELD_HTTP_PORT="${httpPort}"
|
|
594
|
+
export AGENSHIELD_INTERCEPT_EXEC=true
|
|
595
|
+
export AGENSHIELD_INTERCEPT_HTTP=true
|
|
596
|
+
export AGENSHIELD_INTERCEPT_FETCH=true
|
|
597
|
+
export AGENSHIELD_INTERCEPT_WS=true
|
|
598
|
+
export AGENSHIELD_CONTEXT_TYPE=agent
|
|
599
|
+
|
|
600
|
+
exec openclaw "$@"
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
603
|
+
function generateOpenClawGatewayPlist(config) {
|
|
604
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
605
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
606
|
+
<plist version="1.0">
|
|
607
|
+
<dict>
|
|
608
|
+
<key>Label</key>
|
|
609
|
+
<string>${OPENCLAW_GATEWAY_LABEL}</string>
|
|
610
|
+
|
|
611
|
+
<key>ProgramArguments</key>
|
|
612
|
+
<array>
|
|
613
|
+
<string>${OPENCLAW_LAUNCHER_PATH}</string>
|
|
614
|
+
<string>gateway</string>
|
|
615
|
+
<string>run</string>
|
|
616
|
+
</array>
|
|
617
|
+
|
|
618
|
+
<key>UserName</key>
|
|
619
|
+
<string>${config.agentUsername}</string>
|
|
620
|
+
|
|
621
|
+
<key>GroupName</key>
|
|
622
|
+
<string>${config.socketGroupName}</string>
|
|
623
|
+
|
|
624
|
+
<key>RunAtLoad</key>
|
|
625
|
+
<false/>
|
|
626
|
+
|
|
627
|
+
<key>KeepAlive</key>
|
|
628
|
+
<dict>
|
|
629
|
+
<key>OtherJobEnabled</key>
|
|
630
|
+
<dict>
|
|
631
|
+
<key>${BROKER_LABEL}</key>
|
|
632
|
+
<true/>
|
|
633
|
+
</dict>
|
|
634
|
+
</dict>
|
|
635
|
+
|
|
636
|
+
<key>ThrottleInterval</key>
|
|
637
|
+
<integer>10</integer>
|
|
638
|
+
|
|
639
|
+
<key>StandardOutPath</key>
|
|
640
|
+
<string>/var/log/agenshield/openclaw-gateway.log</string>
|
|
641
|
+
|
|
642
|
+
<key>StandardErrorPath</key>
|
|
643
|
+
<string>/var/log/agenshield/openclaw-gateway.error.log</string>
|
|
644
|
+
|
|
645
|
+
<key>WorkingDirectory</key>
|
|
646
|
+
<string>${config.agentHome}</string>
|
|
647
|
+
|
|
648
|
+
<key>SoftResourceLimits</key>
|
|
649
|
+
<dict>
|
|
650
|
+
<key>NumberOfFiles</key>
|
|
651
|
+
<integer>4096</integer>
|
|
652
|
+
</dict>
|
|
653
|
+
</dict>
|
|
654
|
+
</plist>
|
|
655
|
+
`;
|
|
656
|
+
}
|
|
657
|
+
async function installOpenClawLauncher(config) {
|
|
658
|
+
try {
|
|
659
|
+
const content = generateLauncherScript(config);
|
|
660
|
+
await execAsync3(`sudo tee "${OPENCLAW_LAUNCHER_PATH}" > /dev/null << 'LAUNCHEREOF'
|
|
661
|
+
${content}
|
|
662
|
+
LAUNCHEREOF`);
|
|
663
|
+
await execAsync3(`sudo chmod 755 "${OPENCLAW_LAUNCHER_PATH}"`);
|
|
664
|
+
await execAsync3(`sudo chown root:wheel "${OPENCLAW_LAUNCHER_PATH}"`);
|
|
665
|
+
return {
|
|
666
|
+
success: true,
|
|
667
|
+
message: `Launcher script installed at ${OPENCLAW_LAUNCHER_PATH}`
|
|
668
|
+
};
|
|
669
|
+
} catch (error) {
|
|
670
|
+
return {
|
|
671
|
+
success: false,
|
|
672
|
+
message: `Failed to install launcher: ${error.message}`,
|
|
673
|
+
error
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function installOpenClawLaunchDaemons(config) {
|
|
678
|
+
try {
|
|
679
|
+
const launcherResult = await installOpenClawLauncher(config);
|
|
680
|
+
if (!launcherResult.success) {
|
|
681
|
+
return launcherResult;
|
|
682
|
+
}
|
|
683
|
+
const agentUsername = config.agentUsername;
|
|
684
|
+
const socketGroupName = config.socketGroupName;
|
|
685
|
+
await execAsync3(`sudo touch /var/log/agenshield/openclaw-gateway.log /var/log/agenshield/openclaw-gateway.error.log`);
|
|
686
|
+
await execAsync3(`sudo chown ${agentUsername}:${socketGroupName} /var/log/agenshield/openclaw-gateway.log /var/log/agenshield/openclaw-gateway.error.log`);
|
|
687
|
+
try {
|
|
688
|
+
await execAsync3(`sudo launchctl bootout system/${OPENCLAW_DAEMON_LABEL} 2>/dev/null`);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
await execAsync3(`sudo launchctl bootout system/${OPENCLAW_GATEWAY_LABEL} 2>/dev/null`);
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
const gatewayPlist = generateOpenClawGatewayPlist(config);
|
|
696
|
+
await execAsync3(`sudo tee "${OPENCLAW_GATEWAY_PLIST}" > /dev/null << 'PLISTEOF'
|
|
697
|
+
${gatewayPlist}
|
|
698
|
+
PLISTEOF`);
|
|
699
|
+
await execAsync3(`sudo chown root:wheel "${OPENCLAW_GATEWAY_PLIST}"`);
|
|
700
|
+
await execAsync3(`sudo chmod 644 "${OPENCLAW_GATEWAY_PLIST}"`);
|
|
701
|
+
await execAsync3(`sudo launchctl load -w "${OPENCLAW_GATEWAY_PLIST}"`);
|
|
702
|
+
return {
|
|
703
|
+
success: true,
|
|
704
|
+
message: "OpenClaw LaunchDaemons installed and loaded"
|
|
705
|
+
};
|
|
706
|
+
} catch (error) {
|
|
707
|
+
return {
|
|
708
|
+
success: false,
|
|
709
|
+
message: `Failed to install OpenClaw LaunchDaemons: ${error.message}`,
|
|
710
|
+
error
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function startOpenClawServices() {
|
|
715
|
+
try {
|
|
716
|
+
await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
717
|
+
return {
|
|
718
|
+
success: true,
|
|
719
|
+
message: "OpenClaw gateway started"
|
|
720
|
+
};
|
|
721
|
+
} catch (error) {
|
|
722
|
+
return {
|
|
723
|
+
success: false,
|
|
724
|
+
message: `Failed to start OpenClaw gateway: ${error.message}`,
|
|
725
|
+
error
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
async function stopOpenClawServices() {
|
|
730
|
+
try {
|
|
731
|
+
await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
732
|
+
return {
|
|
733
|
+
success: true,
|
|
734
|
+
message: "OpenClaw gateway stopped"
|
|
735
|
+
};
|
|
736
|
+
} catch (error) {
|
|
737
|
+
return {
|
|
738
|
+
success: false,
|
|
739
|
+
message: `Failed to stop OpenClaw gateway: ${error.message}`,
|
|
740
|
+
error
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
async function restartOpenClawServices() {
|
|
745
|
+
try {
|
|
746
|
+
await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
|
|
747
|
+
return {
|
|
748
|
+
success: true,
|
|
749
|
+
message: "OpenClaw gateway restarted"
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
return {
|
|
753
|
+
success: false,
|
|
754
|
+
message: `Failed to restart OpenClaw gateway: ${error.message}`,
|
|
755
|
+
error
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function parseLaunchctlStatus(stdout) {
|
|
760
|
+
const status = { running: false };
|
|
761
|
+
const lines = stdout.split("\n");
|
|
762
|
+
for (const line of lines) {
|
|
763
|
+
if (line.includes('"PID"')) {
|
|
764
|
+
const match = line.match(/"PID"\s*=\s*(\d+)/);
|
|
765
|
+
if (match) {
|
|
766
|
+
status.pid = parseInt(match[1], 10);
|
|
767
|
+
status.running = true;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (line.includes('"LastExitStatus"')) {
|
|
771
|
+
const match = line.match(/"LastExitStatus"\s*=\s*(\d+)/);
|
|
772
|
+
if (match) {
|
|
773
|
+
status.lastExitStatus = parseInt(match[1], 10);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (!status.pid) {
|
|
778
|
+
for (const line of lines) {
|
|
779
|
+
if (line.includes("PID")) {
|
|
780
|
+
const match = line.match(/PID\s*=\s*(\d+)/);
|
|
781
|
+
if (match) {
|
|
782
|
+
status.pid = parseInt(match[1], 10);
|
|
783
|
+
status.running = true;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (line.includes("LastExitStatus")) {
|
|
787
|
+
const match = line.match(/LastExitStatus\s*=\s*(\d+)/);
|
|
788
|
+
if (match) {
|
|
789
|
+
status.lastExitStatus = parseInt(match[1], 10);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return status;
|
|
795
|
+
}
|
|
796
|
+
async function getOpenClawStatus() {
|
|
797
|
+
const result = {
|
|
798
|
+
daemon: { running: false },
|
|
799
|
+
gateway: { running: false }
|
|
800
|
+
};
|
|
801
|
+
try {
|
|
802
|
+
const { stdout } = await execAsync3(`sudo launchctl list ${OPENCLAW_GATEWAY_LABEL} 2>/dev/null`);
|
|
803
|
+
result.gateway = parseLaunchctlStatus(stdout);
|
|
804
|
+
if (!result.gateway.running) {
|
|
805
|
+
result.gateway.running = false;
|
|
806
|
+
}
|
|
807
|
+
} catch {
|
|
808
|
+
}
|
|
809
|
+
return result;
|
|
810
|
+
}
|
|
811
|
+
function getOpenClawStatusSync() {
|
|
812
|
+
const result = {
|
|
813
|
+
daemon: { running: false },
|
|
814
|
+
gateway: { running: false }
|
|
815
|
+
};
|
|
816
|
+
try {
|
|
817
|
+
const stdout = execSync2(`sudo launchctl list ${OPENCLAW_GATEWAY_LABEL} 2>/dev/null`, {
|
|
818
|
+
encoding: "utf-8",
|
|
819
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
820
|
+
});
|
|
821
|
+
result.gateway = parseLaunchctlStatus(stdout);
|
|
822
|
+
} catch {
|
|
823
|
+
}
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
async function getOpenClawDashboardUrl() {
|
|
827
|
+
try {
|
|
828
|
+
const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
|
|
829
|
+
const configPath = path2.join(agentHome, ".openclaw", "openclaw.json");
|
|
830
|
+
let raw;
|
|
831
|
+
try {
|
|
832
|
+
raw = await fs3.readFile(configPath, "utf-8");
|
|
833
|
+
} catch (err) {
|
|
834
|
+
if (err.code === "EACCES") {
|
|
835
|
+
const agentUsername = path2.basename(agentHome);
|
|
836
|
+
const { stdout } = await execAsync3(
|
|
837
|
+
`sudo -H -u ${agentUsername} cat "${configPath}"`,
|
|
838
|
+
{ cwd: "/" }
|
|
839
|
+
);
|
|
840
|
+
raw = stdout;
|
|
841
|
+
} else {
|
|
842
|
+
return { success: false, error: `Cannot read openclaw.json: ${err.message}` };
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const config = JSON.parse(raw);
|
|
846
|
+
const port = config.gateway?.port;
|
|
847
|
+
const token = config.gateway?.auth?.token;
|
|
848
|
+
if (!port || !token) {
|
|
849
|
+
return { success: false, error: "Gateway port or auth token not found in openclaw.json" };
|
|
850
|
+
}
|
|
851
|
+
const url = `https://localhost:${port}/?token=${token}`;
|
|
852
|
+
return { success: true, url };
|
|
853
|
+
} catch (error) {
|
|
854
|
+
return { success: false, error: `Failed to get dashboard URL: ${error.message}` };
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async function isOpenClawInstalled() {
|
|
858
|
+
try {
|
|
859
|
+
await fs3.access(OPENCLAW_GATEWAY_PLIST);
|
|
860
|
+
return true;
|
|
861
|
+
} catch {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
async function uninstallOpenClawLaunchDaemons() {
|
|
866
|
+
try {
|
|
867
|
+
await stopOpenClawServices();
|
|
868
|
+
try {
|
|
869
|
+
await execAsync3(`sudo launchctl bootout system/${OPENCLAW_DAEMON_LABEL} 2>/dev/null`);
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
await execAsync3(`sudo launchctl bootout system/${OPENCLAW_GATEWAY_LABEL} 2>/dev/null`);
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
await execAsync3(`sudo rm -f "${OPENCLAW_DAEMON_PLIST}" "${OPENCLAW_GATEWAY_PLIST}"`);
|
|
877
|
+
await execAsync3(`sudo rm -f "${OPENCLAW_LAUNCHER_PATH}"`);
|
|
878
|
+
return {
|
|
879
|
+
success: true,
|
|
880
|
+
message: "OpenClaw LaunchDaemons uninstalled"
|
|
881
|
+
};
|
|
882
|
+
} catch (error) {
|
|
883
|
+
return {
|
|
884
|
+
success: false,
|
|
885
|
+
message: `Failed to uninstall OpenClaw LaunchDaemons: ${error.message}`,
|
|
886
|
+
error
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
export {
|
|
891
|
+
OPENCLAW_DAEMON_PLIST,
|
|
892
|
+
OPENCLAW_GATEWAY_LABEL,
|
|
893
|
+
OPENCLAW_GATEWAY_PLIST,
|
|
894
|
+
OPENCLAW_LAUNCHER_PATH,
|
|
895
|
+
copyOpenClawConfig,
|
|
896
|
+
detectHostOpenClawVersion,
|
|
897
|
+
generateOpenClawGatewayPlist,
|
|
898
|
+
getHostOpenClawConfigPath,
|
|
899
|
+
getOpenClawDashboardUrl,
|
|
900
|
+
getOpenClawStatus,
|
|
901
|
+
getOpenClawStatusSync,
|
|
902
|
+
getOriginalUser,
|
|
903
|
+
installAgentHomebrew,
|
|
904
|
+
installAgentOpenClaw,
|
|
905
|
+
installOpenClawLaunchDaemons,
|
|
906
|
+
installOpenClawLauncher,
|
|
907
|
+
isAgentHomebrewInstalled,
|
|
908
|
+
isOpenClawInstalled,
|
|
909
|
+
onboardAgentOpenClaw,
|
|
910
|
+
restartOpenClawServices,
|
|
911
|
+
startAgentOpenClawDashboard,
|
|
912
|
+
startAgentOpenClawGateway,
|
|
913
|
+
startOpenClawServices,
|
|
914
|
+
stopHostOpenClaw,
|
|
915
|
+
stopOpenClawServices,
|
|
916
|
+
uninstallOpenClawLaunchDaemons
|
|
917
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Installation for Agent User
|
|
3
|
+
*
|
|
4
|
+
* Handles installing OpenClaw via npm in the agent's sandboxed environment,
|
|
5
|
+
* copying and sanitizing the host user's .openclaw config, and stopping
|
|
6
|
+
* the host user's OpenClaw processes.
|
|
7
|
+
*/
|
|
8
|
+
export interface OpenClawInstallResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
version: string;
|
|
11
|
+
binaryPath: string;
|
|
12
|
+
message: string;
|
|
13
|
+
error?: Error;
|
|
14
|
+
}
|
|
15
|
+
export interface OpenClawConfigCopyResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
configDir: string;
|
|
18
|
+
sanitized: boolean;
|
|
19
|
+
message: string;
|
|
20
|
+
error?: Error;
|
|
21
|
+
}
|
|
22
|
+
export interface StopHostOpenClawResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
daemonStopped: boolean;
|
|
25
|
+
gatewayStopped: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Detect the OpenClaw version installed on the host system.
|
|
30
|
+
* Returns the version string or null if not found.
|
|
31
|
+
*/
|
|
32
|
+
export declare function detectHostOpenClawVersion(): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Install OpenClaw for the agent user via NVM's npm.
|
|
35
|
+
*
|
|
36
|
+
* Uses the agent's NVM environment to run `npm install -g openclaw@<version>`.
|
|
37
|
+
* Falls back to 'latest' if no version specified.
|
|
38
|
+
*/
|
|
39
|
+
export declare function installAgentOpenClaw(options: {
|
|
40
|
+
agentHome: string;
|
|
41
|
+
agentUsername: string;
|
|
42
|
+
socketGroupName: string;
|
|
43
|
+
/** Version to install (from host), or 'latest' */
|
|
44
|
+
targetVersion?: string;
|
|
45
|
+
verbose?: boolean;
|
|
46
|
+
onLog?: (msg: string) => void;
|
|
47
|
+
}): Promise<OpenClawInstallResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Copy the host user's .openclaw config directory to the agent user.
|
|
50
|
+
*
|
|
51
|
+
* Does a full faithful copy using `cp -a` (archive mode) so the entire
|
|
52
|
+
* directory tree is preserved exactly as-is. Then sets ownership to the
|
|
53
|
+
* agent user. The host user's original .openclaw is never modified.
|
|
54
|
+
*/
|
|
55
|
+
export declare function copyOpenClawConfig(options: {
|
|
56
|
+
/** Path to the host user's .openclaw directory (e.g., /Users/david/.openclaw) */
|
|
57
|
+
sourceConfigPath: string;
|
|
58
|
+
/** Agent user's home directory */
|
|
59
|
+
agentHome: string;
|
|
60
|
+
/** Agent username (owns .openclaw and workspace) */
|
|
61
|
+
agentUsername: string;
|
|
62
|
+
/** Socket group name */
|
|
63
|
+
socketGroup: string;
|
|
64
|
+
verbose?: boolean;
|
|
65
|
+
onLog?: (msg: string) => void;
|
|
66
|
+
}): OpenClawConfigCopyResult;
|
|
67
|
+
/**
|
|
68
|
+
* Stop the host user's OpenClaw daemon and gateway processes.
|
|
69
|
+
*
|
|
70
|
+
* 1. Checks for running OpenClaw processes via `ps`
|
|
71
|
+
* 2. Tries graceful stop via `openclaw daemon/gateway stop`
|
|
72
|
+
* 3. Falls back to `kill` if processes are still alive
|
|
73
|
+
* 4. Never fails — all errors are caught and logged
|
|
74
|
+
*/
|
|
75
|
+
export declare function stopHostOpenClaw(options: {
|
|
76
|
+
/** The original user running OpenClaw (e.g., 'david') */
|
|
77
|
+
originalUser: string;
|
|
78
|
+
verbose?: boolean;
|
|
79
|
+
onLog?: (msg: string) => void;
|
|
80
|
+
}): Promise<StopHostOpenClawResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Get the original (host) user who invoked the setup.
|
|
83
|
+
* Uses SUDO_USER env var or falls back to os.userInfo().
|
|
84
|
+
*/
|
|
85
|
+
export declare function getOriginalUser(): string;
|
|
86
|
+
/**
|
|
87
|
+
* Get the host user's .openclaw config path.
|
|
88
|
+
*/
|
|
89
|
+
export declare function getHostOpenClawConfigPath(username?: string): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* Run `openclaw onboard --non-interactive ...` as the agent user to initialize
|
|
92
|
+
* OpenClaw's internal state (session files, local config). Must run after
|
|
93
|
+
* install-openclaw and copy-openclaw-config.
|
|
94
|
+
*/
|
|
95
|
+
export declare function onboardAgentOpenClaw(options: {
|
|
96
|
+
agentHome: string;
|
|
97
|
+
agentUsername: string;
|
|
98
|
+
verbose?: boolean;
|
|
99
|
+
onLog?: (msg: string) => void;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
success: boolean;
|
|
102
|
+
message: string;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Start `openclaw gateway` as the agent user in the background.
|
|
106
|
+
* Returns the PID of the spawned process.
|
|
107
|
+
*
|
|
108
|
+
* Uses `spawn` with `detached: true` so the gateway survives the parent
|
|
109
|
+
* process exiting. Logs go to /var/log/agenshield/openclaw-gateway.log.
|
|
110
|
+
*/
|
|
111
|
+
export declare function startAgentOpenClawGateway(options: {
|
|
112
|
+
agentHome: string;
|
|
113
|
+
agentUsername: string;
|
|
114
|
+
socketGroupName: string;
|
|
115
|
+
verbose?: boolean;
|
|
116
|
+
}): Promise<{
|
|
117
|
+
success: boolean;
|
|
118
|
+
pid?: number;
|
|
119
|
+
message: string;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Start `openclaw dashboard` as the agent user in the background.
|
|
123
|
+
* Returns the PID of the spawned process.
|
|
124
|
+
*/
|
|
125
|
+
export declare function startAgentOpenClawDashboard(options: {
|
|
126
|
+
agentHome: string;
|
|
127
|
+
agentUsername: string;
|
|
128
|
+
socketGroupName: string;
|
|
129
|
+
verbose?: boolean;
|
|
130
|
+
}): Promise<{
|
|
131
|
+
success: boolean;
|
|
132
|
+
pid?: number;
|
|
133
|
+
message: string;
|
|
134
|
+
}>;
|
|
135
|
+
//# sourceMappingURL=openclaw-install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-install.d.ts","sourceRoot":"","sources":["../src/openclaw-install.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoFH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAgBD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,GAAG,IAAI,CASzD;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+EjC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,iFAAiF;IACjF,gBAAgB,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,GAAG,wBAAwB,CA8E3B;AAmCD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA6FlC;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO1E;AAID;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BjD;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2D/D;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA+C/D"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw LaunchDaemon Management
|
|
3
|
+
*
|
|
4
|
+
* Creates and manages macOS LaunchDaemons for OpenClaw daemon and gateway
|
|
5
|
+
* processes, running inside the AgenShield sandbox with intercepted Node.js.
|
|
6
|
+
*
|
|
7
|
+
* Both processes run as the agent user with NODE_OPTIONS set to load the
|
|
8
|
+
* interceptor, ensuring all network and exec operations are monitored.
|
|
9
|
+
*/
|
|
10
|
+
declare const OPENCLAW_GATEWAY_LABEL = "com.agenshield.openclaw.gateway";
|
|
11
|
+
declare const OPENCLAW_DAEMON_PLIST = "/Library/LaunchDaemons/com.agenshield.openclaw.daemon.plist";
|
|
12
|
+
declare const OPENCLAW_GATEWAY_PLIST = "/Library/LaunchDaemons/com.agenshield.openclaw.gateway.plist";
|
|
13
|
+
declare const OPENCLAW_LAUNCHER_PATH = "/opt/agenshield/bin/openclaw-launcher.sh";
|
|
14
|
+
export interface OpenClawLaunchConfig {
|
|
15
|
+
agentUsername: string;
|
|
16
|
+
socketGroupName: string;
|
|
17
|
+
agentHome: string;
|
|
18
|
+
socketPath?: string;
|
|
19
|
+
interceptorPath?: string;
|
|
20
|
+
httpPort?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface OpenClawProcessStatus {
|
|
23
|
+
running: boolean;
|
|
24
|
+
pid?: number;
|
|
25
|
+
lastExitStatus?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface OpenClawStatus {
|
|
28
|
+
daemon: OpenClawProcessStatus;
|
|
29
|
+
gateway: OpenClawProcessStatus;
|
|
30
|
+
}
|
|
31
|
+
export interface OpenClawDaemonResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
message: string;
|
|
34
|
+
error?: Error;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate LaunchDaemon plist for OpenClaw daemon process.
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateOpenClawDaemonPlist(config: OpenClawLaunchConfig): string;
|
|
40
|
+
/**
|
|
41
|
+
* Generate LaunchDaemon plist for OpenClaw gateway process.
|
|
42
|
+
*/
|
|
43
|
+
export declare function generateOpenClawGatewayPlist(config: OpenClawLaunchConfig): string;
|
|
44
|
+
/**
|
|
45
|
+
* Install the openclaw-launcher.sh script to /opt/agenshield/bin/.
|
|
46
|
+
*/
|
|
47
|
+
export declare function installOpenClawLauncher(config: OpenClawLaunchConfig): Promise<OpenClawDaemonResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Install OpenClaw LaunchDaemon plists and launcher script.
|
|
50
|
+
*/
|
|
51
|
+
export declare function installOpenClawLaunchDaemons(config: OpenClawLaunchConfig): Promise<OpenClawDaemonResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Start OpenClaw daemon and gateway services via launchctl.
|
|
54
|
+
*/
|
|
55
|
+
export declare function startOpenClawServices(): Promise<OpenClawDaemonResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Stop OpenClaw daemon and gateway services via launchctl.
|
|
58
|
+
*/
|
|
59
|
+
export declare function stopOpenClawServices(): Promise<OpenClawDaemonResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Restart OpenClaw daemon and gateway services.
|
|
62
|
+
*/
|
|
63
|
+
export declare function restartOpenClawServices(): Promise<OpenClawDaemonResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Get OpenClaw process status (async).
|
|
66
|
+
*/
|
|
67
|
+
export declare function getOpenClawStatus(): Promise<OpenClawStatus>;
|
|
68
|
+
/**
|
|
69
|
+
* Get OpenClaw process status (sync version for use in buildDaemonStatus).
|
|
70
|
+
*/
|
|
71
|
+
export declare function getOpenClawStatusSync(): OpenClawStatus;
|
|
72
|
+
/**
|
|
73
|
+
* Get the OpenClaw dashboard URL by reading gateway config from openclaw.json.
|
|
74
|
+
*
|
|
75
|
+
* Constructs the URL from gateway.port and gateway.auth.token fields.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getOpenClawDashboardUrl(): Promise<{
|
|
78
|
+
success: boolean;
|
|
79
|
+
url?: string;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Check if OpenClaw LaunchDaemon plists are installed.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isOpenClawInstalled(): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Uninstall OpenClaw LaunchDaemons.
|
|
88
|
+
*/
|
|
89
|
+
export declare function uninstallOpenClawLaunchDaemons(): Promise<OpenClawDaemonResult>;
|
|
90
|
+
export { OPENCLAW_GATEWAY_LABEL, OPENCLAW_DAEMON_PLIST, OPENCLAW_GATEWAY_PLIST, OPENCLAW_LAUNCHER_PATH, };
|
|
91
|
+
//# sourceMappingURL=openclaw-launchdaemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-launchdaemon.d.ts","sourceRoot":"","sources":["../src/openclaw-launchdaemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,QAAA,MAAM,sBAAsB,oCAAoC,CAAC;AACjE,QAAA,MAAM,qBAAqB,gEAAgE,CAAC;AAC5F,QAAA,MAAM,sBAAsB,iEAAiE,CAAC;AAC9F,QAAA,MAAM,sBAAsB,6CAA6C,CAAC;AAK1E,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,qBAAqB,CAAC;IAC9B,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AA6CD;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAqDhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAqDjF;AAID;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,CAqB/B;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,CA4C/B;AAID;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAc3E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAc1E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAc7E;AAiDD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAkBjE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAetD;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqC3G;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO5D;AAED;;GAEG;AACH,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,oBAAoB,CAAC,CA8BpF;AAGD,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,GACvB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agenshield/integrations",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "OpenClaw and third-party integration utilities for AgenShield",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"import": "./index.js",
|
|
13
|
+
"default": "./index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^24.0.0",
|
|
19
|
+
"typescript": "^5.9.3"
|
|
20
|
+
}
|
|
21
|
+
}
|