create-openclaw-bot 5.1.2 → 5.1.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/CHANGELOG.md +11 -0
- package/CHANGELOG.vi.md +11 -0
- package/README.md +3 -3
- package/README.vi.md +3 -3
- package/cli.js +51 -38
- package/package.json +1 -1
- package/setup.js +35 -27
- package/tests/smoke-cli-logic.mjs +25 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Changelog (English)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [5.1.3] — 2026-04-06
|
|
5
|
+
|
|
6
|
+
### 🐜 Fix Docker Compose Variable Interpolation Leak
|
|
7
|
+
|
|
8
|
+
The previous base64 fix introduced a regression where the template literal `${Buffer.from(...)}` was mistakenly escaped in the composition script, causing the literal string to leak into `docker-compose.yml` instead of the actual base64 computed value.
|
|
9
|
+
|
|
10
|
+
- **Fix**: Precompute the base64 string completely in JavaScript (`const syncScriptBase64 = encodeBase64Utf8(syncScript)`) before injecting it into the compose template
|
|
11
|
+
- This guarantees the generated compose file receives the raw base64 string without any template interpolator conflicts
|
|
12
|
+
- Also cleans up testing logic validating these fixes
|
|
13
|
+
|
|
14
|
+
|
|
4
15
|
## [5.1.2] — 2026-04-06
|
|
5
16
|
|
|
6
17
|
### 🐛 Fix Shell Injection: Sync Script Now Uses Base64 Encoding
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Changelog (Tiếng Việt)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [5.1.3] — 2026-04-06
|
|
5
|
+
|
|
6
|
+
### 🐜 Lỗi lọt biến nội suy vào giao diện Docker Compose
|
|
7
|
+
|
|
8
|
+
Bản vá lỗi base64 trước đó đã gây ra lỗi mới (regression) do dùng ngoặc `${Buffer.from(...)}` bên trong chuỗi string sinh ra docker-compose. Điều này làm lọt nguyên đoạn text nội suy vào `docker-compose.yml` thay vì sinh ra chuỗi base64 thật.
|
|
9
|
+
|
|
10
|
+
- **Fix**: Thực hiện tạo mã base64 hoàn chỉnh qua JavaScript (`const syncScriptBase64 = encodeBase64Utf8(syncScript)`) ngay từ ban đầu trước khi ghép chuỗi vào file compose
|
|
11
|
+
- Đảm bảo file compose tạo thành nhận chính xác mã base64 thuần túy mà không bị lọt biến môi trường
|
|
12
|
+
- Dọn dẹp lại script test tương ứng
|
|
13
|
+
|
|
14
|
+
|
|
4
15
|
## [5.1.2] — 2026-04-06
|
|
5
16
|
|
|
6
17
|
### 🐛 Fix Shell Injection: Sync Script Dùng Base64
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.3-0EA5E9?style=for-the-badge" alt="Version 5.1.3" /></a>
|
|
7
7
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
8
8
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
9
9
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -24,7 +24,7 @@ An interactive **CLI tool** and **Setup Wizard** to deploy your own free AI Bot
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## 🆕 What's new in v5.1.
|
|
27
|
+
## 🆕 What's new in v5.1.3
|
|
28
28
|
|
|
29
29
|
- 💻 **OS-First Setup** — Step 1 is now choosing your OS (Windows, macOS, Ubuntu, VPS). All scripts, configs, and instructions are generated to match.
|
|
30
30
|
- 🧠 **Gemma 4 — 4 sizes** — `gemma4:e2b` (~4 GB), `gemma4:e4b` (~8 GB), `gemma4:26b` (~18 GB), `gemma4:31b` (~24 GB). Auto-pulled on first launch.
|
|
@@ -112,7 +112,7 @@ Run in your terminal → follow the interactive prompts → startup script is ge
|
|
|
112
112
|
2. Open this repo as your workspace
|
|
113
113
|
3. Paste into chat:
|
|
114
114
|
```
|
|
115
|
-
Read SETUP.md and set up OpenClaw v5.1.
|
|
115
|
+
Read SETUP.md and set up OpenClaw v5.1.3 for me.
|
|
116
116
|
My bot token is X. Use 9Router (no API key).
|
|
117
117
|
My project folder: <YOUR_PATH>
|
|
118
118
|
```
|
package/README.vi.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.1.3-0EA5E9?style=for-the-badge" alt="Version 5.1.3" /></a>
|
|
7
7
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
8
8
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
9
9
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -24,7 +24,7 @@ Công cụ **CLI tương tác** và **Setup Wizard** để tự triển khai Bot
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## 🆕 Có gì mới trong v5.1.
|
|
27
|
+
## 🆕 Có gì mới trong v5.1.3
|
|
28
28
|
|
|
29
29
|
- 💻 **OS-First Setup** — Bước đầu tiên bây giờ là chọn hệ điều hành của bạn (Windows, macOS, Ubuntu, VPS). Toàn bộ script, cấu hình và hướng dẫn được tạo ra phù hợp với lựa chọn đó.
|
|
30
30
|
- 🧠 **Gemma 4 — 4 kích thước** — `gemma4:e2b` (~4 GB), `gemma4:e4b` (~8 GB), `gemma4:26b` (~18 GB), `gemma4:31b` (~24 GB). Tự pull về khi bot khởi động lần đầu.
|
|
@@ -112,7 +112,7 @@ Chạy lệnh trên trong Terminal → làm theo các prompt tương tác → sc
|
|
|
112
112
|
2. Mở repo này làm workspace
|
|
113
113
|
3. Paste vào chat:
|
|
114
114
|
```
|
|
115
|
-
Read SETUP.md and set up OpenClaw v5.1.
|
|
115
|
+
Read SETUP.md and set up OpenClaw v5.1.3 for me.
|
|
116
116
|
My bot token is X. Use 9Router (no API key).
|
|
117
117
|
My project folder: <THƯ_MỤC_CỦA_BẠN>
|
|
118
118
|
```
|
package/cli.js
CHANGED
|
@@ -312,9 +312,9 @@ function installLatestOpenClaw({ isVi, osChoice }) {
|
|
|
312
312
|
: '✅ openclaw is now on the latest version!'));
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
function build9RouterSmartRouteSyncScript(dbPath) {
|
|
316
|
-
const safeDbPath = JSON.stringify(dbPath);
|
|
317
|
-
return `const fs=require('fs');
|
|
315
|
+
function build9RouterSmartRouteSyncScript(dbPath) {
|
|
316
|
+
const safeDbPath = JSON.stringify(dbPath);
|
|
317
|
+
return `const fs=require('fs');
|
|
318
318
|
const INTERVAL=30000;
|
|
319
319
|
const p=${safeDbPath};
|
|
320
320
|
const ROUTER='http://localhost:20128';
|
|
@@ -359,15 +359,32 @@ const sync = async () => {
|
|
|
359
359
|
}
|
|
360
360
|
} catch(e) { console.log('[sync-combo] Error:', e.message); }
|
|
361
361
|
};
|
|
362
|
-
setTimeout(sync, 5000);
|
|
363
|
-
setInterval(sync, INTERVAL);`;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
362
|
+
setTimeout(sync, 5000);
|
|
363
|
+
setInterval(sync, INTERVAL);`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function indentBlock(text, spaces) {
|
|
367
|
+
const prefix = ' '.repeat(spaces);
|
|
368
|
+
return String(text)
|
|
369
|
+
.split('\n')
|
|
370
|
+
.map((line) => `${prefix}${line}`)
|
|
371
|
+
.join('\n');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function build9RouterComposeEntrypointScript(syncScriptBase64) {
|
|
375
|
+
return [
|
|
376
|
+
'npm install -g 9router',
|
|
377
|
+
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
378
|
+
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
379
|
+
'exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update'
|
|
380
|
+
].join('\n');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function writeNative9RouterSyncScript(projectDir) {
|
|
384
|
+
const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
|
|
385
|
+
await fs.ensureDir(path.dirname(syncScriptPath));
|
|
386
|
+
await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json')));
|
|
387
|
+
return syncScriptPath;
|
|
371
388
|
}
|
|
372
389
|
|
|
373
390
|
function extractFirstHttpUrl(text) {
|
|
@@ -1260,10 +1277,12 @@ async function main() {
|
|
|
1260
1277
|
const agentId = botName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-$/, '') || 'chat';
|
|
1261
1278
|
|
|
1262
1279
|
// ─── Dynamic Smart Route Sync Script ────────────────────────────────────────
|
|
1263
|
-
// This script runs inside the 9Router container as a background loop.
|
|
1264
|
-
// It reads the persisted 9Router DB directly so smart-route still works
|
|
1265
|
-
// even when newer dashboard APIs require auth or change response shape.
|
|
1266
|
-
const syncComboScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
|
|
1280
|
+
// This script runs inside the 9Router container as a background loop.
|
|
1281
|
+
// It reads the persisted 9Router DB directly so smart-route still works
|
|
1282
|
+
// even when newer dashboard APIs require auth or change response shape.
|
|
1283
|
+
const syncComboScript = build9RouterSmartRouteSyncScript('/root/.9router/db.json');
|
|
1284
|
+
const syncComboScriptBase64 = Buffer.from(syncComboScript).toString('base64');
|
|
1285
|
+
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncComboScriptBase64);
|
|
1267
1286
|
|
|
1268
1287
|
// ─── Resolve primary model ───────────────────────────────────────────────────
|
|
1269
1288
|
let modelsPrimary;
|
|
@@ -1307,17 +1326,14 @@ ${dependsOn}${extraHosts} ports:
|
|
|
1307
1326
|
image: node:22-slim
|
|
1308
1327
|
container_name: 9router-multibot
|
|
1309
1328
|
restart: always
|
|
1310
|
-
entrypoint:
|
|
1311
|
-
- /bin/sh
|
|
1312
|
-
- -c
|
|
1313
|
-
- |
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
environment:
|
|
1319
|
-
- PORT=20128
|
|
1320
|
-
- HOSTNAME=0.0.0.0
|
|
1329
|
+
entrypoint:
|
|
1330
|
+
- /bin/sh
|
|
1331
|
+
- -c
|
|
1332
|
+
- |
|
|
1333
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
1334
|
+
environment:
|
|
1335
|
+
- PORT=20128
|
|
1336
|
+
- HOSTNAME=0.0.0.0
|
|
1321
1337
|
- CI=true
|
|
1322
1338
|
volumes:
|
|
1323
1339
|
- 9router-data:/root/.9router
|
|
@@ -1402,17 +1418,14 @@ ${hasBrowserDesktop ? ` extra_hosts:\n - "host.docker.internal:host-gate
|
|
|
1402
1418
|
image: node:22-slim
|
|
1403
1419
|
container_name: 9router-${agentId}
|
|
1404
1420
|
restart: always
|
|
1405
|
-
entrypoint:
|
|
1406
|
-
- /bin/sh
|
|
1407
|
-
- -c
|
|
1408
|
-
- |
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
environment:
|
|
1414
|
-
- PORT=20128
|
|
1415
|
-
- HOSTNAME=0.0.0.0
|
|
1421
|
+
entrypoint:
|
|
1422
|
+
- /bin/sh
|
|
1423
|
+
- -c
|
|
1424
|
+
- |
|
|
1425
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
1426
|
+
environment:
|
|
1427
|
+
- PORT=20128
|
|
1428
|
+
- HOSTNAME=0.0.0.0
|
|
1416
1429
|
- CI=true
|
|
1417
1430
|
volumes:
|
|
1418
1431
|
- 9router-data:/root/.9router
|
package/package.json
CHANGED
package/setup.js
CHANGED
|
@@ -1758,7 +1758,19 @@ model:
|
|
|
1758
1758
|
// 3. Dockerfile
|
|
1759
1759
|
const allPlugins = [];
|
|
1760
1760
|
if (ch.pluginInstall) allPlugins.push(ch.pluginInstall);
|
|
1761
|
-
|
|
1761
|
+
const encodeBase64Utf8 = (value) => btoa(String.fromCharCode(...new TextEncoder().encode(String(value))));
|
|
1762
|
+
const indentBlock = (text, spaces) => {
|
|
1763
|
+
const prefix = ' '.repeat(spaces);
|
|
1764
|
+
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
1765
|
+
};
|
|
1766
|
+
const build9RouterComposeEntrypointScript = (syncScriptBase64) => [
|
|
1767
|
+
'npm install -g 9router',
|
|
1768
|
+
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
1769
|
+
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
1770
|
+
'exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update'
|
|
1771
|
+
].join('\n');
|
|
1772
|
+
|
|
1773
|
+
state.config.plugins.forEach((pid) => {
|
|
1762
1774
|
const plug = PLUGINS.find((p) => p.id === pid);
|
|
1763
1775
|
if (plug) allPlugins.push(plug.package);
|
|
1764
1776
|
});
|
|
@@ -1834,7 +1846,7 @@ ${finalCmd}`;
|
|
|
1834
1846
|
// Background loop inside 9Router container every 30s.
|
|
1835
1847
|
// Read providerConnections directly from db.json so smart-route survives
|
|
1836
1848
|
// dashboard auth/response changes in newer 9Router builds.
|
|
1837
|
-
const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
|
|
1849
|
+
const syncScript = `const fs=require('fs');const INTERVAL=30000;const p='/root/.9router/db.json';
|
|
1838
1850
|
const PM={codex:['cx/gpt-5.4','cx/gpt-5.3-codex','cx/gpt-5.3-codex-high','cx/gpt-5.2-codex','cx/gpt-5.2','cx/gpt-5.1-codex-max','cx/gpt-5.1-codex','cx/gpt-5.1','cx/gpt-5-codex'],'claude-code':['cc/claude-opus-4-6','cc/claude-sonnet-4-6','cc/claude-opus-4-5-20251101','cc/claude-sonnet-4-5-20250929','cc/claude-haiku-4-5-20251001'],github:['gh/gpt-5.4','gh/gpt-5.3-codex','gh/gpt-5.2-codex','gh/gpt-5.2','gh/gpt-5.1-codex-max','gh/gpt-5.1-codex','gh/gpt-5.1','gh/gpt-5','gh/gpt-4.1','gh/gpt-4o','gh/claude-opus-4.6','gh/claude-sonnet-4.6','gh/claude-sonnet-4.5','gh/claude-opus-4.5','gh/claude-haiku-4.5','gh/gemini-3-pro-preview','gh/gemini-3-flash-preview','gh/gemini-2.5-pro'],cursor:['cu/default','cu/claude-4.6-opus-max','cu/claude-4.5-opus-high-thinking','cu/claude-4.5-sonnet-thinking','cu/claude-4.5-sonnet','cu/gpt-5.3-codex','cu/gpt-5.2-codex','cu/gemini-3-flash-preview'],kilo:['kc/anthropic/claude-sonnet-4-20250514','kc/anthropic/claude-opus-4-20250514','kc/google/gemini-2.5-pro','kc/google/gemini-2.5-flash','kc/openai/gpt-4.1','kc/deepseek/deepseek-chat'],cline:['cl/anthropic/claude-sonnet-4.6','cl/anthropic/claude-opus-4.6','cl/openai/gpt-5.3-codex','cl/openai/gpt-5.4','cl/google/gemini-3.1-pro-preview'],'gemini-cli':['gc/gemini-3-flash-preview','gc/gemini-3-pro-preview'],iflow:['if/qwen3-coder-plus','if/kimi-k2','if/kimi-k2-thinking','if/glm-4.7','if/deepseek-r1','if/deepseek-v3.2','if/deepseek-v3','if/qwen3-max','if/qwen3-235b','if/iflow-rome-30ba3b'],qwen:['qw/qwen3-coder-plus','qw/qwen3-coder-flash','qw/vision-model','qw/coder-model'],kiro:['kr/claude-sonnet-4.5','kr/claude-haiku-4.5','kr/deepseek-3.2','kr/deepseek-3.1','kr/qwen3-coder-next'],ollama:['ollama/gemma4:e2b','ollama/gemma4:e4b','ollama/gemma4:26b','ollama/gemma4:31b','ollama/qwen3.5','ollama/kimi-k2.5','ollama/glm-5','ollama/glm-4.7-flash','ollama/minimax-m2.5','ollama/gpt-oss:120b'],'kimi-coding':['kmc/kimi-k2.5','kmc/kimi-k2.5-thinking','kmc/kimi-latest'],glm:['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],'glm-cn':['glm/glm-5.1','glm/glm-5','glm/glm-4.7'],minimax:['minimax/MiniMax-M2.7','minimax/MiniMax-M2.5','minimax/MiniMax-M2.1'],kimi:['kimi/kimi-k2.5','kimi/kimi-k2.5-thinking','kimi/kimi-latest'],deepseek:['deepseek/deepseek-chat','deepseek/deepseek-reasoner'],xai:['xai/grok-4','xai/grok-4-fast-reasoning','xai/grok-code-fast-1'],mistral:['mistral/mistral-large-latest','mistral/codestral-latest'],groq:['groq/llama-3.3-70b-versatile','groq/openai/gpt-oss-120b'],cerebras:['cerebras/gpt-oss-120b'],alicode:['alicode/qwen3.5-plus','alicode/qwen3-coder-plus'],openai:['openai/gpt-4o','openai/gpt-4.1'],anthropic:['anthropic/claude-sonnet-4','anthropic/claude-haiku-3.5'],gemini:['gemini/gemini-2.5-flash','gemini/gemini-2.5-pro']};
|
|
1839
1851
|
console.log('[sync-combo] 9Router sync loop started...');
|
|
1840
1852
|
const sync = async () => {
|
|
@@ -1884,9 +1896,11 @@ const sync = async () => {
|
|
|
1884
1896
|
console.log('[sync-combo] Created smart-route: ' + c.models.length + ' models');
|
|
1885
1897
|
}
|
|
1886
1898
|
} catch (e) { }
|
|
1887
|
-
};
|
|
1888
|
-
setTimeout(sync, 5000);
|
|
1889
|
-
setInterval(sync, INTERVAL);`;
|
|
1899
|
+
};
|
|
1900
|
+
setTimeout(sync, 5000);
|
|
1901
|
+
setInterval(sync, INTERVAL);`;
|
|
1902
|
+
const syncScriptBase64 = encodeBase64Utf8(syncScript);
|
|
1903
|
+
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64);
|
|
1890
1904
|
|
|
1891
1905
|
let compose;
|
|
1892
1906
|
if (isMultiBotWizard) {
|
|
@@ -1915,17 +1929,14 @@ ${dependsOn}${extraHosts} volumes:
|
|
|
1915
1929
|
image: node:22-slim
|
|
1916
1930
|
container_name: 9router-multibot
|
|
1917
1931
|
restart: always
|
|
1918
|
-
entrypoint:
|
|
1919
|
-
- /bin/sh
|
|
1920
|
-
- -c
|
|
1921
|
-
- |
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
environment:
|
|
1927
|
-
- PORT=20128
|
|
1928
|
-
- HOSTNAME=0.0.0.0
|
|
1932
|
+
entrypoint:
|
|
1933
|
+
- /bin/sh
|
|
1934
|
+
- -c
|
|
1935
|
+
- |
|
|
1936
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
1937
|
+
environment:
|
|
1938
|
+
- PORT=20128
|
|
1939
|
+
- HOSTNAME=0.0.0.0
|
|
1929
1940
|
- CI=true
|
|
1930
1941
|
volumes:
|
|
1931
1942
|
- 9router-data:/root/.9router
|
|
@@ -2011,17 +2022,14 @@ ${extraHostsBlock}
|
|
|
2011
2022
|
image: node:22-slim
|
|
2012
2023
|
container_name: 9router
|
|
2013
2024
|
restart: always
|
|
2014
|
-
entrypoint:
|
|
2015
|
-
- /bin/sh
|
|
2016
|
-
- -c
|
|
2017
|
-
- |
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
environment:
|
|
2023
|
-
- PORT=20128
|
|
2024
|
-
- HOSTNAME=0.0.0.0
|
|
2025
|
+
entrypoint:
|
|
2026
|
+
- /bin/sh
|
|
2027
|
+
- -c
|
|
2028
|
+
- |
|
|
2029
|
+
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
2030
|
+
environment:
|
|
2031
|
+
- PORT=20128
|
|
2032
|
+
- HOSTNAME=0.0.0.0
|
|
2025
2033
|
- CI=true
|
|
2026
2034
|
volumes:
|
|
2027
2035
|
- 9router-data:/root/.9router
|
|
@@ -86,9 +86,12 @@ checks.push(() => expectMatch(
|
|
|
86
86
|
'CLI fast-test mode must be able to reuse an existing 9Router install'
|
|
87
87
|
));
|
|
88
88
|
|
|
89
|
-
checks.push(() =>
|
|
90
|
-
cli
|
|
91
|
-
|
|
89
|
+
checks.push(() => expect(
|
|
90
|
+
cli.includes('function build9RouterSmartRouteSyncScript(dbPath) {')
|
|
91
|
+
&& cli.includes('const safeDbPath = JSON.stringify(dbPath);')
|
|
92
|
+
&& cli.includes("const ROUTER='http://localhost:20128';")
|
|
93
|
+
&& cli.includes("fetch(ROUTER + '/api/providers')")
|
|
94
|
+
&& cli.includes("build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json'))"),
|
|
92
95
|
'Native 9Router flow must write a smart-route sync script based on the platform-specific 9Router data directory'
|
|
93
96
|
));
|
|
94
97
|
|
|
@@ -100,8 +103,9 @@ checks.push(() => expectMatch(
|
|
|
100
103
|
|
|
101
104
|
checks.push(() => expect(
|
|
102
105
|
cli.includes("Removed smart-route (no active providers)")
|
|
103
|
-
&& cli.includes("if(!a.length){
|
|
104
|
-
&& cli.includes("if(!m.length){
|
|
106
|
+
&& cli.includes("if (!a.length) {")
|
|
107
|
+
&& cli.includes("if (!m.length) {")
|
|
108
|
+
&& cli.includes("removeSmartRoute();"),
|
|
105
109
|
'9Router sync logic in CLI must remove stale smart-route combos when providers are disabled'
|
|
106
110
|
));
|
|
107
111
|
|
|
@@ -282,20 +286,21 @@ checks.push(() => expectMatch(
|
|
|
282
286
|
|
|
283
287
|
checks.push(() => expectMatch(
|
|
284
288
|
setup,
|
|
285
|
-
/function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*
|
|
289
|
+
/function providerLines\(arr, shell\) \{[\s\S]*npm install -g 9router[\s\S]*start "9Router" cmd \/k "9router -n -t -l -H 0\.0\.0\.0 -p 20128 --skip-update"[\s\S]*9router-smart-route-sync\.js/s,
|
|
286
290
|
'Native script generation must install and start a standalone 9Router dashboard on port 20128'
|
|
287
291
|
));
|
|
288
292
|
|
|
289
293
|
checks.push(() => expectMatch(
|
|
290
294
|
setup,
|
|
291
|
-
/function native9RouterSyncScriptContent\(\) \{[\s\S]*process\.
|
|
292
|
-
'Native script generation must embed a 9Router smart-route sync script
|
|
295
|
+
/function native9RouterSyncScriptContent\(\) \{[\s\S]*path\.join\(process\.env\.HOME\|\|process\.env\.USERPROFILE\|\|'\.'\,\'\.9router\'\,\'db\.json\'\)[\s\S]*providerConnections[\s\S]*smart-route/s,
|
|
296
|
+
'Native script generation must embed a 9Router smart-route sync script'
|
|
293
297
|
));
|
|
294
298
|
|
|
295
299
|
checks.push(() => expect(
|
|
296
300
|
setup.includes("Removed smart-route (no active providers)")
|
|
297
|
-
&& setup.includes("if(!a.length){
|
|
298
|
-
&& setup.includes("if(!m.length){
|
|
301
|
+
&& setup.includes("if (!a.length) {")
|
|
302
|
+
&& setup.includes("if (!m.length) {")
|
|
303
|
+
&& setup.includes("removeSmartRoute();"),
|
|
299
304
|
'9Router sync logic in setup.js must remove stale smart-route combos when providers are disabled'
|
|
300
305
|
));
|
|
301
306
|
|
|
@@ -311,12 +316,22 @@ checks.push(() => expectMatch(
|
|
|
311
316
|
'Dockerfile patching in CLI must resolve gateway-cli dist files dynamically instead of hardcoding one hash'
|
|
312
317
|
));
|
|
313
318
|
|
|
319
|
+
checks.push(() => expect(
|
|
320
|
+
!cli.includes("Buffer.from('\\${Buffer.from(syncComboScript).toString('base64')}','base64')"),
|
|
321
|
+
'CLI must precompute the 9Router sync script base64 instead of leaking Docker Compose interpolation markers'
|
|
322
|
+
));
|
|
323
|
+
|
|
314
324
|
checks.push(() => expectMatch(
|
|
315
325
|
setup,
|
|
316
326
|
/readdirSync\(dir\)\.find\(n=>\/\^gateway-cli-.*\\\\\.js\$\/\.test\(n\)\)[\s\S]*skipping timeout patch/,
|
|
317
327
|
'Dockerfile patching in setup.js must resolve gateway-cli dist files dynamically instead of hardcoding one hash'
|
|
318
328
|
));
|
|
319
329
|
|
|
330
|
+
checks.push(() => expect(
|
|
331
|
+
!setup.includes("Buffer.from('\\${Buffer.from(syncScript).toString('base64')}','base64')"),
|
|
332
|
+
'Wizard compose generation must precompute the 9Router sync script base64 instead of leaking Docker Compose interpolation markers'
|
|
333
|
+
));
|
|
334
|
+
|
|
320
335
|
checks.push(() => expectMatch(
|
|
321
336
|
setup,
|
|
322
337
|
/else if \(state\.nativeOs === 'vps'\) \{[\s\S]*pm2 start --name openclaw-multibot -- sh -c "openclaw gateway run"[\s\S]*pm2 logs openclaw-multibot/s,
|