@linkclaw/clawpool 0.2.1 → 0.2.3
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 +28 -0
- package/clawpool.ts +53 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,34 @@ Stored at `~/.clawpool/config.json`:
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
## Host Config Inheritance
|
|
102
|
+
|
|
103
|
+
When creating or restarting an instance, clawpool automatically merges settings from the host machine's OpenClaw installation (if present) into each container:
|
|
104
|
+
|
|
105
|
+
- **`~/.openclaw/openclaw.json`** — model providers, default model, env vars, and other settings are inherited. Gateway-specific settings (`gateway`, `meta`) are excluded and managed by clawpool per-instance.
|
|
106
|
+
- **`~/.openclaw/agents/main/agent/auth-profiles.json`** — OAuth auth profiles (e.g. `openai-codex`) are copied into each container, enabling provider access without per-instance login.
|
|
107
|
+
|
|
108
|
+
If OpenClaw is not installed on the host, instances start with a minimal config (gateway auth only). You can then configure providers via environment variables:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
clawpool create alpha -e "ANTHROPIC_API_KEY=sk-ant-..." -e "OPENAI_API_KEY=sk-..."
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Reusing auth profiles from another machine
|
|
115
|
+
|
|
116
|
+
If you want to copy auth profiles from a remote OpenClaw installation:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# On the machine where OpenClaw is logged in:
|
|
120
|
+
cat ~/.openclaw/agents/main/agent/auth-profiles.json
|
|
121
|
+
|
|
122
|
+
# Copy the output to the clawpool host:
|
|
123
|
+
mkdir -p ~/.openclaw/agents/main/agent
|
|
124
|
+
# paste into ~/.openclaw/agents/main/agent/auth-profiles.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
clawpool will pick up the file on the next `create` or `restart`.
|
|
128
|
+
|
|
101
129
|
## Environment
|
|
102
130
|
|
|
103
131
|
| Variable | Purpose |
|
package/clawpool.ts
CHANGED
|
@@ -63,8 +63,8 @@ function getLanIP(): string {
|
|
|
63
63
|
return "localhost";
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function dashboardURL(host: string, port: number
|
|
67
|
-
return `http://${host}:${port}
|
|
66
|
+
function dashboardURL(host: string, port: number): string {
|
|
67
|
+
return `http://${host}:${port}`;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function humanTime(iso: string): string {
|
|
@@ -154,7 +154,9 @@ async function syncStatus(
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const
|
|
157
|
+
const HOST_OC_DIR = join(homedir(), ".openclaw");
|
|
158
|
+
const HOST_OC_CONFIG = join(HOST_OC_DIR, "openclaw.json");
|
|
159
|
+
const HOST_AUTH_PROFILES = join(HOST_OC_DIR, "agents", "main", "agent", "auth-profiles.json");
|
|
158
160
|
|
|
159
161
|
function loadHostOpenClawConfig(): Record<string, unknown> {
|
|
160
162
|
if (!existsSync(HOST_OC_CONFIG)) return {};
|
|
@@ -188,13 +190,26 @@ function deepMerge(target: Record<string, unknown>, source: Record<string, unkno
|
|
|
188
190
|
return result;
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
function loadHostAuthProfiles(): string | null {
|
|
194
|
+
if (!existsSync(HOST_AUTH_PROFILES)) return null;
|
|
195
|
+
try {
|
|
196
|
+
return require("fs").readFileSync(HOST_AUTH_PROFILES, "utf-8").trim();
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
191
202
|
function buildOpenClawConfig(token: string): string {
|
|
192
203
|
const hostConfig = loadHostOpenClawConfig();
|
|
193
204
|
// Gateway config is always overridden per-instance
|
|
194
205
|
const instanceOverrides: Record<string, unknown> = {
|
|
195
206
|
gateway: {
|
|
196
207
|
auth: { mode: "token", token },
|
|
197
|
-
controlUi: {
|
|
208
|
+
controlUi: {
|
|
209
|
+
dangerouslyAllowHostHeaderOriginFallback: true,
|
|
210
|
+
allowInsecureAuth: true,
|
|
211
|
+
dangerouslyDisableDeviceAuth: true,
|
|
212
|
+
},
|
|
198
213
|
},
|
|
199
214
|
};
|
|
200
215
|
// Strip host gateway settings (we manage those), keep everything else
|
|
@@ -205,18 +220,28 @@ function buildOpenClawConfig(token: string): string {
|
|
|
205
220
|
|
|
206
221
|
function buildRunArgs(config: Config, inst: Instance): string[] {
|
|
207
222
|
const ocConfig = buildOpenClawConfig(inst.gateway_token);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"chown -R node:node /home/node/data",
|
|
215
|
-
`echo '${ocConfig}' > /home/node/data/.openclaw/openclaw.json`,
|
|
216
|
-
`exec su -s /bin/sh node -c 'exec node dist/index.js gateway --allow-unconfigured --port ${inst.port} --bind lan'`,
|
|
217
|
-
].join(" && "),
|
|
223
|
+
const authProfiles = loadHostAuthProfiles();
|
|
224
|
+
|
|
225
|
+
const initSteps = [
|
|
226
|
+
"mkdir -p /home/node/data/.openclaw/agents/main/agent",
|
|
227
|
+
"chown -R node:node /home/node/data",
|
|
228
|
+
`echo '${ocConfig}' > /home/node/data/.openclaw/openclaw.json`,
|
|
218
229
|
];
|
|
219
230
|
|
|
231
|
+
if (authProfiles) {
|
|
232
|
+
// Escape single quotes in JSON for shell embedding
|
|
233
|
+
const escaped = authProfiles.replace(/'/g, "'\\''");
|
|
234
|
+
initSteps.push(
|
|
235
|
+
`echo '${escaped}' > /home/node/data/.openclaw/agents/main/agent/auth-profiles.json`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
initSteps.push(
|
|
240
|
+
`exec su -s /bin/sh node -c 'exec node dist/index.js gateway --allow-unconfigured --port ${inst.port} --bind lan'`
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const entrypoint = ["sh", "-c", initSteps.join(" && ")];
|
|
244
|
+
|
|
220
245
|
const args = [
|
|
221
246
|
"run",
|
|
222
247
|
"-d",
|
|
@@ -227,7 +252,7 @@ function buildRunArgs(config: Config, inst: Instance): string[] {
|
|
|
227
252
|
"--user",
|
|
228
253
|
"root",
|
|
229
254
|
"-p",
|
|
230
|
-
|
|
255
|
+
`127.0.0.1:${inst.port}:${inst.port}`,
|
|
231
256
|
"-v",
|
|
232
257
|
`${inst.volume}:/home/node/data`,
|
|
233
258
|
"-e",
|
|
@@ -325,7 +350,8 @@ async function cmdCreate(config: Config, args: string[]) {
|
|
|
325
350
|
|
|
326
351
|
const host = getLanIP();
|
|
327
352
|
ok(`Instance '${name}' created (port: ${port}, container: ${cid.slice(0, 12)})`);
|
|
328
|
-
console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, port
|
|
353
|
+
console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, port)}`);
|
|
354
|
+
console.log(` ${c.bold("Token:")} ${gatewayToken}`);
|
|
329
355
|
}
|
|
330
356
|
|
|
331
357
|
async function cmdList(config: Config, args: string[]) {
|
|
@@ -350,10 +376,10 @@ async function cmdList(config: Config, args: string[]) {
|
|
|
350
376
|
const host = getLanIP();
|
|
351
377
|
|
|
352
378
|
console.log(
|
|
353
|
-
`${"NAME".padEnd(16)} ${"STATUS".padEnd(10)} ${"PORT".padEnd(7)}
|
|
379
|
+
`${"NAME".padEnd(16)} ${"STATUS".padEnd(10)} ${"PORT".padEnd(7)} ${"TOKEN".padEnd(34)} URL`
|
|
354
380
|
);
|
|
355
381
|
console.log(
|
|
356
|
-
`${"----".padEnd(16)} ${"------".padEnd(10)} ${"----".padEnd(7)}
|
|
382
|
+
`${"----".padEnd(16)} ${"------".padEnd(10)} ${"----".padEnd(7)} ${"-----".padEnd(34)} ---`
|
|
357
383
|
);
|
|
358
384
|
for (const name of names) {
|
|
359
385
|
const inst = config.instances[name];
|
|
@@ -365,10 +391,14 @@ async function cmdList(config: Config, args: string[]) {
|
|
|
365
391
|
: c.yellow(inst.status);
|
|
366
392
|
const url =
|
|
367
393
|
inst.status === "running"
|
|
368
|
-
? dashboardURL(host, inst.port
|
|
394
|
+
? dashboardURL(host, inst.port)
|
|
395
|
+
: c.dim("—");
|
|
396
|
+
const token =
|
|
397
|
+
inst.status === "running"
|
|
398
|
+
? inst.gateway_token
|
|
369
399
|
: c.dim("—");
|
|
370
400
|
console.log(
|
|
371
|
-
`${name.padEnd(16)} ${statusCol.padEnd(21)} ${String(inst.port).padEnd(7)} ${url}`
|
|
401
|
+
`${name.padEnd(16)} ${statusCol.padEnd(21)} ${String(inst.port).padEnd(7)} ${token.padEnd(34)} ${url}`
|
|
372
402
|
);
|
|
373
403
|
}
|
|
374
404
|
}
|
|
@@ -383,7 +413,7 @@ async function cmdStart(config: Config, args: string[]) {
|
|
|
383
413
|
const host = getLanIP();
|
|
384
414
|
const inst = config.instances[name];
|
|
385
415
|
ok(`Instance '${name}' started`);
|
|
386
|
-
console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port
|
|
416
|
+
console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port)}`);
|
|
387
417
|
}
|
|
388
418
|
|
|
389
419
|
async function cmdStop(config: Config, args: string[]) {
|
|
@@ -406,7 +436,7 @@ async function cmdRestart(config: Config, args: string[]) {
|
|
|
406
436
|
const host = getLanIP();
|
|
407
437
|
const inst = config.instances[name];
|
|
408
438
|
ok(`Instance '${name}' restarted`);
|
|
409
|
-
console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port
|
|
439
|
+
console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port)}`);
|
|
410
440
|
}
|
|
411
441
|
|
|
412
442
|
async function cmdDelete(config: Config, args: string[]) {
|
|
@@ -543,7 +573,7 @@ async function cmdStatus(config: Config, args: string[]) {
|
|
|
543
573
|
console.log(`${c.bold("Volume:")} ${inst.volume}`);
|
|
544
574
|
console.log(`${c.bold("Created:")} ${inst.created_at}`);
|
|
545
575
|
if (inst.status === "running") {
|
|
546
|
-
console.log(`${c.bold("Dashboard:")} ${dashboardURL(host, inst.port
|
|
576
|
+
console.log(`${c.bold("Dashboard:")} ${dashboardURL(host, inst.port)}`);
|
|
547
577
|
}
|
|
548
578
|
if (inst.telegram_bot_token) {
|
|
549
579
|
console.log(`${c.bold("Telegram:")} configured`);
|