8900 commited on
Commit
bd35f1b
·
verified ·
1 Parent(s): dd094e5

Update setup-hf-config.mjs

Browse files
Files changed (1) hide show
  1. setup-hf-config.mjs +200 -134
setup-hf-config.mjs CHANGED
@@ -5,22 +5,18 @@ import https from "node:https";
5
  // ============================================================
6
  // OpenClaw HF Spaces - Production Config Writer
7
  //
8
- // Storage: HF Storage Bucket mounted at /data (100 GB persistent).
9
- // All data lives in /data/.openclaw/ and survives restarts natively.
10
- // No Dataset sync needed.
11
  //
12
- // Priority order for base config:
13
- // 1. /data/.openclaw/openclaw.json exists (bucket, persistent) -> PATCH only
14
- // 2. /app/openclaw-template.json exists (from Space repo) -> use as base
15
- // 3. neither -> write minimal fresh
16
  //
17
- // What ALWAYS gets patched (from env vars):
18
- // gateway.auth <- OPENCLAW_GATEWAY_TOKEN
19
- // gateway.trustedProxies
20
- // agents.defaults.workspace
21
- // env.vars <- all API keys
22
- // channels.telegram <- TELEGRAM_BOT_TOKEN
23
- // models.providers.*.apiKey <- matching API key env vars
24
  // ============================================================
25
 
26
  var HOME = process.env.OPENCLAW_HOME || process.env.HOME || "/home/user";
@@ -30,26 +26,39 @@ var WORKSPACE = path.join(STATE_DIR, "workspace");
30
  var SPACE_HOST = (process.env.SPACE_HOST || "").trim();
31
  var TEMPLATE = "/app/openclaw-template.json";
32
 
33
- console.log("[setup] Starting... HOME=" + HOME);
 
 
 
 
 
 
 
 
34
 
35
  function parseList(val) {
36
  if (!val || !val.trim()) return [];
37
- return val.split(",").map(function(s) { return s.trim(); }).filter(Boolean);
38
  }
39
- function envStr(key) { return (process.env[key] || "").trim(); }
40
 
41
- // -- auth ---------------------------------------------------------
 
 
42
  var gatewayToken = envStr("OPENCLAW_GATEWAY_TOKEN");
43
  var gatewayPassword = envStr("OPENCLAW_GATEWAY_PASSWORD");
44
  if (!gatewayToken && !gatewayPassword) {
45
- console.error("[setup] FATAL: set OPENCLAW_GATEWAY_TOKEN in Secrets");
46
  process.exit(0);
47
  }
48
 
49
- // -- model --------------------------------------------------------
 
 
50
  var defaultModel = envStr("OPENCLAW_HF_DEFAULT_MODEL") || "google/gemini-2.0-flash";
51
 
52
- // -- provider keys ------------------------------------------------
 
 
53
  var EXCLUDE_PREFIXES = [
54
  "OPENCLAW_", "SPACE_", "SYSTEM_", "HF_",
55
  "NODE_", "PATH", "HOME", "USER", "PWD", "LANG", "LC_",
@@ -59,6 +68,7 @@ var INCLUDE_SUFFIXES = [
59
  "_API_KEY", "_SECRET_KEY", "_ACCESS_TOKEN",
60
  "_BOT_TOKEN", "_AUTH_TOKEN", "_APP_KEY"
61
  ];
 
62
  function isProviderKey(k) {
63
  var i;
64
  for (i = 0; i < EXCLUDE_PREFIXES.length; i++) {
@@ -70,76 +80,71 @@ function isProviderKey(k) {
70
  }
71
  return false;
72
  }
 
73
  var providerKeys = Object.keys(process.env).filter(function(k) {
74
- return isProviderKey(k) && (process.env[k] || "").trim();
75
  }).sort();
76
 
77
- console.log("[setup] Provider keys (" + providerKeys.length + "): " + providerKeys.join(", "));
78
 
79
- // -- trusted proxies ----------------------------------------------
80
- var envProxies = parseList(envStr("OPENCLAW_GATEWAY_TRUSTED_PROXIES"));
 
 
81
  var trustedProxies = envProxies.length > 0 ? envProxies : [
82
  "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
83
- "10.16.0.0/12", "10.20.0.0/12",
84
- "10.16.4.123", "10.16.7.92", "10.16.18.232",
85
- "10.16.34.155", "10.16.43.133", "10.16.1.206",
86
- "10.16.37.110", "10.16.43.246",
87
- "10.20.1.9", "10.20.1.222",
88
- "10.20.26.157", "10.20.31.87",
89
- "10.20.0.1", "172.17.0.1", "127.0.0.1"
90
  ];
91
 
92
- // -- helpers ------------------------------------------------------
93
- function buildAuth() {
94
- return gatewayToken
95
- ? { mode: "token", token: gatewayToken }
96
- : { mode: "password", password: gatewayPassword };
97
- }
98
- function buildEnvVars() {
99
- var vars = {};
100
- providerKeys.forEach(function(pk) { vars[pk] = (process.env[pk] || "").trim(); });
101
- return vars;
102
- }
103
-
104
- // Patch model provider API keys from env vars
105
  var PROVIDER_KEY_MAP = {
106
- "openrouter": "OPENROUTER_API_KEY",
107
- "siliconflow": "SILICONFLOW_API_KEY",
108
- "deepseek": "DEEPSEEK_API_KEY",
109
- "groq": "GROQ_API_KEY",
110
- "google": "GOOGLE_API_KEY",
111
- "gemini": "GEMINI_API_KEY",
112
- "minimax": "MINIMAX_API_KEY",
113
- "zai": "ZAI_API_KEY",
114
- "github": "GITHUB_MODELS_API_KEY",
115
- "openai": "OPENAI_API_KEY",
116
- "anthropic": "ANTHROPIC_API_KEY"
117
  };
118
 
119
  function patchProviderKeys(config) {
120
  if (!config.models || !config.models.providers) return;
121
  var providers = config.models.providers;
122
- Object.keys(providers).forEach(function(providerName) {
123
- var lower = providerName.toLowerCase();
124
  var envKey = PROVIDER_KEY_MAP[lower];
125
- if (envKey && process.env[envKey]) {
126
- providers[providerName].apiKey = process.env[envKey].trim();
127
- console.log("[setup] Provider key set: " + providerName + " <- " + envKey);
128
- } else {
129
  var found = providerKeys.find(function(k) {
130
  return k.toLowerCase().indexOf(lower) >= 0;
131
  });
132
- if (found) {
133
- providers[providerName].apiKey = process.env[found].trim();
134
- console.log("[setup] Provider key set: " + providerName + " <- " + found);
135
- } else if (providers[providerName].apiKey === "WILL_BE_SET_BY_ENV") {
136
- delete providers[providerName].apiKey;
137
- }
 
 
138
  }
139
  });
140
  }
141
 
142
- // -- Telegram -----------------------------------------------------
 
 
143
  function tgRequest(token, method, body) {
144
  return new Promise(function(resolve) {
145
  var data = JSON.stringify(body || {});
@@ -147,150 +152,211 @@ function tgRequest(token, method, body) {
147
  hostname: "api.telegram.org",
148
  path: "/bot" + token + "/" + method,
149
  method: "POST",
150
- headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) }
 
 
 
151
  }, function(res) {
152
  var buf = "";
153
  res.on("data", function(c) { buf += c; });
154
- res.on("end", function() { try { resolve(JSON.parse(buf)); } catch (e) { resolve(null); } });
 
 
155
  });
156
- req.on("error", function() { resolve(null); });
157
- req.setTimeout(8000, function() { req.destroy(); resolve(null); });
158
  req.write(data);
159
  req.end();
160
  });
161
  }
162
 
163
  async function setupWebhook(token) {
164
- if (!SPACE_HOST) { console.log("[setup] Telegram: no SPACE_HOST for webhook"); return; }
165
- var url = "https://" + SPACE_HOST + "/tg-webhook";
 
 
 
 
 
 
 
 
 
 
 
166
  var r = await tgRequest(token, "setWebhook", {
167
- url: url, drop_pending_updates: true, max_connections: 10
168
  });
169
  if (r && r.ok) {
170
- console.log("[setup] Telegram: webhook -> " + url);
171
  } else {
172
- console.log("[setup] Telegram: webhook failed. Open in browser:");
173
- console.log(" https://api.telegram.org/bot" + token + "/setWebhook?url=" + url + "&drop_pending_updates=true");
 
 
174
  }
175
  }
176
 
177
- // -- seed workspace files (first boot only) -----------------------
 
 
178
  function seedWorkspace() {
179
  var soul = path.join(WORKSPACE, "SOUL.md");
180
  if (!fs.existsSync(soul)) {
181
  fs.writeFileSync(soul, [
182
- "# Soul", "",
183
- "You are a helpful, warm, concise AI assistant.", "",
184
- "## Language", "",
 
 
 
185
  "Default language: Simplified Chinese.",
186
- "Always reply in Chinese unless the user writes in another language first.", "",
187
- "## Tone", "",
 
 
188
  "- Natural and friendly, not overly formal",
189
  "- Concise and to the point"
190
  ].join("\n") + "\n", "utf-8");
191
- console.log("[setup] Seeded SOUL.md");
192
  }
193
  var mem = path.join(WORKSPACE, "MEMORY.md");
194
  if (!fs.existsSync(mem)) {
195
  fs.writeFileSync(mem, [
196
- "# Long-term Memory", "",
 
197
  "<!-- OpenClaw writes important facts here. -->"
198
  ].join("\n") + "\n", "utf-8");
199
- console.log("[setup] Seeded MEMORY.md");
200
  }
201
  }
202
 
203
- // -- apply all env patches to a config object ---------------------
204
- function applyEnvPatches(config, token) {
205
- config.gateway = config.gateway || {};
206
- config.gateway.auth = buildAuth();
207
- config.gateway.controlUi = config.gateway.controlUi || {};
 
 
 
 
 
 
 
208
  config.gateway.controlUi.allowInsecureAuth = true;
209
  config.gateway.controlUi.allowedOrigins = ["*"];
210
  config.gateway.controlUi.dangerouslyDisableDeviceAuth = true;
211
  config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true;
212
- if (!config.gateway.trustedProxies || config.gateway.trustedProxies.length < 5) {
213
- config.gateway.trustedProxies = trustedProxies;
214
- }
215
- config.agents = config.agents || {};
216
- config.agents.defaults = config.agents.defaults || {};
 
 
217
  config.agents.defaults.workspace = WORKSPACE;
218
- if (config.agents.defaults.model === "WILL_BE_SET_BY_ENV") {
 
219
  config.agents.defaults.model = defaultModel;
220
  }
221
- config.env = config.env || {};
222
- config.env.vars = buildEnvVars();
 
 
 
 
 
223
  patchProviderKeys(config);
224
- if (token) {
 
 
225
  config.channels = config.channels || {};
226
  config.channels.telegram = config.channels.telegram || {};
227
  config.channels.telegram.enabled = true;
228
  config.channels.telegram.accounts = config.channels.telegram.accounts || {};
229
  config.channels.telegram.accounts.main = {
230
- botToken: token,
231
  apiRoot: "https://api.telegram.org"
232
  };
233
  }
 
234
  return config;
235
  }
236
 
237
- // -- main ---------------------------------------------------------
 
 
238
  (async function() {
239
- fs.mkdirSync(STATE_DIR, { recursive: true });
240
- fs.mkdirSync(WORKSPACE, { recursive: true });
241
- fs.mkdirSync(path.join(WORKSPACE, "memory"), { recursive: true });
 
242
 
243
- // Seed workspace files on first boot (bucket persists them after that)
244
  seedWorkspace();
245
 
246
- var token = envStr("TELEGRAM_BOT_TOKEN");
247
- if (token) await setupWebhook(token);
248
- else console.log("[setup] Telegram: disabled (no TELEGRAM_BOT_TOKEN)");
 
 
 
 
249
 
 
250
  var config = null;
251
  var mode = "";
252
 
253
- // Priority 1: existing config from bucket (persisted across restarts)
254
  if (fs.existsSync(CONFIG_PATH)) {
255
  try {
256
  config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8").trim());
257
- mode = "patch (restored from bucket)";
258
- } catch (e) { config = null; }
 
 
259
  }
260
 
261
- // Priority 2: Space repo template
262
  if (!config && fs.existsSync(TEMPLATE)) {
263
  try {
264
  config = JSON.parse(fs.readFileSync(TEMPLATE, "utf-8").trim());
265
- mode = "template (from Space repo openclaw.json)";
266
- } catch (e) { config = null; }
 
 
267
  }
268
 
269
- // Priority 3: minimal fresh config
270
  if (!config) {
271
- config = {
272
- gateway: {}, agents: { defaults: {} }, env: { vars: {} }
273
- };
274
- mode = "fresh (no template found)";
275
  }
276
 
277
- config = applyEnvPatches(config, token);
 
278
 
 
279
  if (fs.existsSync(CONFIG_PATH)) {
280
  fs.copyFileSync(CONFIG_PATH, CONFIG_PATH + ".bak");
281
  }
282
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
283
 
284
- console.log("[setup] Done. Mode: " + mode);
285
- console.log("[setup] auth = " + (gatewayToken ? "token" : "password"));
286
- console.log("[setup] model = " + (config.agents.defaults.model || defaultModel));
287
- console.log("[setup] workspace = " + WORKSPACE);
288
- console.log("[setup] proxies = " + trustedProxies.length);
289
- console.log("[setup] env.vars = " + providerKeys.length);
290
- var provCount = config.models && config.models.providers ? Object.keys(config.models.providers).length : 0;
291
- console.log("[setup] providers = " + provCount);
292
- console.log("[setup] storage = bucket at /data (100 GB persistent)");
 
 
 
 
 
 
 
 
293
  })().catch(function(e) {
294
- console.error("[setup] Fatal: " + e.message);
295
  process.exit(0);
296
- });
 
5
  // ============================================================
6
  // OpenClaw HF Spaces - Production Config Writer
7
  //
8
+ // Storage: HF Storage Bucket at /data (100 GB persistent).
9
+ // All runtime data lives in OPENCLAW_HOME/.openclaw/.
 
10
  //
11
+ // Config priority:
12
+ // 1. Existing openclaw.json in bucket -> PATCH env vars only
13
+ // 2. /app/openclaw-template.json -> use as base + patch
14
+ // 3. Neither -> minimal fresh + patch
15
  //
16
+ // Always patched from env vars (never lost on restart):
17
+ // gateway.auth, gateway.controlUi, gateway.trustedProxies
18
+ // agents.defaults.workspace, agents.defaults.model
19
+ // env.vars, models.providers.*.apiKey, channels.telegram
 
 
 
20
  // ============================================================
21
 
22
  var HOME = process.env.OPENCLAW_HOME || process.env.HOME || "/home/user";
 
26
  var SPACE_HOST = (process.env.SPACE_HOST || "").trim();
27
  var TEMPLATE = "/app/openclaw-template.json";
28
 
29
+ function log(msg) { console.log("[setup] " + msg); }
30
+ function err(msg) { console.error("[setup] ERROR: " + msg); }
31
+
32
+ log("Starting... HOME=" + HOME);
33
+
34
+ // ----------------------------------------------------------------
35
+ // Helpers
36
+ // ----------------------------------------------------------------
37
+ function envStr(key) { return (process.env[key] || "").trim(); }
38
 
39
  function parseList(val) {
40
  if (!val || !val.trim()) return [];
41
+ return val.split(",").map(function(s){ return s.trim(); }).filter(Boolean);
42
  }
 
43
 
44
+ // ----------------------------------------------------------------
45
+ // Auth (required)
46
+ // ----------------------------------------------------------------
47
  var gatewayToken = envStr("OPENCLAW_GATEWAY_TOKEN");
48
  var gatewayPassword = envStr("OPENCLAW_GATEWAY_PASSWORD");
49
  if (!gatewayToken && !gatewayPassword) {
50
+ err("FATAL: set OPENCLAW_GATEWAY_TOKEN in Secrets");
51
  process.exit(0);
52
  }
53
 
54
+ // ----------------------------------------------------------------
55
+ // Default model
56
+ // ----------------------------------------------------------------
57
  var defaultModel = envStr("OPENCLAW_HF_DEFAULT_MODEL") || "google/gemini-2.0-flash";
58
 
59
+ // ----------------------------------------------------------------
60
+ // Provider API key detection
61
+ // ----------------------------------------------------------------
62
  var EXCLUDE_PREFIXES = [
63
  "OPENCLAW_", "SPACE_", "SYSTEM_", "HF_",
64
  "NODE_", "PATH", "HOME", "USER", "PWD", "LANG", "LC_",
 
68
  "_API_KEY", "_SECRET_KEY", "_ACCESS_TOKEN",
69
  "_BOT_TOKEN", "_AUTH_TOKEN", "_APP_KEY"
70
  ];
71
+
72
  function isProviderKey(k) {
73
  var i;
74
  for (i = 0; i < EXCLUDE_PREFIXES.length; i++) {
 
80
  }
81
  return false;
82
  }
83
+
84
  var providerKeys = Object.keys(process.env).filter(function(k) {
85
+ return isProviderKey(k) && envStr(k);
86
  }).sort();
87
 
88
+ log("Provider keys (" + providerKeys.length + "): " + providerKeys.join(", "));
89
 
90
+ // ----------------------------------------------------------------
91
+ // Trusted proxies (HF internal + standard RFC1918)
92
+ // ----------------------------------------------------------------
93
+ var envProxies = parseList(envStr("OPENCLAW_GATEWAY_TRUSTED_PROXIES"));
94
  var trustedProxies = envProxies.length > 0 ? envProxies : [
95
  "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
96
+ "10.16.0.0/12", "10.20.0.0/12",
97
+ "10.16.4.123", "10.16.7.92", "10.16.18.232",
98
+ "10.16.34.155", "10.16.43.133", "10.16.1.206",
99
+ "10.16.37.110", "10.16.43.246",
100
+ "10.20.1.9", "10.20.1.222", "10.20.26.157", "10.20.31.87",
101
+ "10.20.0.1", "172.17.0.1", "127.0.0.1"
 
102
  ];
103
 
104
+ // ----------------------------------------------------------------
105
+ // Provider -> env var mapping
106
+ // ----------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
107
  var PROVIDER_KEY_MAP = {
108
+ "openrouter": "OPENROUTER_API_KEY",
109
+ "siliconflow": "SILICONFLOW_API_KEY",
110
+ "deepseek": "DEEPSEEK_API_KEY",
111
+ "groq": "GROQ_API_KEY",
112
+ "google": "GOOGLE_API_KEY",
113
+ "gemini": "GEMINI_API_KEY",
114
+ "minimax": "MINIMAX_API_KEY",
115
+ "zai": "ZAI_API_KEY",
116
+ "github": "GITHUB_MODELS_API_KEY",
117
+ "openai": "OPENAI_API_KEY",
118
+ "anthropic": "ANTHROPIC_API_KEY"
119
  };
120
 
121
  function patchProviderKeys(config) {
122
  if (!config.models || !config.models.providers) return;
123
  var providers = config.models.providers;
124
+ Object.keys(providers).forEach(function(name) {
125
+ var lower = name.toLowerCase();
126
  var envKey = PROVIDER_KEY_MAP[lower];
127
+ var val = envKey ? envStr(envKey) : "";
128
+ if (!val) {
129
+ // fuzzy match: find any provider key whose name contains the provider name
 
130
  var found = providerKeys.find(function(k) {
131
  return k.toLowerCase().indexOf(lower) >= 0;
132
  });
133
+ if (found) val = envStr(found);
134
+ }
135
+ if (val) {
136
+ providers[name].apiKey = val;
137
+ log("Provider key set: " + name + " <- " + (envKey || "fuzzy"));
138
+ } else if (providers[name].apiKey === "WILL_BE_SET_BY_ENV") {
139
+ delete providers[name].apiKey;
140
+ log("Provider key missing (skipped): " + name);
141
  }
142
  });
143
  }
144
 
145
+ // ----------------------------------------------------------------
146
+ // Telegram webhook helpers
147
+ // ----------------------------------------------------------------
148
  function tgRequest(token, method, body) {
149
  return new Promise(function(resolve) {
150
  var data = JSON.stringify(body || {});
 
152
  hostname: "api.telegram.org",
153
  path: "/bot" + token + "/" + method,
154
  method: "POST",
155
+ headers: {
156
+ "Content-Type": "application/json",
157
+ "Content-Length": Buffer.byteLength(data)
158
+ }
159
  }, function(res) {
160
  var buf = "";
161
  res.on("data", function(c) { buf += c; });
162
+ res.on("end", function() {
163
+ try { resolve(JSON.parse(buf)); } catch(e) { resolve(null); }
164
+ });
165
  });
166
+ req.on("error", function() { resolve(null); });
167
+ req.setTimeout(10000, function() { req.destroy(); resolve(null); });
168
  req.write(data);
169
  req.end();
170
  });
171
  }
172
 
173
  async function setupWebhook(token) {
174
+ if (!SPACE_HOST) {
175
+ log("Telegram: no SPACE_HOST - webhook skipped");
176
+ return;
177
+ }
178
+ var targetUrl = "https://" + SPACE_HOST + "/tg-webhook";
179
+
180
+ // Check if webhook already points to our URL - skip if already set
181
+ var info = await tgRequest(token, "getWebhookInfo", {});
182
+ if (info && info.ok && info.result && info.result.url === targetUrl) {
183
+ log("Telegram: webhook already set -> " + targetUrl);
184
+ return;
185
+ }
186
+
187
  var r = await tgRequest(token, "setWebhook", {
188
+ url: targetUrl, drop_pending_updates: true, max_connections: 10
189
  });
190
  if (r && r.ok) {
191
+ log("Telegram: webhook registered -> " + targetUrl);
192
  } else {
193
+ var desc = r && r.description ? r.description : "unknown error";
194
+ log("Telegram: webhook failed (" + desc + ")");
195
+ log("Telegram: set manually at:");
196
+ log(" https://api.telegram.org/bot" + token + "/setWebhook?url=" + targetUrl + "&drop_pending_updates=true");
197
  }
198
  }
199
 
200
+ // ----------------------------------------------------------------
201
+ // Seed workspace (first boot only - bucket persists after)
202
+ // ----------------------------------------------------------------
203
  function seedWorkspace() {
204
  var soul = path.join(WORKSPACE, "SOUL.md");
205
  if (!fs.existsSync(soul)) {
206
  fs.writeFileSync(soul, [
207
+ "# Soul",
208
+ "",
209
+ "You are a helpful, warm, concise AI assistant.",
210
+ "",
211
+ "## Language",
212
+ "",
213
  "Default language: Simplified Chinese.",
214
+ "Always reply in Chinese unless the user writes in another language first.",
215
+ "",
216
+ "## Tone",
217
+ "",
218
  "- Natural and friendly, not overly formal",
219
  "- Concise and to the point"
220
  ].join("\n") + "\n", "utf-8");
221
+ log("Seeded SOUL.md");
222
  }
223
  var mem = path.join(WORKSPACE, "MEMORY.md");
224
  if (!fs.existsSync(mem)) {
225
  fs.writeFileSync(mem, [
226
+ "# Long-term Memory",
227
+ "",
228
  "<!-- OpenClaw writes important facts here. -->"
229
  ].join("\n") + "\n", "utf-8");
230
+ log("Seeded MEMORY.md");
231
  }
232
  }
233
 
234
+ // ----------------------------------------------------------------
235
+ // Apply all env-derived patches to a config object
236
+ // ----------------------------------------------------------------
237
+ function applyEnvPatches(config, tgToken) {
238
+ // Auth
239
+ config.gateway = config.gateway || {};
240
+ config.gateway.auth = gatewayToken
241
+ ? { mode: "token", token: gatewayToken }
242
+ : { mode: "password", password: gatewayPassword };
243
+
244
+ // Control UI flags required for HF Spaces iframe embedding
245
+ config.gateway.controlUi = config.gateway.controlUi || {};
246
  config.gateway.controlUi.allowInsecureAuth = true;
247
  config.gateway.controlUi.allowedOrigins = ["*"];
248
  config.gateway.controlUi.dangerouslyDisableDeviceAuth = true;
249
  config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true;
250
+
251
+ // Always refresh trusted proxies (HF pod IPs can change)
252
+ config.gateway.trustedProxies = trustedProxies;
253
+
254
+ // Agents
255
+ config.agents = config.agents || {};
256
+ config.agents.defaults = config.agents.defaults || {};
257
  config.agents.defaults.workspace = WORKSPACE;
258
+ if (!config.agents.defaults.model ||
259
+ config.agents.defaults.model === "WILL_BE_SET_BY_ENV") {
260
  config.agents.defaults.model = defaultModel;
261
  }
262
+
263
+ // Env vars (all provider keys available inside agent tools)
264
+ config.env = config.env || {};
265
+ config.env.vars = {};
266
+ providerKeys.forEach(function(k) { config.env.vars[k] = envStr(k); });
267
+
268
+ // Model provider API keys
269
  patchProviderKeys(config);
270
+
271
+ // Telegram channel
272
+ if (tgToken) {
273
  config.channels = config.channels || {};
274
  config.channels.telegram = config.channels.telegram || {};
275
  config.channels.telegram.enabled = true;
276
  config.channels.telegram.accounts = config.channels.telegram.accounts || {};
277
  config.channels.telegram.accounts.main = {
278
+ botToken: tgToken,
279
  apiRoot: "https://api.telegram.org"
280
  };
281
  }
282
+
283
  return config;
284
  }
285
 
286
+ // ----------------------------------------------------------------
287
+ // Main
288
+ // ----------------------------------------------------------------
289
  (async function() {
290
+ // Ensure directories exist
291
+ fs.mkdirSync(STATE_DIR, { recursive: true });
292
+ fs.mkdirSync(WORKSPACE, { recursive: true });
293
+ fs.mkdirSync(path.join(WORKSPACE, "memory"), { recursive: true });
294
 
295
+ // Seed workspace on first boot
296
  seedWorkspace();
297
 
298
+ // Telegram webhook
299
+ var tgToken = envStr("TELEGRAM_BOT_TOKEN");
300
+ if (tgToken) {
301
+ await setupWebhook(tgToken);
302
+ } else {
303
+ log("Telegram: disabled (no TELEGRAM_BOT_TOKEN)");
304
+ }
305
 
306
+ // Load base config
307
  var config = null;
308
  var mode = "";
309
 
 
310
  if (fs.existsSync(CONFIG_PATH)) {
311
  try {
312
  config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8").trim());
313
+ mode = "patch (existing bucket config)";
314
+ } catch(e) {
315
+ log("Existing config unreadable (" + e.message + ") - falling back to template");
316
+ }
317
  }
318
 
 
319
  if (!config && fs.existsSync(TEMPLATE)) {
320
  try {
321
  config = JSON.parse(fs.readFileSync(TEMPLATE, "utf-8").trim());
322
+ mode = "template (Space repo openclaw.json)";
323
+ } catch(e) {
324
+ log("Template unreadable (" + e.message + ")");
325
+ }
326
  }
327
 
 
328
  if (!config) {
329
+ config = { gateway: {}, agents: { defaults: {} }, env: { vars: {} } };
330
+ mode = "fresh (no template found)";
 
 
331
  }
332
 
333
+ // Apply env patches
334
+ config = applyEnvPatches(config, tgToken);
335
 
336
+ // Atomic write: backup then overwrite
337
  if (fs.existsSync(CONFIG_PATH)) {
338
  fs.copyFileSync(CONFIG_PATH, CONFIG_PATH + ".bak");
339
  }
340
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
341
 
342
+ // Summary
343
+ var provCount = config.models && config.models.providers
344
+ ? Object.keys(config.models.providers).length : 0;
345
+ var modelCount = config.models && config.models.providers
346
+ ? Object.values(config.models.providers).reduce(function(n, p) {
347
+ return n + (p.models ? p.models.length : 0);
348
+ }, 0) : 0;
349
+
350
+ log("Done. Mode: " + mode);
351
+ log(" auth = " + (gatewayToken ? "token" : "password"));
352
+ log(" model = " + (config.agents.defaults.model || defaultModel));
353
+ log(" workspace = " + WORKSPACE);
354
+ log(" proxies = " + trustedProxies.length);
355
+ log(" env.vars = " + providerKeys.length);
356
+ log(" providers = " + provCount + " (" + modelCount + " models)");
357
+ log(" storage = bucket at /data (100 GB persistent)");
358
+
359
  })().catch(function(e) {
360
+ err("Fatal: " + e.message);
361
  process.exit(0);
362
+ });