8900 commited on
Commit
c845922
Β·
verified Β·
1 Parent(s): 8924dbd

Update setup-hf-config.mjs

Browse files
Files changed (1) hide show
  1. setup-hf-config.mjs +211 -184
setup-hf-config.mjs CHANGED
@@ -5,10 +5,14 @@ import https from "node:https";
5
  // ============================================================
6
  // OpenClaw HF Spaces - Production Config Writer
7
  //
 
 
 
 
8
  // Priority order for base config:
9
- // 1. ~/.openclaw/openclaw.json exists (restored from Dataset) -> PATCH only
10
- // 2. /app/openclaw-template.json exists (from Space repo) -> use as base
11
- // 3. neither -> write minimal fresh
12
  //
13
  // What ALWAYS gets patched (from env vars):
14
  // gateway.auth <- OPENCLAW_GATEWAY_TOKEN
@@ -26,244 +30,267 @@ var WORKSPACE = path.join(STATE_DIR, "workspace");
26
  var SPACE_HOST = (process.env.SPACE_HOST || "").trim();
27
  var TEMPLATE = "/app/openclaw-template.json";
28
 
29
- console.log("[setup] Starting… HOME=" + HOME);
30
 
31
  function parseList(val) {
32
- if (!val || !val.trim()) return [];
33
- return val.split(",").map(function(s) { return s.trim(); }).filter(Boolean);
34
  }
35
  function envStr(key) { return (process.env[key] || "").trim(); }
36
 
37
- // –– auth –––––––––––––––––––––––––
38
  var gatewayToken = envStr("OPENCLAW_GATEWAY_TOKEN");
39
  var gatewayPassword = envStr("OPENCLAW_GATEWAY_PASSWORD");
40
  if (!gatewayToken && !gatewayPassword) {
41
- console.error("[setup] FATAL: set OPENCLAW_GATEWAY_TOKEN in Secrets");
42
- process.exit(0);
43
  }
44
 
45
- // –– model ———————————————––
46
  var defaultModel = envStr("OPENCLAW_HF_DEFAULT_MODEL") || "google/gemini-2.0-flash";
47
 
48
- // –– provider keys —————————————–
49
  var EXCLUDE_PREFIXES = [
50
- "OPENCLAW_", "SPACE_", "SYSTEM_", "HF_",
51
- "NODE_", "PATH", "HOME", "USER", "PWD", "LANG", "LC_",
52
- "npm_", "HOSTNAME", "SHELL", "TERM", "SHLVL"
53
  ];
54
  var INCLUDE_SUFFIXES = [
55
- "_API_KEY", "_SECRET_KEY", "_ACCESS_TOKEN",
56
- "_BOT_TOKEN", "_AUTH_TOKEN", "_APP_KEY"
57
  ];
58
  function isProviderKey(k) {
59
- var i;
60
- for (i = 0; i < EXCLUDE_PREFIXES.length; i++) {
61
- if (k.indexOf(EXCLUDE_PREFIXES[i]) === 0) return false;
62
- }
63
- for (i = 0; i < INCLUDE_SUFFIXES.length; i++) {
64
- var s = INCLUDE_SUFFIXES[i];
65
- if (k.length > s.length && k.indexOf(s) === k.length - s.length) return true;
66
- }
67
- return false;
68
  }
69
  var providerKeys = Object.keys(process.env).filter(function(k) {
70
- return isProviderKey(k) && (process.env[k] || "").trim();
71
  }).sort();
72
 
73
  console.log("[setup] Provider keys (" + providerKeys.length + "): " + providerKeys.join(", "));
74
 
75
- // –– trusted proxies β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
76
  var envProxies = parseList(envStr("OPENCLAW_GATEWAY_TRUSTED_PROXIES"));
77
  var trustedProxies = envProxies.length > 0 ? envProxies : [
78
- "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
79
- "10.16.0.0/12", "10.20.0.0/12",
80
- "10.16.4.123", "10.16.7.92", "10.16.18.232",
81
- "10.16.34.155", "10.16.43.133", "10.16.1.206",
82
- "10.16.37.110", "10.16.43.246",
83
- "10.20.1.9", "10.20.1.222",
84
- "10.20.26.157", "10.20.31.87",
85
- "10.20.0.1", "172.17.0.1", "127.0.0.1"
86
  ];
87
 
88
- // –– helpers ———————————————–
89
  function buildAuth() {
90
- return gatewayToken
91
- ? { mode: "token", token: gatewayToken }
92
- : { mode: "password", password: gatewayPassword };
93
  }
94
  function buildEnvVars() {
95
- var vars = {};
96
- providerKeys.forEach(function(pk) { vars[pk] = (process.env[pk] || "").trim(); });
97
- return vars;
98
  }
99
 
100
  // Patch model provider API keys from env vars
101
- // Maps provider name patterns to env var names
102
  var PROVIDER_KEY_MAP = {
103
- "openrouter": "OPENROUTER_API_KEY",
104
- "siliconflow": "SILICONFLOW_API_KEY",
105
- "deepseek": "DEEPSEEK_API_KEY",
106
- "groq": "GROQ_API_KEY",
107
- "google": "GOOGLE_API_KEY",
108
- "gemini": "GEMINI_API_KEY",
109
- "minimax": "MINIMAX_API_KEY",
110
- "zai": "ZAI_API_KEY",
111
- "github": "GITHUB_MODELS_API_KEY",
112
- "openai": "OPENAI_API_KEY",
113
- "anthropic": "ANTHROPIC_API_KEY"
114
  };
115
 
116
  function patchProviderKeys(config) {
117
- if (!config.models || !config.models.providers) return;
118
- var providers = config.models.providers;
119
- Object.keys(providers).forEach(function(providerName) {
120
- var lower = providerName.toLowerCase();
121
- var envKey = PROVIDER_KEY_MAP[lower];
122
- if (envKey && process.env[envKey]) {
123
- providers[providerName].apiKey = process.env[envKey].trim();
124
- console.log("[setup] Provider key set: " + providerName + " <- " + envKey);
125
- } else {
126
- // Try to find a matching env var by provider name
127
- var found = providerKeys.find(function(k) {
128
- return k.toLowerCase().indexOf(lower) >= 0;
129
- });
130
- if (found) {
131
- providers[providerName].apiKey = process.env[found].trim();
132
- console.log("[setup] Provider key set: " + providerName + " <- " + found);
133
- } else if (providers[providerName].apiKey === "WILL_BE_SET_BY_ENV") {
134
- delete providers[providerName].apiKey;
135
- }
136
- }
137
- });
138
  }
139
 
140
- // –– Telegram –––––––––––––––––––––––
141
  function tgRequest(token, method, body) {
142
- return new Promise(function(resolve) {
143
- var data = JSON.stringify(body || {});
144
- var req = https.request({
145
- hostname: "api.telegram.org",
146
- path: "/bot" + token + "/" + method,
147
- method: "POST",
148
- headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) }
149
- }, function(res) {
150
- var buf = "";
151
- res.on("data", function(c) { buf += c; });
152
- res.on("end", function() { try { resolve(JSON.parse(buf)); } catch (e) { resolve(null); } });
153
- });
154
- req.on("error", function() { resolve(null); });
155
- req.setTimeout(8000, function() { req.destroy(); resolve(null); });
156
- req.write(data);
157
- req.end();
158
- });
159
  }
160
 
161
  async function setupWebhook(token) {
162
- if (!SPACE_HOST) { console.log("[setup] Telegram: no SPACE_HOST for webhook"); return; }
163
- var url = "https://" + SPACE_HOST + "/tg-webhook";
164
- var r = await tgRequest(token, "setWebhook", {
165
- url: url, drop_pending_updates: true, max_connections: 10
166
- });
167
- if (r && r.ok) {
168
- console.log("[setup] Telegram: webhook -> " + url);
169
- } else {
170
- console.log("[setup] Telegram: webhook failed. Open in browser:");
171
- console.log(" https://api.telegram.org/bot" + token + "/setWebhook?url=" + url + "&drop_pending_updates=true");
 
172
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  }
174
 
175
- // –– apply all env patches to a config object –––––––
176
  function applyEnvPatches(config, token) {
177
- // auth
178
- config.gateway = config.gateway || {};
179
- config.gateway.auth = buildAuth();
180
- config.gateway.controlUi = config.gateway.controlUi || {};
181
- config.gateway.controlUi.allowInsecureAuth = true;
182
- config.gateway.controlUi.allowedOrigins = ["*"];
183
- config.gateway.controlUi.dangerouslyDisableDeviceAuth = true;
184
- config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true;
185
- if (!config.gateway.trustedProxies || config.gateway.trustedProxies.length < 5) {
186
- config.gateway.trustedProxies = trustedProxies;
187
- }
188
- // agents
189
- config.agents = config.agents || {};
190
- config.agents.defaults = config.agents.defaults || {};
191
- config.agents.defaults.workspace = WORKSPACE;
192
- if (config.agents.defaults.model === "WILL_BE_SET_BY_ENV") {
193
- config.agents.defaults.model = defaultModel;
194
- }
195
- // env.vars
196
- config.env = config.env || {};
197
- config.env.vars = buildEnvVars();
198
- // model provider keys
199
- patchProviderKeys(config);
200
- // telegram
201
- if (token) {
202
- config.channels = config.channels || {};
203
- config.channels.telegram = config.channels.telegram || {};
204
- config.channels.telegram.enabled = true;
205
- config.channels.telegram.accounts = config.channels.telegram.accounts || {};
206
- config.channels.telegram.accounts.main = {
207
- botToken: token,
208
- apiRoot: "https://api.telegram.org"
209
- };
210
- }
211
- return config;
212
  }
213
 
214
- // –– main –––––––––––––––––––––––––
215
  (async function() {
216
- fs.mkdirSync(STATE_DIR, { recursive: true });
217
- fs.mkdirSync(WORKSPACE, { recursive: true });
218
- fs.mkdirSync(path.join(WORKSPACE, "memory"), { recursive: true });
219
 
220
- var token = envStr("TELEGRAM_BOT_TOKEN");
221
- if (token) await setupWebhook(token);
222
- else console.log("[setup] Telegram: disabled (no TELEGRAM_BOT_TOKEN)");
223
 
224
- var config = null;
225
- var mode = "";
 
226
 
227
- // Priority 1: existing config from Dataset restore
228
- if (fs.existsSync(CONFIG_PATH)) {
229
- try {
230
- config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8").trim());
231
- mode = "patch (restored from Dataset)";
232
- } catch (e) { config = null; }
233
- }
234
 
235
- // Priority 2: Space repo template
236
- if (!config && fs.existsSync(TEMPLATE)) {
237
- try {
238
- config = JSON.parse(fs.readFileSync(TEMPLATE, "utf-8").trim());
239
- mode = "template (from Space repo openclaw.json)";
240
- } catch (e) { config = null; }
241
- }
242
 
243
- // Priority 3: minimal fresh config
244
- if (!config) {
245
- config = {
246
- gateway: {}, agents: { defaults: {} }, env: { vars: {} }
247
- };
248
- mode = "fresh (no template found)";
249
- }
250
 
251
- config = applyEnvPatches(config, token);
 
 
 
 
 
 
252
 
253
- if (fs.existsSync(CONFIG_PATH)) {
254
- fs.copyFileSync(CONFIG_PATH, CONFIG_PATH + ".bak");
255
- }
256
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
 
 
257
 
258
- console.log("[setup] Done. Mode: " + mode);
259
- console.log("[setup] auth = " + (gatewayToken ? "token" : "password"));
260
- console.log("[setup] model = " + (config.agents.defaults.model || defaultModel));
261
- console.log("[setup] workspace = " + WORKSPACE);
262
- console.log("[setup] proxies = " + trustedProxies.length);
263
- console.log("[setup] env.vars = " + providerKeys.length);
264
- var provCount = config.models && config.models.providers ? Object.keys(config.models.providers).length : 0;
265
- console.log("[setup] providers = " + provCount);
 
266
  })().catch(function(e) {
267
- console.error("[setup] Fatal: " + e.message);
268
- process.exit(0);
269
  });
 
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
 
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_",
56
+ "npm_", "HOSTNAME", "SHELL", "TERM", "SHLVL"
57
  ];
58
  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++) {
65
+ if (k.indexOf(EXCLUDE_PREFIXES[i]) === 0) return false;
66
+ }
67
+ for (i = 0; i < INCLUDE_SUFFIXES.length; i++) {
68
+ var s = INCLUDE_SUFFIXES[i];
69
+ if (k.length > s.length && k.indexOf(s) === k.length - s.length) return true;
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 || {});
146
+ var req = https.request({
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
  });