@vellumai/cli 0.11.8-staging.2 → 0.11.9-staging.1
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/README.md +6 -7
- package/knip.json +0 -1
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/guardian-token-refresh.test.ts +2 -8
- package/node_modules/@vellumai/local-mode/src/__tests__/hatch.test.ts +2 -8
- package/node_modules/@vellumai/local-mode/src/__tests__/helpers/child-process-mock.ts +55 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/retire.test.ts +2 -8
- package/node_modules/@vellumai/local-mode/src/__tests__/upgrade.test.ts +2 -8
- package/node_modules/@vellumai/local-mode/src/__tests__/wake.test.ts +2 -8
- package/node_modules/@vellumai/local-mode/src/devices.test.ts +5 -8
- package/node_modules/@vellumai/local-mode/src/index.ts +1 -0
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +6 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +165 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.ts +63 -6
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/package.json +1 -1
- package/src/__tests__/assistant-api-key-resolution.test.ts +102 -0
- package/src/__tests__/constants.test.ts +6 -2
- package/src/commands/hatch.ts +1 -367
- package/src/commands/login.ts +16 -29
- package/src/commands/teleport.ts +14 -26
- package/src/lib/assistant-api-key-resolution.ts +89 -0
- package/src/lib/aws.ts +0 -593
- package/src/lib/constants.ts +0 -2
- package/src/lib/gcp.ts +3 -671
- package/src/lib/platform-client.ts +58 -5
- package/src/__tests__/ssh-user-guard.test.ts +0 -28
- package/src/adapters/openclaw-http-server.ts +0 -209
- package/src/adapters/openclaw.ts +0 -131
- package/src/lib/openclaw-runtime-server.ts +0 -28
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PlatformCredentialVerificationStatus,
|
|
3
|
+
PlatformVerifyCredentialResponseSchema,
|
|
4
|
+
} from "@vellumai/service-contracts/platform-credential";
|
|
1
5
|
import {
|
|
2
6
|
chmodSync,
|
|
3
7
|
readFileSync,
|
|
@@ -289,7 +293,10 @@ export interface ReprovisionApiKeyResponse {
|
|
|
289
293
|
* Reprovision (rotate) the API key for a self-hosted local assistant.
|
|
290
294
|
*
|
|
291
295
|
* Calls `POST /v1/assistants/self-hosted-local/reprovision-api-key/`.
|
|
292
|
-
* Returns a fresh API key. The previous key
|
|
296
|
+
* Returns a fresh API key. The platform keeps the previous key valid for a
|
|
297
|
+
* grace period rather than revoking it on the spot, so a rotation whose
|
|
298
|
+
* replacement never gets stored leaves the assistant on a key that still
|
|
299
|
+
* works until that window closes.
|
|
293
300
|
*/
|
|
294
301
|
export async function reprovisionAssistantApiKey(
|
|
295
302
|
token: string,
|
|
@@ -349,6 +356,51 @@ export interface GatewayCredentialResult {
|
|
|
349
356
|
unreachable: boolean;
|
|
350
357
|
}
|
|
351
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Ask a running assistant to check its stored managed credential against the
|
|
361
|
+
* platform, via the gateway-proxied `POST /v1/platform/verify-credential`.
|
|
362
|
+
*
|
|
363
|
+
* Returns `"rejected"` only when the platform settled that answer. Every other
|
|
364
|
+
* case, including a daemon too old to serve the route and any failure to reach
|
|
365
|
+
* one, returns null: the answer decides whether a key is rotated, so an absent
|
|
366
|
+
* answer must never read as a rejection.
|
|
367
|
+
*
|
|
368
|
+
* Never throws.
|
|
369
|
+
*/
|
|
370
|
+
export async function verifyGatewayManagedCredential(
|
|
371
|
+
gatewayUrl: string,
|
|
372
|
+
bearerToken?: string,
|
|
373
|
+
): Promise<PlatformCredentialVerificationStatus | null> {
|
|
374
|
+
try {
|
|
375
|
+
const headers: Record<string, string> = { Accept: "application/json" };
|
|
376
|
+
if (bearerToken) {
|
|
377
|
+
headers["Authorization"] = `Bearer ${bearerToken}`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const response = await loopbackSafeFetch(
|
|
381
|
+
`${gatewayUrl}/v1/platform/verify-credential`,
|
|
382
|
+
{
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers,
|
|
385
|
+
signal: AbortSignal.timeout(10_000),
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
if (!response.ok) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const parsed = PlatformVerifyCredentialResponseSchema.safeParse(
|
|
393
|
+
await response.json(),
|
|
394
|
+
);
|
|
395
|
+
if (parsed.success) {
|
|
396
|
+
return parsed.data.status;
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
} catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
352
404
|
/**
|
|
353
405
|
* Read an existing credential from the assistant's secret store via the
|
|
354
406
|
* gateway-proxied `POST /v1/secrets/read` endpoint (with `reveal: true`).
|
|
@@ -356,9 +408,10 @@ export interface GatewayCredentialResult {
|
|
|
356
408
|
* Returns a result distinguishing "key not found" (`value: null,
|
|
357
409
|
* unreachable: false`) from "gateway unreachable" (`value: null,
|
|
358
410
|
* unreachable: true`). Callers should only reprovision when the gateway
|
|
359
|
-
* is reachable
|
|
360
|
-
* the gateway is down
|
|
361
|
-
*
|
|
411
|
+
* is reachable: the platform keeps the outgoing key valid for a grace
|
|
412
|
+
* period, so reprovisioning while the gateway is down does not break the
|
|
413
|
+
* assistant immediately, but it starts a clock on a key the caller cannot
|
|
414
|
+
* yet replace.
|
|
362
415
|
*
|
|
363
416
|
* Never throws.
|
|
364
417
|
*/
|
|
@@ -385,7 +438,7 @@ export async function readGatewayCredential(
|
|
|
385
438
|
|
|
386
439
|
if (!response.ok) {
|
|
387
440
|
// 5xx means the gateway/daemon backend is down — treat as unreachable
|
|
388
|
-
// so callers don't
|
|
441
|
+
// so callers don't rotate away from a working key they cannot replace.
|
|
389
442
|
return { value: null, unreachable: response.status >= 500 };
|
|
390
443
|
}
|
|
391
444
|
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { readFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
// Regression guard for PR #25292: hatchAws/hatchGcp must abort before
|
|
6
|
-
// launching a cloud instance when sshUser is empty. Otherwise `useradd ""`
|
|
7
|
-
// in the generated startup script fails after billable resources are live.
|
|
8
|
-
describe("sshUser empty-string guard", () => {
|
|
9
|
-
const files = [
|
|
10
|
-
join(import.meta.dir, "..", "lib", "aws.ts"),
|
|
11
|
-
join(import.meta.dir, "..", "lib", "gcp.ts"),
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
for (const file of files) {
|
|
15
|
-
test(`${file.split("/").slice(-2).join("/")} aborts on empty sshUser`, () => {
|
|
16
|
-
const source = readFileSync(file, "utf8");
|
|
17
|
-
const fallbackIdx = source.indexOf('sshUser = process.env.USER ?? ""');
|
|
18
|
-
expect(fallbackIdx).toBeGreaterThan(-1);
|
|
19
|
-
|
|
20
|
-
const afterFallback = source.slice(fallbackIdx);
|
|
21
|
-
const guardIdx = afterFallback.search(/if\s*\(\s*!sshUser\s*\)/);
|
|
22
|
-
expect(guardIdx).toBeGreaterThan(-1);
|
|
23
|
-
|
|
24
|
-
const guardBlock = afterFallback.slice(guardIdx, guardIdx + 400);
|
|
25
|
-
expect(guardBlock).toContain("process.exit(1)");
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
});
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import { execSync, spawn } from "node:child_process";
|
|
3
|
-
import http from "node:http";
|
|
4
|
-
|
|
5
|
-
const PORT = parseInt(process.env.PORT || "7830", 10);
|
|
6
|
-
|
|
7
|
-
interface StoredMessage {
|
|
8
|
-
id: string;
|
|
9
|
-
role: "user" | "assistant";
|
|
10
|
-
content: string;
|
|
11
|
-
timestamp: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const messages: Record<string, StoredMessage[]> = {};
|
|
15
|
-
|
|
16
|
-
function parseBody(
|
|
17
|
-
req: http.IncomingMessage,
|
|
18
|
-
): Promise<Record<string, unknown>> {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
let body = "";
|
|
21
|
-
req.on("data", (chunk: Buffer) => (body += chunk.toString()));
|
|
22
|
-
req.on("end", () => {
|
|
23
|
-
try {
|
|
24
|
-
resolve(JSON.parse(body));
|
|
25
|
-
} catch (e) {
|
|
26
|
-
reject(e);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
req.on("error", reject);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function log(method: string, path: string, status: number): void {
|
|
34
|
-
const ts = new Date().toISOString();
|
|
35
|
-
console.log(`[${ts}] ${method} ${path} -> ${status}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const server = http.createServer(async (req, res) => {
|
|
39
|
-
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
40
|
-
const method = req.method ?? "UNKNOWN";
|
|
41
|
-
|
|
42
|
-
res.setHeader("Content-Type", "application/json");
|
|
43
|
-
|
|
44
|
-
if (req.method === "POST" && url.pathname === "/upgrade") {
|
|
45
|
-
try {
|
|
46
|
-
const npmEnv = {
|
|
47
|
-
...process.env,
|
|
48
|
-
PATH: `${process.env.HOME || "/root"}/.npm-global/bin:${process.env.HOME || "/root"}/.local/bin:/usr/local/bin:${process.env.PATH}`,
|
|
49
|
-
};
|
|
50
|
-
execSync("npm install -g @vellum/openclaw-adapter@latest", {
|
|
51
|
-
encoding: "utf-8",
|
|
52
|
-
timeout: 120000,
|
|
53
|
-
env: npmEnv,
|
|
54
|
-
windowsHide: true,
|
|
55
|
-
});
|
|
56
|
-
const child = spawn("vellum-openclaw-adapter", [], {
|
|
57
|
-
detached: true,
|
|
58
|
-
stdio: "ignore",
|
|
59
|
-
env: npmEnv,
|
|
60
|
-
windowsHide: true,
|
|
61
|
-
});
|
|
62
|
-
child.unref();
|
|
63
|
-
const responseBody = JSON.stringify({
|
|
64
|
-
status: "success",
|
|
65
|
-
message:
|
|
66
|
-
"HTTPS adapter installed and started. HTTP adapter shutting down.",
|
|
67
|
-
});
|
|
68
|
-
res.writeHead(200);
|
|
69
|
-
res.end(responseBody, () => {
|
|
70
|
-
log(method, url.pathname, 200);
|
|
71
|
-
server.close(() => process.exit(0));
|
|
72
|
-
});
|
|
73
|
-
} catch (e) {
|
|
74
|
-
const responseBody = JSON.stringify({
|
|
75
|
-
status: "error",
|
|
76
|
-
message: e instanceof Error ? e.message : String(e),
|
|
77
|
-
});
|
|
78
|
-
res.writeHead(500);
|
|
79
|
-
res.end(responseBody);
|
|
80
|
-
log(method, url.pathname, 500);
|
|
81
|
-
}
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (req.method === "GET" && url.pathname === "/healthz") {
|
|
86
|
-
const execEnv = {
|
|
87
|
-
...process.env,
|
|
88
|
-
PATH: `${process.env.HOME || "/root"}/.npm-global/bin:${process.env.HOME || "/root"}/.local/bin:/usr/local/bin:${process.env.PATH}`,
|
|
89
|
-
};
|
|
90
|
-
let responseBody: string;
|
|
91
|
-
try {
|
|
92
|
-
const output = execSync("openclaw health --json", {
|
|
93
|
-
encoding: "utf-8",
|
|
94
|
-
timeout: 10000,
|
|
95
|
-
env: execEnv,
|
|
96
|
-
windowsHide: true,
|
|
97
|
-
});
|
|
98
|
-
const health = JSON.parse(output.trim()) as Record<string, unknown>;
|
|
99
|
-
const result: Record<string, unknown> = {
|
|
100
|
-
status: health.status ?? "healthy",
|
|
101
|
-
message: health.message,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const healthStr = JSON.stringify(health);
|
|
105
|
-
if (
|
|
106
|
-
healthStr.includes("1006") ||
|
|
107
|
-
healthStr.includes("abnormal closure")
|
|
108
|
-
) {
|
|
109
|
-
try {
|
|
110
|
-
const gatewayOutput = execSync("openclaw gateway status", {
|
|
111
|
-
encoding: "utf-8",
|
|
112
|
-
timeout: 10000,
|
|
113
|
-
env: execEnv,
|
|
114
|
-
windowsHide: true,
|
|
115
|
-
});
|
|
116
|
-
result.message = `${result.message}\n\nGateway Status:\n${gatewayOutput.trim()}`;
|
|
117
|
-
} catch (gatewayErr) {
|
|
118
|
-
const gatewayErrMsg =
|
|
119
|
-
gatewayErr instanceof Error
|
|
120
|
-
? gatewayErr.message
|
|
121
|
-
: String(gatewayErr);
|
|
122
|
-
result.message = `${result.message}\n\nGateway Status Error:\n${gatewayErrMsg}`;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
responseBody = JSON.stringify(result);
|
|
127
|
-
res.writeHead(200);
|
|
128
|
-
res.end(responseBody);
|
|
129
|
-
} catch (e) {
|
|
130
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
131
|
-
const result: Record<string, unknown> = {
|
|
132
|
-
status: "unhealthy",
|
|
133
|
-
message: errorMessage,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
errorMessage.includes("1006") ||
|
|
138
|
-
errorMessage.includes("abnormal closure")
|
|
139
|
-
) {
|
|
140
|
-
try {
|
|
141
|
-
const gatewayOutput = execSync("openclaw gateway status", {
|
|
142
|
-
encoding: "utf-8",
|
|
143
|
-
timeout: 10000,
|
|
144
|
-
env: execEnv,
|
|
145
|
-
windowsHide: true,
|
|
146
|
-
});
|
|
147
|
-
result.message = `${result.message}\n\nGateway Status:\n${gatewayOutput.trim()}`;
|
|
148
|
-
} catch (gatewayErr) {
|
|
149
|
-
const gatewayErrMsg =
|
|
150
|
-
gatewayErr instanceof Error
|
|
151
|
-
? gatewayErr.message
|
|
152
|
-
: String(gatewayErr);
|
|
153
|
-
result.message = `${result.message}\n\nGateway Status Error:\n${gatewayErrMsg}`;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
responseBody = JSON.stringify(result);
|
|
158
|
-
res.writeHead(200);
|
|
159
|
-
res.end(responseBody);
|
|
160
|
-
}
|
|
161
|
-
log(method, url.pathname, 200);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const messagesMatch = url.pathname.match(
|
|
166
|
-
/^\/v1\/assistants\/([^/]+)\/messages$/,
|
|
167
|
-
);
|
|
168
|
-
if (messagesMatch) {
|
|
169
|
-
const assistantId = messagesMatch[1];
|
|
170
|
-
|
|
171
|
-
if (req.method === "GET") {
|
|
172
|
-
const key = url.searchParams.get("conversationKey") ?? assistantId;
|
|
173
|
-
const msgs = messages[key] ?? [];
|
|
174
|
-
res.writeHead(200);
|
|
175
|
-
res.end(JSON.stringify({ messages: msgs }));
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (req.method === "POST") {
|
|
180
|
-
try {
|
|
181
|
-
const parsed = await parseBody(req);
|
|
182
|
-
const key = (parsed.conversationKey as string) || assistantId;
|
|
183
|
-
if (!messages[key]) messages[key] = [];
|
|
184
|
-
const messageId = crypto.randomUUID();
|
|
185
|
-
messages[key].push({
|
|
186
|
-
id: messageId,
|
|
187
|
-
role: "user",
|
|
188
|
-
content: parsed.content as string,
|
|
189
|
-
timestamp: new Date().toISOString(),
|
|
190
|
-
});
|
|
191
|
-
res.writeHead(200);
|
|
192
|
-
res.end(JSON.stringify({ accepted: true, messageId }));
|
|
193
|
-
} catch {
|
|
194
|
-
res.writeHead(400);
|
|
195
|
-
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
196
|
-
}
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const notFoundBody = JSON.stringify({ error: "Not found" });
|
|
202
|
-
res.writeHead(404);
|
|
203
|
-
res.end(notFoundBody);
|
|
204
|
-
log(method, url.pathname, 404);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
server.listen(PORT, "0.0.0.0", () => {
|
|
208
|
-
console.log(`OpenClaw runtime server listening on port ${PORT}`);
|
|
209
|
-
});
|
package/src/adapters/openclaw.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { GATEWAY_PORT } from "../lib/constants";
|
|
2
|
-
import { buildOpenclawRuntimeServer } from "../lib/openclaw-runtime-server";
|
|
3
|
-
|
|
4
|
-
export async function buildOpenclawStartupScript(
|
|
5
|
-
sshUser: string,
|
|
6
|
-
providerApiKeys: Record<string, string>,
|
|
7
|
-
timestampRedirect: string,
|
|
8
|
-
userSetup: string,
|
|
9
|
-
ownershipFixup: string,
|
|
10
|
-
): Promise<string> {
|
|
11
|
-
const runtimeServer = await buildOpenclawRuntimeServer();
|
|
12
|
-
|
|
13
|
-
return `#!/bin/bash
|
|
14
|
-
set -e
|
|
15
|
-
|
|
16
|
-
${timestampRedirect}
|
|
17
|
-
|
|
18
|
-
trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE" > /var/log/startup-error; fi' EXIT
|
|
19
|
-
${userSetup}
|
|
20
|
-
|
|
21
|
-
export OPENCLAW_NPM_LOGLEVEL=verbose
|
|
22
|
-
export OPENCLAW_NO_ONBOARD=1
|
|
23
|
-
export OPENCLAW_NO_PROMPT=1
|
|
24
|
-
|
|
25
|
-
echo "=== Pre-install diagnostics ==="
|
|
26
|
-
echo "Date: $(date -u)"
|
|
27
|
-
echo "Disk:" && df -h / 2>&1 || true
|
|
28
|
-
echo "Memory:" && free -m 2>&1 || true
|
|
29
|
-
echo "DNS:" && nslookup registry.npmjs.org 2>&1 || true
|
|
30
|
-
echo "Registry ping:" && curl -sSf --max-time 10 https://registry.npmjs.org/-/ping 2>&1 || echo "WARN: npm registry unreachable"
|
|
31
|
-
echo "=== End pre-install diagnostics ==="
|
|
32
|
-
|
|
33
|
-
echo "=== Installing build dependencies ==="
|
|
34
|
-
apt-get update -y
|
|
35
|
-
apt-get install -y build-essential python3 python3-pip git
|
|
36
|
-
pip3 install cmake
|
|
37
|
-
echo "cmake version: $(cmake --version | head -1)"
|
|
38
|
-
echo "=== Build dependencies installed ==="
|
|
39
|
-
|
|
40
|
-
curl -fsSL https://openclaw.ai/install.sh -o /tmp/openclaw-install.sh
|
|
41
|
-
chmod +x /tmp/openclaw-install.sh
|
|
42
|
-
|
|
43
|
-
set +e
|
|
44
|
-
bash /tmp/openclaw-install.sh
|
|
45
|
-
INSTALL_EXIT_CODE=\$?
|
|
46
|
-
set -e
|
|
47
|
-
|
|
48
|
-
if [ \$INSTALL_EXIT_CODE -ne 0 ]; then
|
|
49
|
-
echo "=== OpenClaw install failed (exit code: \$INSTALL_EXIT_CODE) ==="
|
|
50
|
-
echo "=== npm debug logs ==="
|
|
51
|
-
find \$HOME/.npm/_logs -name '*.log' -type f 2>/dev/null | sort | while read -r logfile; do
|
|
52
|
-
echo "--- \$logfile ---"
|
|
53
|
-
tail -n 200 "\$logfile" 2>/dev/null || true
|
|
54
|
-
done
|
|
55
|
-
echo "=== Post-failure diagnostics ==="
|
|
56
|
-
echo "Disk:" && df -h / 2>&1 || true
|
|
57
|
-
echo "Memory:" && free -m 2>&1 || true
|
|
58
|
-
echo "node version:" && node --version 2>&1 || echo "node not found"
|
|
59
|
-
echo "npm version:" && npm --version 2>&1 || echo "npm not found"
|
|
60
|
-
echo "npm config:" && npm config list 2>&1 || true
|
|
61
|
-
echo "cmake version:" && cmake --version 2>&1 || echo "cmake not found"
|
|
62
|
-
echo "PATH: \$PATH"
|
|
63
|
-
echo "=== End diagnostics ==="
|
|
64
|
-
exit \$INSTALL_EXIT_CODE
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
export PATH="\$HOME/.npm-global/bin:\$HOME/.local/bin:/usr/local/bin:\$PATH"
|
|
68
|
-
|
|
69
|
-
if ! command -v openclaw >/dev/null 2>&1; then
|
|
70
|
-
echo "ERROR: openclaw CLI installation failed. The 'openclaw' command is not available."
|
|
71
|
-
echo "PATH: \$PATH"
|
|
72
|
-
echo "which openclaw:" && which openclaw 2>&1 || true
|
|
73
|
-
echo "npm global bin:" && npm bin -g 2>&1 || true
|
|
74
|
-
echo "npm global list:" && npm list -g --depth=0 2>&1 || true
|
|
75
|
-
exit 1
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
export XDG_RUNTIME_DIR="/run/user/\$(id -u)"
|
|
79
|
-
export DBUS_SESSION_BUS_ADDRESS="unix:path=\$XDG_RUNTIME_DIR/bus"
|
|
80
|
-
mkdir -p "\$XDG_RUNTIME_DIR"
|
|
81
|
-
loginctl enable-linger root 2>/dev/null || true
|
|
82
|
-
systemctl --user daemon-reexec 2>/dev/null || true
|
|
83
|
-
|
|
84
|
-
if ! command -v bun >/dev/null 2>&1; then
|
|
85
|
-
echo "=== Installing bun ==="
|
|
86
|
-
if ! command -v unzip >/dev/null 2>&1; then
|
|
87
|
-
echo "Installing unzip (required by bun)..."
|
|
88
|
-
apt-get install -y unzip
|
|
89
|
-
fi
|
|
90
|
-
# Pinned. Keep in sync with .tool-versions.
|
|
91
|
-
curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.11"
|
|
92
|
-
export BUN_INSTALL="\$HOME/.bun"
|
|
93
|
-
export PATH="\$BUN_INSTALL/bin:\$PATH"
|
|
94
|
-
echo "bun version: $(bun --version)"
|
|
95
|
-
echo "=== Bun installed ==="
|
|
96
|
-
else
|
|
97
|
-
echo "bun already installed: $(bun --version)"
|
|
98
|
-
fi
|
|
99
|
-
|
|
100
|
-
set +e
|
|
101
|
-
openclaw gateway install
|
|
102
|
-
GATEWAY_INSTALL_EXIT=\$?
|
|
103
|
-
set -e
|
|
104
|
-
|
|
105
|
-
if [ \$GATEWAY_INSTALL_EXIT -ne 0 ]; then
|
|
106
|
-
echo "WARN: openclaw gateway install exited with \$GATEWAY_INSTALL_EXIT (expected systemd mismatch), continuing with user-level systemd setup"
|
|
107
|
-
fi
|
|
108
|
-
|
|
109
|
-
OPENCLAW_GW_TOKEN=$(openssl rand -hex 32)
|
|
110
|
-
echo -n "\$OPENCLAW_GW_TOKEN" > /tmp/openclaw-gateway-token
|
|
111
|
-
chmod 600 /tmp/openclaw-gateway-token
|
|
112
|
-
|
|
113
|
-
mkdir -p /root/.openclaw
|
|
114
|
-
${Object.entries(providerApiKeys)
|
|
115
|
-
.map(([envVar, value]) => `openclaw config set env.${envVar} "${value}"`)
|
|
116
|
-
.join("\n")}
|
|
117
|
-
openclaw config set agents.defaults.model.primary "anthropic/claude-opus-4-6"
|
|
118
|
-
openclaw config set gateway.auth.token "\$OPENCLAW_GW_TOKEN"
|
|
119
|
-
|
|
120
|
-
echo "=== Starting openclaw gateway at user level ==="
|
|
121
|
-
systemctl --user daemon-reload
|
|
122
|
-
systemctl --user enable --now openclaw-gateway.service
|
|
123
|
-
|
|
124
|
-
export PORT=${GATEWAY_PORT}
|
|
125
|
-
|
|
126
|
-
echo "=== Starting OpenClaw runtime server ==="
|
|
127
|
-
${runtimeServer}
|
|
128
|
-
echo "=== OpenClaw runtime server started ==="
|
|
129
|
-
${ownershipFixup}
|
|
130
|
-
`;
|
|
131
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// Read source file using Bun.file() with string concatenation (not join())
|
|
2
|
-
// so Bun's bundler can statically analyze the path and embed the file
|
|
3
|
-
// in the compiled binary ($bunfs). The file must also be passed via --embed
|
|
4
|
-
// in the bun build --compile invocation.
|
|
5
|
-
|
|
6
|
-
export async function buildOpenclawRuntimeServer(): Promise<string> {
|
|
7
|
-
try {
|
|
8
|
-
const serverSource = await Bun.file(
|
|
9
|
-
import.meta.dir + "/../adapters/openclaw-http-server.ts",
|
|
10
|
-
).text();
|
|
11
|
-
|
|
12
|
-
return `
|
|
13
|
-
cat > /opt/openclaw-runtime-server.ts << 'RUNTIME_SERVER_EOF'
|
|
14
|
-
${serverSource}
|
|
15
|
-
RUNTIME_SERVER_EOF
|
|
16
|
-
|
|
17
|
-
mkdir -p "\$HOME/.vellum"
|
|
18
|
-
nohup bun run /opt/openclaw-runtime-server.ts >> "\$HOME/.vellum/http-gateway.log" 2>&1 &
|
|
19
|
-
echo "OpenClaw runtime server started (PID: \$!)"
|
|
20
|
-
`;
|
|
21
|
-
} catch (err) {
|
|
22
|
-
console.warn(
|
|
23
|
-
"⚠️ Could not embed openclaw runtime server (expected in compiled binary without --embed):",
|
|
24
|
-
(err as Error).message,
|
|
25
|
-
);
|
|
26
|
-
return "# openclaw-runtime-server: skipped (source files not available in compiled binary)";
|
|
27
|
-
}
|
|
28
|
-
}
|