create-openclaw-bot 5.7.10 → 5.8.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 +61 -219
- package/README.vi.md +63 -216
- package/dist/cli.js +92 -2777
- package/dist/legacy-cli.js +2812 -0
- package/dist/server/local-server.js +2568 -0
- package/dist/setup/data/header.js +80 -80
- package/dist/setup/data/index.js +0 -1
- package/dist/setup/data/plugins.js +8 -1
- package/dist/setup/data/skills.js +2 -10
- package/dist/setup/shared/bot-config-gen.js +469 -462
- package/dist/setup/shared/common-gen.js +313 -315
- package/dist/setup/shared/docker-gen.js +193 -124
- package/dist/setup/shared/install-gen.js +566 -566
- package/dist/setup/shared/workspace-gen.js +813 -525
- package/dist/setup.js +729 -499
- package/dist/web/app.js +1247 -0
- package/dist/web/bvvbank.jpg +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/momo.jpg +0 -0
- package/dist/web/openclaw-logo.png +0 -0
- package/dist/web/openclaw-logo.svg +1 -0
- package/dist/web/styles.css +1375 -0
- package/package.json +40 -39
|
@@ -17,65 +17,106 @@
|
|
|
17
17
|
return String(text).split('\n').map((line) => `${prefix}${line}`).join('\n');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function build9RouterSmartRouteSyncScript(
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
20
|
+
function build9RouterSmartRouteSyncScript() {
|
|
21
|
+
const lines = [
|
|
22
|
+
"const fs = require('fs');",
|
|
23
|
+
"const INTERVAL = 30000;",
|
|
24
|
+
"const DB_PATH = '/root/.9router/db/data.sqlite';",
|
|
25
|
+
"const PORT = process.env.PORT || 20128;",
|
|
26
|
+
"const COMBO_NAME = 'smart-route';",
|
|
27
|
+
"const API_BASE = `http://localhost:${PORT}`;",
|
|
28
|
+
"",
|
|
29
|
+
"function ensureSettings() {",
|
|
30
|
+
" try {",
|
|
31
|
+
" let db = null;",
|
|
32
|
+
" try {",
|
|
33
|
+
" const { DatabaseSync } = require('node:sqlite');",
|
|
34
|
+
" db = new DatabaseSync(DB_PATH);",
|
|
35
|
+
" } catch {",
|
|
36
|
+
" let Database;",
|
|
37
|
+
" try { Database = require('/usr/local/lib/node_modules/better-sqlite3'); } catch {",
|
|
38
|
+
" try { Database = require('better-sqlite3'); } catch { return; }",
|
|
39
|
+
" }",
|
|
40
|
+
" db = Database(DB_PATH);",
|
|
41
|
+
" }",
|
|
42
|
+
' const existing = db.prepare("SELECT * FROM settings WHERE id = 1").get();',
|
|
43
|
+
" if (!existing) {",
|
|
44
|
+
' db.prepare("INSERT INTO settings (id, data) VALUES (1, ?)").run(JSON.stringify({ requireLogin: false }));',
|
|
45
|
+
" } else {",
|
|
46
|
+
" try {",
|
|
47
|
+
" const data = JSON.parse(existing.data || '{}');",
|
|
48
|
+
" if (data.requireLogin !== false) {",
|
|
49
|
+
" data.requireLogin = false;",
|
|
50
|
+
' db.prepare("UPDATE settings SET data = ? WHERE id = 1").run(JSON.stringify(data));',
|
|
51
|
+
" }",
|
|
52
|
+
" } catch {}",
|
|
53
|
+
" }",
|
|
54
|
+
" db.close();",
|
|
55
|
+
" } catch (e) {}",
|
|
56
|
+
"}",
|
|
57
|
+
"",
|
|
58
|
+
"const sync = async () => {",
|
|
59
|
+
" try {",
|
|
60
|
+
" if (!fs.existsSync(DB_PATH)) return;",
|
|
61
|
+
"",
|
|
62
|
+
" let existingCombo = null;",
|
|
63
|
+
" try {",
|
|
64
|
+
" const resp = await fetch(`${API_BASE}/api/combos`);",
|
|
65
|
+
" if (resp.status === 401) {",
|
|
66
|
+
" ensureSettings();",
|
|
67
|
+
" return;",
|
|
68
|
+
" }",
|
|
69
|
+
" const data = await resp.json();",
|
|
70
|
+
" if (data.combos) {",
|
|
71
|
+
" existingCombo = data.combos.find(c => c.name === COMBO_NAME);",
|
|
72
|
+
" }",
|
|
73
|
+
" } catch (e) { return; }",
|
|
74
|
+
"",
|
|
75
|
+
" if (existingCombo) return;",
|
|
76
|
+
"",
|
|
77
|
+
" let activeProviders = [];",
|
|
78
|
+
" try {",
|
|
79
|
+
" const resp = await fetch(`${API_BASE}/api/providers`);",
|
|
80
|
+
" const data = await resp.json();",
|
|
81
|
+
" const conns = data.connections || data.providerConnections || [];",
|
|
82
|
+
" activeProviders = [...new Set(",
|
|
83
|
+
" conns.filter(c => c && c.provider && c.isActive !== false && !c.disabled).map(c => c.provider)",
|
|
84
|
+
" )];",
|
|
85
|
+
" } catch (e) { return; }",
|
|
86
|
+
"",
|
|
87
|
+
" if (!activeProviders.length) return;",
|
|
88
|
+
"",
|
|
89
|
+
" let models = [];",
|
|
90
|
+
" try {",
|
|
91
|
+
" const resp = await fetch(`${API_BASE}/api/models`);",
|
|
92
|
+
" const data = await resp.json();",
|
|
93
|
+
" if (data.models && Array.isArray(data.models)) {",
|
|
94
|
+
" models = data.models",
|
|
95
|
+
" .filter(m => activeProviders.includes(m.provider))",
|
|
96
|
+
" .filter(m => !/(embedding|image|tts|stt|audio|vision)/i.test(m.model))",
|
|
97
|
+
" .map(m => m.fullModel);",
|
|
98
|
+
" }",
|
|
99
|
+
" models = [...new Set(models)];",
|
|
100
|
+
" } catch (e) { return; }",
|
|
101
|
+
"",
|
|
102
|
+
" if (!models.length) return;",
|
|
103
|
+
"",
|
|
104
|
+
" try {",
|
|
105
|
+
" await fetch(`${API_BASE}/api/combos`, {",
|
|
106
|
+
" method: 'POST',",
|
|
107
|
+
" headers: { 'Content-Type': 'application/json' },",
|
|
108
|
+
" body: JSON.stringify({ name: COMBO_NAME, models })",
|
|
109
|
+
" });",
|
|
110
|
+
" console.log('[sync-combo] Created smart-route with ' + models.length + ' models');",
|
|
111
|
+
" } catch (e) {}",
|
|
112
|
+
" } catch (e) {}",
|
|
113
|
+
"};",
|
|
114
|
+
"",
|
|
115
|
+
"if (fs.existsSync(DB_PATH)) ensureSettings();",
|
|
116
|
+
"setTimeout(sync, 10000);",
|
|
117
|
+
"setInterval(sync, INTERVAL);",
|
|
118
|
+
];
|
|
119
|
+
return lines.join('\n');
|
|
79
120
|
}
|
|
80
121
|
|
|
81
122
|
function build9RouterPatchScript() {
|
|
@@ -140,20 +181,20 @@ for(const root of roots){if(!root||!fs.existsSync(root))continue;touched+=patchP
|
|
|
140
181
|
if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}else{console.log('[patch-9router] No compatible 9router source files found to patch.');}`;
|
|
141
182
|
}
|
|
142
183
|
|
|
143
|
-
function build9RouterComposeEntrypointScript(
|
|
184
|
+
function build9RouterComposeEntrypointScript(routerPort) {
|
|
185
|
+
const port = routerPort || 20128;
|
|
144
186
|
const nineRouterSpec = (typeof globalThis !== 'undefined' && globalThis.__openclawCommon && globalThis.__openclawCommon.NINE_ROUTER_NPM_SPEC) || '9router@latest';
|
|
145
187
|
return [
|
|
146
|
-
`npm install -g
|
|
147
|
-
`node -e "require('fs').writeFileSync('/tmp/patch-9router.js',Buffer.from('${patchScriptBase64}','base64').toString())"`,
|
|
148
|
-
`node -e "require('fs').writeFileSync('/tmp/sync.js',Buffer.from('${syncScriptBase64}','base64').toString())"`,
|
|
188
|
+
`npm install -g ` + nineRouterSpec + ` better-sqlite3`,
|
|
149
189
|
'node /tmp/patch-9router.js || true',
|
|
190
|
+
'node -e "const fs=require(\'fs\'),path=require(\'path\'); const DB_PATH=\'/root/.9router/db/data.sqlite\'; const dir=path.dirname(DB_PATH); if(!fs.existsSync(dir))fs.mkdirSync(dir,{recursive:true}); try{ const {DatabaseSync}=require(\'node:sqlite\'); const db=new DatabaseSync(DB_PATH); db.prepare(\'CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)\').run(); const existing=db.prepare(\'SELECT * FROM settings WHERE id = 1\').get(); if(!existing){ db.prepare(\'INSERT INTO settings (id, data) VALUES (1, ?)\').run(JSON.stringify({requireLogin:false})); } db.close(); }catch(e){}" || true',
|
|
150
191
|
'node /tmp/sync.js > /tmp/sync.log 2>&1 &',
|
|
151
|
-
|
|
192
|
+
`exec 9router -n -l -H 0.0.0.0 -p ${port} --skip-update`
|
|
152
193
|
].join('\n');
|
|
153
194
|
}
|
|
154
195
|
|
|
155
196
|
function buildGatewayPatchCmd() {
|
|
156
|
-
return `node -e \\"const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const a=new Set(['http://localhost:
|
|
197
|
+
return `node -e \\"const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://' + entry.address + ':'+gp);}}const p9=c.models&&c.models.providers&&c.models.providers['9router'];if(p9){p9.request=Object.assign({},p9.request,{allowPrivateNetwork:true});}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,bind:'custom',customBindHost:'0.0.0.0',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));}\\"`;
|
|
157
198
|
}
|
|
158
199
|
|
|
159
200
|
function buildDockerArtifacts(options) {
|
|
@@ -163,14 +204,13 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
163
204
|
is9Router,
|
|
164
205
|
isLocal,
|
|
165
206
|
isMultiBot,
|
|
166
|
-
hasBrowser,
|
|
167
207
|
selectedModel,
|
|
168
208
|
agentId,
|
|
169
209
|
allSkills = [],
|
|
170
210
|
dockerfilePlugins = [],
|
|
171
211
|
dockerfileSkillInstallMode = 'none',
|
|
172
212
|
runtimeCommandParts = [],
|
|
173
|
-
volumeMount = '../../.openclaw:/root/project/.openclaw
|
|
213
|
+
volumeMount = '../../.openclaw:/root/project/.openclaw\n - ../../:/mnt/project',
|
|
174
214
|
singleComposeName = 'oc-bot',
|
|
175
215
|
multiComposeName = 'oc-multibot',
|
|
176
216
|
singleAppContainerName = 'openclaw-bot',
|
|
@@ -182,22 +222,9 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
182
222
|
plainSingleExtraHosts = false,
|
|
183
223
|
multiOllamaNumParallel = 1,
|
|
184
224
|
singleOllamaNumParallel = 1,
|
|
185
|
-
|
|
186
|
-
|
|
225
|
+
gatewayPort = 18789,
|
|
226
|
+
routerPort = 20128,
|
|
187
227
|
} = options;
|
|
188
|
-
|
|
189
|
-
const browserAptExtra = hasBrowser ? ' xvfb socat' : '';
|
|
190
|
-
const browserInstallLines = hasBrowser && emitBrowserInstall
|
|
191
|
-
? [
|
|
192
|
-
'',
|
|
193
|
-
'# Browser Automation: Playwright engine (needed for native CDP)',
|
|
194
|
-
'RUN npm install -g agent-browser playwright \\',
|
|
195
|
-
' && npx playwright install chromium --with-deps \\',
|
|
196
|
-
' && ln -f -s /root/.cache/ms-playwright/chromium-*/chrome-linux*/chrome /usr/bin/google-chrome',
|
|
197
|
-
'',
|
|
198
|
-
''
|
|
199
|
-
].join('\n')
|
|
200
|
-
: '';
|
|
201
228
|
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
202
229
|
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
203
230
|
: '';
|
|
@@ -209,10 +236,9 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
209
236
|
// Dynamic runtime configuration: backup config before any first-run install, restore after.
|
|
210
237
|
// Missing plugin install may touch openclaw.json, so preserve critical fields.
|
|
211
238
|
const backupConfigScript = `const fs=require('fs'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)){fs.copyFileSync(p,b);}`;
|
|
212
|
-
const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
|
|
213
239
|
|
|
214
|
-
const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const a=new Set(['http://localhost:
|
|
215
|
-
const
|
|
240
|
+
const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills','plugins','tools'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||bk.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':'+gp);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
|
|
241
|
+
const securityCompatScript = `const fs=require('fs'),path=require('path');const scopes=['operator.admin','operator.pairing','operator.approvals'];function uniq(a){return Array.from(new Set([...(Array.isArray(a)?a:[]),...scopes]));}function walk(v){if(!v||typeof v!=='object')return;if(Array.isArray(v)){v.forEach(walk);return;}if(Array.isArray(v.scopes)||Array.isArray(v.approvedScopes)){v.scopes=uniq(v.scopes);v.approvedScopes=uniq(v.approvedScopes);}Object.values(v).forEach(walk);}const home=process.env.OPENCLAW_HOME||path.join(process.cwd(),'.openclaw');const state=process.env.OPENCLAW_STATE_DIR||home;const cfgPath=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(cfgPath)){const c=JSON.parse(fs.readFileSync(cfgPath,'utf8'));const p=c.models&&c.models.providers&&c.models.providers['9router'];if(p){p.request=Object.assign({},p.request,{allowPrivateNetwork:true});}fs.writeFileSync(cfgPath,JSON.stringify(c,null,2));}for(const root of Array.from(new Set([home,state]))){const f=path.join(root,'devices','paired.json');if(fs.existsSync(f)){const d=JSON.parse(fs.readFileSync(f,'utf8'));walk(d);fs.writeFileSync(f,JSON.stringify(d,null,2));}}`;
|
|
216
242
|
|
|
217
243
|
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
218
244
|
const runtimePrelude = [
|
|
@@ -238,6 +264,20 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
238
264
|
' echo "[entrypoint] plugin $id missing; installing $spec"',
|
|
239
265
|
' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
|
|
240
266
|
'}',
|
|
267
|
+
'ensure_zalouser() {',
|
|
268
|
+
' NPM_DIR="$OPENCLAW_HOME/npm"',
|
|
269
|
+
' PKG_DIR="$NPM_DIR/node_modules/@openclaw/zalouser"',
|
|
270
|
+
' if [ -d "$PKG_DIR" ]; then',
|
|
271
|
+
' echo "[entrypoint] zalouser plugin already installed"',
|
|
272
|
+
' else',
|
|
273
|
+
' echo "[entrypoint] zalouser plugin missing; installing via npm"',
|
|
274
|
+
' mkdir -p "$NPM_DIR"',
|
|
275
|
+
' cd "$NPM_DIR"',
|
|
276
|
+
' npm init -y 2>/dev/null || true',
|
|
277
|
+
' npm install @openclaw/zalouser@latest 2>/dev/null || echo "[entrypoint] warning: failed to install @openclaw/zalouser"',
|
|
278
|
+
' cd /root/project',
|
|
279
|
+
' fi',
|
|
280
|
+
'}',
|
|
241
281
|
'ensure_skill() {',
|
|
242
282
|
' id="$1"',
|
|
243
283
|
' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
|
|
@@ -251,40 +291,59 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
251
291
|
];
|
|
252
292
|
runtimeParts.unshift(...runtimePrelude);
|
|
253
293
|
// Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
|
|
254
|
-
runtimeParts.unshift(`node -
|
|
294
|
+
runtimeParts.unshift(`node - <<'NODE'\n${backupConfigScript}\nNODE`);
|
|
255
295
|
// Restore config AFTER plugin installs (which may clobber openclaw.json)
|
|
256
|
-
runtimeParts.push(`node -
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
296
|
+
runtimeParts.push(`node - <<'NODE'\n${restoreConfigScript}\nNODE`);
|
|
297
|
+
runtimeParts.push(`node - <<'NODE'\n${securityCompatScript}\nNODE`);
|
|
298
|
+
// Zalouser stability: patch watchdog tolerance and add auto-restart monitor
|
|
299
|
+
runtimeParts.push([
|
|
300
|
+
'# Patch zalouser watchdog tolerance (35s -> 90s) to survive provider auth pre-warming',
|
|
301
|
+
'ZALO_JS=$(find "$OPENCLAW_HOME" -path "*/zalouser/dist/zalo-js-*.js" -type f 2>/dev/null | head -1)',
|
|
302
|
+
'if [ -n "$ZALO_JS" ] && grep -q "35e3" "$ZALO_JS" 2>/dev/null; then',
|
|
303
|
+
' sed -i "s/LISTENER_WATCHDOG_MAX_GAP_MS\\\\s*=\\\\s*35e3/LISTENER_WATCHDOG_MAX_GAP_MS = 90e3/" "$ZALO_JS"',
|
|
304
|
+
' echo "[entrypoint] patched zalouser watchdog gap: 35s -> 90s"',
|
|
305
|
+
'fi',
|
|
306
|
+
].join('\n'));
|
|
307
|
+
runtimeParts.push([
|
|
308
|
+
'# Zalo channel auto-restart monitor (background)',
|
|
309
|
+
'(',
|
|
310
|
+
' sleep 180',
|
|
311
|
+
' while true; do',
|
|
312
|
+
' sleep 60',
|
|
313
|
+
' STATUS=$(openclaw channels status 2>/dev/null | grep -i "Zalo Personal" || true)',
|
|
314
|
+
' if echo "$STATUS" | grep -qi "stopped"; then',
|
|
315
|
+
' echo "[zalo-monitor] Zalo channel stopped - restarting container in 5s"',
|
|
316
|
+
' sleep 5',
|
|
317
|
+
' kill 1 2>/dev/null || true',
|
|
318
|
+
' fi',
|
|
319
|
+
' done',
|
|
320
|
+
') &',
|
|
321
|
+
].join('\n'));
|
|
322
|
+
runtimeParts.push('openclaw gateway run');
|
|
263
323
|
const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
|
|
264
|
-
const runtimeScriptB64 = encodeBase64Utf8(runtimeScript);
|
|
265
324
|
const dockerfile = `FROM node:22-slim
|
|
266
325
|
|
|
267
|
-
RUN apt-get update && apt-get install -y git curl python3
|
|
268
|
-
|
|
326
|
+
RUN apt-get update && apt-get install -y git curl python3 && rm -rf /var/lib/apt/lists/*
|
|
327
|
+
|
|
269
328
|
ARG OPENCLAW_VER="${openClawNpmSpec}"
|
|
270
329
|
ARG CACHE_BUST=""
|
|
271
330
|
RUN echo "CACHE_BUST=$CACHE_BUST" && npm install -g $OPENCLAW_VER ${openClawRuntimePackages}${skillLines}${pluginLines}
|
|
272
331
|
${patchLine}
|
|
273
|
-
|
|
332
|
+
|
|
333
|
+
COPY entrypoint.sh /usr/local/bin/openclaw-entrypoint.sh
|
|
334
|
+
RUN chmod +x /usr/local/bin/openclaw-entrypoint.sh
|
|
274
335
|
WORKDIR /root/project
|
|
275
336
|
|
|
276
|
-
EXPOSE
|
|
337
|
+
EXPOSE ${gatewayPort}
|
|
277
338
|
|
|
278
339
|
CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
|
|
279
340
|
|
|
280
|
-
const syncScript = build9RouterSmartRouteSyncScript(
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
const patchScriptBase64 = encodeBase64Utf8(patchScript);
|
|
284
|
-
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
|
|
341
|
+
const syncScript = build9RouterSmartRouteSyncScript();
|
|
342
|
+
const patchScript = build9RouterPatchScript();
|
|
343
|
+
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(routerPort);
|
|
285
344
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
286
345
|
|
|
287
|
-
const appEnvironmentBlock =
|
|
346
|
+
const appEnvironmentBlock = ` environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/root/project/.openclaw\n - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1\n - OPENCLAW_GATEWAY_PORT=${gatewayPort}\n - OPENCLAW_PORT=${gatewayPort}\n tmpfs:\n - /root/project/.openclaw/plugin-runtime-deps\n`;
|
|
288
347
|
|
|
289
348
|
let compose;
|
|
290
349
|
if (isMultiBot) {
|
|
@@ -293,7 +352,7 @@ const patchScript = build9RouterPatchScript();
|
|
|
293
352
|
: isLocal
|
|
294
353
|
? ' depends_on:\n ollama:\n condition: service_healthy\n'
|
|
295
354
|
: '';
|
|
296
|
-
const extraHosts =
|
|
355
|
+
const extraHosts = `${extraHostsBlock}\n`;
|
|
297
356
|
if (is9Router) {
|
|
298
357
|
compose = `name: ${multiComposeName}
|
|
299
358
|
services:
|
|
@@ -302,11 +361,11 @@ services:
|
|
|
302
361
|
container_name: ${multiAppContainerName}
|
|
303
362
|
restart: always
|
|
304
363
|
env_file:
|
|
305
|
-
-
|
|
364
|
+
- ../../.env
|
|
306
365
|
${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
307
366
|
- ${volumeMount}
|
|
308
367
|
ports:
|
|
309
|
-
- "
|
|
368
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
310
369
|
|
|
311
370
|
9router:
|
|
312
371
|
image: node:22-slim
|
|
@@ -318,13 +377,15 @@ ${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
|
318
377
|
- |
|
|
319
378
|
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
320
379
|
environment:
|
|
321
|
-
- PORT
|
|
380
|
+
- PORT=${routerPort}
|
|
322
381
|
- HOSTNAME=0.0.0.0
|
|
323
382
|
- CI=true
|
|
324
383
|
volumes:
|
|
325
384
|
- 9router-data:/root/.9router
|
|
385
|
+
- ./sync.js:/tmp/sync.js:ro
|
|
386
|
+
- ./patch-9router.js:/tmp/patch-9router.js:ro
|
|
326
387
|
ports:
|
|
327
|
-
- "
|
|
388
|
+
- "${routerPort}:${routerPort}"
|
|
328
389
|
|
|
329
390
|
volumes:
|
|
330
391
|
9router-data:`;
|
|
@@ -337,11 +398,11 @@ services:
|
|
|
337
398
|
container_name: ${multiAppContainerName}
|
|
338
399
|
restart: always
|
|
339
400
|
env_file:
|
|
340
|
-
-
|
|
401
|
+
- ../../.env
|
|
341
402
|
${appEnvironmentBlock}${dependsOn}${extraHosts} volumes:
|
|
342
403
|
- ${volumeMount}
|
|
343
404
|
ports:
|
|
344
|
-
- "
|
|
405
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
345
406
|
|
|
346
407
|
ollama:
|
|
347
408
|
image: ollama/ollama:latest
|
|
@@ -377,11 +438,11 @@ services:
|
|
|
377
438
|
container_name: ${multiAppContainerName}
|
|
378
439
|
restart: always
|
|
379
440
|
env_file:
|
|
380
|
-
-
|
|
441
|
+
- ../../.env
|
|
381
442
|
${appEnvironmentBlock}${extraHosts} volumes:
|
|
382
443
|
- ${volumeMount}
|
|
383
444
|
ports:
|
|
384
|
-
- "
|
|
445
|
+
- "${gatewayPort}:${gatewayPort}"`;
|
|
385
446
|
}
|
|
386
447
|
} else if (is9Router) {
|
|
387
448
|
compose = `name: ${singleComposeName}
|
|
@@ -391,13 +452,14 @@ services:
|
|
|
391
452
|
container_name: ${singleAppContainerName}
|
|
392
453
|
restart: always
|
|
393
454
|
env_file:
|
|
394
|
-
-
|
|
455
|
+
- ../../.env
|
|
395
456
|
depends_on:
|
|
396
457
|
- 9router
|
|
397
|
-
${appEnvironmentBlock}${
|
|
458
|
+
${appEnvironmentBlock}${extraHostsBlock}\n volumes:
|
|
398
459
|
- ${volumeMount}
|
|
460
|
+
- openclaw-plugins:/root/project/.openclaw/npm
|
|
399
461
|
ports:
|
|
400
|
-
- "
|
|
462
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
401
463
|
|
|
402
464
|
9router:
|
|
403
465
|
image: node:22-slim
|
|
@@ -409,16 +471,19 @@ ${appEnvironmentBlock}${hasBrowser ? `${extraHostsBlock}\n` : ''} volumes:
|
|
|
409
471
|
- |
|
|
410
472
|
${indentBlock(docker9RouterEntrypointScript, 8)}
|
|
411
473
|
environment:
|
|
412
|
-
- PORT
|
|
474
|
+
- PORT=${routerPort}
|
|
413
475
|
- HOSTNAME=0.0.0.0
|
|
414
476
|
- CI=true
|
|
415
477
|
volumes:
|
|
416
478
|
- 9router-data:/root/.9router
|
|
479
|
+
- ./sync.js:/tmp/sync.js:ro
|
|
480
|
+
- ./patch-9router.js:/tmp/patch-9router.js:ro
|
|
417
481
|
ports:
|
|
418
|
-
- "
|
|
482
|
+
- "${routerPort}:${routerPort}"
|
|
419
483
|
|
|
420
484
|
volumes:
|
|
421
|
-
9router-data
|
|
485
|
+
9router-data:
|
|
486
|
+
openclaw-plugins:`;
|
|
422
487
|
} else if (isLocal) {
|
|
423
488
|
const ollamaModelTag = String(selectedModel || 'ollama/gemma4:e2b').replace('ollama/', '');
|
|
424
489
|
compose = `name: ${singleComposeName}
|
|
@@ -427,12 +492,12 @@ services:
|
|
|
427
492
|
build: .
|
|
428
493
|
container_name: ${singleAppContainerName}
|
|
429
494
|
restart: always
|
|
430
|
-
env_file:
|
|
495
|
+
env_file: ../../.env
|
|
431
496
|
${appEnvironmentBlock} depends_on:
|
|
432
497
|
ollama:
|
|
433
498
|
condition: service_healthy
|
|
434
|
-
${
|
|
435
|
-
- "
|
|
499
|
+
${extraHostsBlock}\n ports:
|
|
500
|
+
- "${gatewayPort}:${gatewayPort}"
|
|
436
501
|
volumes:
|
|
437
502
|
- ${volumeMount}
|
|
438
503
|
|
|
@@ -470,17 +535,19 @@ services:
|
|
|
470
535
|
container_name: ${singleAppContainerName}
|
|
471
536
|
restart: always
|
|
472
537
|
env_file:
|
|
473
|
-
-
|
|
538
|
+
- ../../.env
|
|
474
539
|
${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''} volumes:
|
|
475
540
|
- ${volumeMount}
|
|
476
541
|
ports:
|
|
477
|
-
- "
|
|
542
|
+
- "${gatewayPort}:${gatewayPort}"`;
|
|
478
543
|
}
|
|
479
544
|
|
|
480
545
|
return {
|
|
481
546
|
dockerfile,
|
|
482
547
|
compose,
|
|
548
|
+
entrypointScript: runtimeScript,
|
|
483
549
|
syncScript,
|
|
550
|
+
patchScript,
|
|
484
551
|
docker9RouterEntrypointScript,
|
|
485
552
|
gatewayPatchCmd: buildGatewayPatchCmd(),
|
|
486
553
|
};
|
|
@@ -500,3 +567,5 @@ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''}
|
|
|
500
567
|
if (typeof exports !== 'undefined' && typeof globalThis !== 'undefined' && globalThis.__openclawDockerGen) {
|
|
501
568
|
Object.assign(exports, globalThis.__openclawDockerGen);
|
|
502
569
|
}
|
|
570
|
+
|
|
571
|
+
|