create-openclaw-bot 5.1.1 → 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 CHANGED
@@ -1,6 +1,29 @@
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
+
15
+ ## [5.1.2] — 2026-04-06
16
+
17
+ ### 🐛 Fix Shell Injection: Sync Script Now Uses Base64 Encoding
18
+
19
+ The `node -e "...JSON.stringify(script)..."` approach caused `/bin/sh: Syntax error: "(" unexpected` because `JSON.stringify` produces a double-quoted string that breaks out of the surrounding `node -e "..."` shell argument.
20
+
21
+ - **Fix**: sync script content is now **base64-encoded at compose-generation time** using `Buffer.from(script).toString('base64')`
22
+ - The generated entrypoint becomes: `node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('<b64>','base64').toString())"`
23
+ - Base64 output contains only `[A-Za-z0-9+/=]` — zero shell quoting issues, works in YAML `|` blocks without escaping
24
+ - Applies to all compose generation paths: Docker web wizard (`setup.js` × 2) and Docker CLI (`cli.js` × 2)
25
+
26
+
4
27
  ## [5.1.1] — 2026-04-06
5
28
 
6
29
  ### 🔧 9Router Smart-Route Sync — Stable via API
package/CHANGELOG.vi.md CHANGED
@@ -1,6 +1,29 @@
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
+
15
+ ## [5.1.2] — 2026-04-06
16
+
17
+ ### 🐛 Fix Shell Injection: Sync Script Dùng Base64
18
+
19
+ Approach node -e JSON.stringify gây lỗi /bin/sh: Syntax error "(" unexpected vì JSON.stringify sinh chuỗi double-quoted phá vỡ shell argument.
20
+
21
+ - **Fix**: nội dung sync script nay được **base64-encode tại thời điểm gen compose** bằng Buffer.from(script).toString base64
22
+ - Entrypoint sinh ra dạng: node -e writeFileSync Buffer.from b64 base64 toString
23
+ - Base64 chỉ chứa [A-Za-z0-9+/=] — không có ký tự đặc biệt, hoạt động đúng trong YAML block
24
+ - Áp dụng cho tất cả luồng gen compose: Docker web wizard (setup.js x2) và Docker CLI (cli.js x2)
25
+
26
+
4
27
  ## [5.1.1] — 2026-04-06
5
28
 
6
29
  ### 🔧 9Router Smart-Route Sync — Ổn định qua API
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.1-0EA5E9?style=for-the-badge" alt="Version 5.1.1" /></a>
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.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.1 for me.
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.1-0EA5E9?style=for-the-badge" alt="Version 5.1.1" /></a>
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.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.1 for me.
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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { input, select, checkbox, confirm } from '@inquirer/prompts';
4
4
  import fs from 'fs-extra';
@@ -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
- async function writeNative9RouterSyncScript(projectDir) {
367
- const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
368
- await fs.ensureDir(path.dirname(syncScriptPath));
369
- await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(getNative9RouterDataDir(), 'db.json')));
370
- return syncScriptPath;
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
- npm install -g 9router
1315
- node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncComboScript)})"
1316
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
1317
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
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
- npm install -g 9router
1410
- node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncComboScript)})"
1411
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
1412
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.1.1",
3
+ "version": "5.1.3",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "cli.js",
6
6
  "bin": {
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
- state.config.plugins.forEach((pid) => {
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
- npm install -g 9router
1923
- node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncScript)})"
1924
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
1925
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
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
- npm install -g 9router
2019
- node -e "require('fs').writeFileSync('/tmp/sync.js',${JSON.stringify(syncScript)})"
2020
- node /tmp/sync.js > /tmp/sync.log 2>&1 &
2021
- exec 9router -n -t -l -H 0.0.0.0 -p 20128 --skip-update
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(() => expectMatch(
90
- cli,
91
- /function build9RouterSmartRouteSyncScript\(dbPath\) \{[\s\S]*safeDbPath[\s\S]*providerConnections[\s\S]*smart-route[\s\S]*async function writeNative9RouterSyncScript\(projectDir\) \{[\s\S]*getNative9RouterDataDir\(\), 'db\.json'/s,
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){removeSmartRoute();return;}")
104
- && cli.includes("if(!m.length){removeSmartRoute();return;}"),
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]*npm root -g[\s\S]*Join-Path \$npmRoot[\s\S]*Start-Process -WindowStyle Hidden -FilePath ''node\.exe''[\s\S]*9router-smart-route-sync\.js/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\.platform==='win32'[\s\S]*AppData[\s\S]*Roaming[\s\S]*providerConnections[\s\S]*smart-route/s,
292
- 'Native script generation must embed a 9Router smart-route sync script with the correct Windows data directory'
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){removeSmartRoute();return;}")
298
- && setup.includes("if(!m.length){removeSmartRoute();return;}"),
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,