| import fs from "node:fs"; |
| import path from "node:path"; |
| import https from "node:https"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| var HOME = process.env.OPENCLAW_HOME || process.env.HOME || "/home/user"; |
| var STATE_DIR = path.join(HOME, ".openclaw"); |
| var CONFIG_PATH = path.join(STATE_DIR, "openclaw.json"); |
| var WORKSPACE = path.join(STATE_DIR, "workspace"); |
| var SPACE_HOST = (process.env.SPACE_HOST || "").trim(); |
| var TEMPLATE = "/app/openclaw-template.json"; |
|
|
| function log(msg) { console.log("[setup] " + msg); } |
| function err(msg) { console.error("[setup] ERROR: " + msg); } |
|
|
| log("Starting... HOME=" + HOME); |
|
|
| |
| |
| |
| function envStr(key) { return (process.env[key] || "").trim(); } |
|
|
| function parseList(val) { |
| if (!val || !val.trim()) return []; |
| return val.split(",").map(function(s){ return s.trim(); }).filter(Boolean); |
| } |
|
|
| |
| |
| |
| var gatewayToken = envStr("OPENCLAW_GATEWAY_TOKEN"); |
| var gatewayPassword = envStr("OPENCLAW_GATEWAY_PASSWORD"); |
| if (!gatewayToken && !gatewayPassword) { |
| err("FATAL: set OPENCLAW_GATEWAY_TOKEN in Secrets"); |
| process.exit(0); |
| } |
|
|
| |
| |
| |
| var defaultModel = envStr("OPENCLAW_HF_DEFAULT_MODEL") || "google/gemini-2.0-flash"; |
|
|
| |
| |
| |
| var EXCLUDE_PREFIXES = [ |
| "OPENCLAW_", "SPACE_", "SYSTEM_", "HF_", |
| "NODE_", "PATH", "HOME", "USER", "PWD", "LANG", "LC_", |
| "npm_", "HOSTNAME", "SHELL", "TERM", "SHLVL" |
| ]; |
| var INCLUDE_SUFFIXES = [ |
| "_API_KEY", "_SECRET_KEY", "_ACCESS_TOKEN", |
| "_BOT_TOKEN", "_AUTH_TOKEN", "_APP_KEY" |
| ]; |
|
|
| function isProviderKey(k) { |
| var i; |
| for (i = 0; i < EXCLUDE_PREFIXES.length; i++) { |
| if (k.indexOf(EXCLUDE_PREFIXES[i]) === 0) return false; |
| } |
| for (i = 0; i < INCLUDE_SUFFIXES.length; i++) { |
| var s = INCLUDE_SUFFIXES[i]; |
| if (k.length > s.length && k.indexOf(s) === k.length - s.length) return true; |
| } |
| return false; |
| } |
|
|
| var providerKeys = Object.keys(process.env).filter(function(k) { |
| return isProviderKey(k) && envStr(k); |
| }).sort(); |
|
|
| log("Provider keys (" + providerKeys.length + "): " + providerKeys.join(", ")); |
|
|
| |
| |
| |
| var envProxies = parseList(envStr("OPENCLAW_GATEWAY_TRUSTED_PROXIES")); |
| var trustedProxies = envProxies.length > 0 ? envProxies : [ |
| "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", |
| "10.16.0.0/12", "10.20.0.0/12", |
| "10.16.4.123", "10.16.7.92", "10.16.18.232", |
| "10.16.34.155", "10.16.43.133", "10.16.1.206", |
| "10.16.37.110", "10.16.43.246", |
| "10.20.1.9", "10.20.1.222", "10.20.26.157", "10.20.31.87", |
| "10.20.0.1", "172.17.0.1", "127.0.0.1" |
| ]; |
|
|
| |
| |
| |
| var PROVIDER_KEY_MAP = { |
| "openrouter": "OPENROUTER_API_KEY", |
| "siliconflow": "SILICONFLOW_API_KEY", |
| "deepseek": "DEEPSEEK_API_KEY", |
| "groq": "GROQ_API_KEY", |
| "google": "GOOGLE_API_KEY", |
| "gemini": "GEMINI_API_KEY", |
| "minimax": "MINIMAX_API_KEY", |
| "zai": "ZAI_API_KEY", |
| "github": "GITHUB_MODELS_API_KEY", |
| "github-copilot": "GITHUB_MODELS_API_KEY", |
| "openai": "OPENAI_API_KEY", |
| "anthropic": "ANTHROPIC_API_KEY" |
| }; |
|
|
| function patchProviderKeys(config) { |
| if (!config.models || !config.models.providers) return; |
| var providers = config.models.providers; |
| Object.keys(providers).forEach(function(name) { |
| var lower = name.toLowerCase(); |
| var envKey = PROVIDER_KEY_MAP[lower]; |
| var val = envKey ? envStr(envKey) : ""; |
| if (!val) { |
| |
| var found = providerKeys.find(function(k) { |
| return k.toLowerCase().indexOf(lower) >= 0; |
| }); |
| if (found) val = envStr(found); |
| } |
| if (val) { |
| providers[name].apiKey = val; |
| log("Provider key set: " + name + " <- " + (envKey || "fuzzy")); |
| } else if (providers[name].apiKey === "WILL_BE_SET_BY_ENV") { |
| delete providers[name].apiKey; |
| log("Provider key missing (skipped): " + name); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| function tgRequest(token, method, body) { |
| return new Promise(function(resolve) { |
| var data = JSON.stringify(body || {}); |
| var req = https.request({ |
| hostname: "api.telegram.org", |
| path: "/bot" + token + "/" + method, |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| "Content-Length": Buffer.byteLength(data) |
| } |
| }, function(res) { |
| var buf = ""; |
| res.on("data", function(c) { buf += c; }); |
| res.on("end", function() { |
| try { resolve(JSON.parse(buf)); } catch(e) { resolve(null); } |
| }); |
| }); |
| req.on("error", function() { resolve(null); }); |
| req.setTimeout(10000, function() { req.destroy(); resolve(null); }); |
| req.write(data); |
| req.end(); |
| }); |
| } |
|
|
| async function setupWebhook(token) { |
| if (!SPACE_HOST) { |
| log("Telegram: no SPACE_HOST - webhook skipped"); |
| return; |
| } |
| var targetUrl = "https://" + SPACE_HOST + "/tg-webhook"; |
|
|
| |
| var info = await tgRequest(token, "getWebhookInfo", {}); |
| if (info && info.ok && info.result && info.result.url === targetUrl) { |
| log("Telegram: webhook already set -> " + targetUrl); |
| return; |
| } |
|
|
| var r = await tgRequest(token, "setWebhook", { |
| url: targetUrl, drop_pending_updates: true, max_connections: 10 |
| }); |
| if (r && r.ok) { |
| log("Telegram: webhook registered -> " + targetUrl); |
| } else { |
| var desc = r && r.description ? r.description : "unknown error"; |
| log("Telegram: webhook failed (" + desc + ")"); |
| log("Telegram: set manually at:"); |
| log(" https://api.telegram.org/bot" + token + "/setWebhook?url=" + targetUrl + "&drop_pending_updates=true"); |
| } |
| } |
|
|
| |
| |
| |
| function seedWorkspace() { |
| var soul = path.join(WORKSPACE, "SOUL.md"); |
| if (!fs.existsSync(soul)) { |
| fs.writeFileSync(soul, [ |
| "# Soul", |
| "", |
| "You are a helpful, warm, concise AI assistant.", |
| "", |
| "## Language", |
| "", |
| "Default language: Simplified Chinese.", |
| "Always reply in Chinese unless the user writes in another language first.", |
| "", |
| "## Tone", |
| "", |
| "- Natural and friendly, not overly formal", |
| "- Concise and to the point" |
| ].join("\n") + "\n", "utf-8"); |
| log("Seeded SOUL.md"); |
| } |
| var mem = path.join(WORKSPACE, "MEMORY.md"); |
| if (!fs.existsSync(mem)) { |
| fs.writeFileSync(mem, [ |
| "# Long-term Memory", |
| "", |
| "<!-- OpenClaw writes important facts here. -->" |
| ].join("\n") + "\n", "utf-8"); |
| log("Seeded MEMORY.md"); |
| } |
| } |
|
|
| |
| |
| |
| function applyEnvPatches(config, tgToken) { |
| |
| config.gateway = config.gateway || {}; |
| config.gateway.auth = gatewayToken |
| ? { mode: "token", token: gatewayToken } |
| : { mode: "password", password: gatewayPassword }; |
|
|
| |
| config.gateway.controlUi = config.gateway.controlUi || {}; |
| config.gateway.controlUi.allowInsecureAuth = true; |
| config.gateway.controlUi.allowedOrigins = ["*"]; |
| config.gateway.controlUi.dangerouslyDisableDeviceAuth = true; |
| config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true; |
|
|
| |
| config.gateway.trustedProxies = trustedProxies; |
|
|
| |
| config.agents = config.agents || {}; |
| config.agents.defaults = config.agents.defaults || {}; |
| config.agents.defaults.workspace = WORKSPACE; |
| if (!config.agents.defaults.model || |
| config.agents.defaults.model === "WILL_BE_SET_BY_ENV") { |
| config.agents.defaults.model = defaultModel; |
| } |
|
|
| |
| config.env = config.env || {}; |
| config.env.vars = {}; |
| providerKeys.forEach(function(k) { config.env.vars[k] = envStr(k); }); |
|
|
| |
| patchProviderKeys(config); |
|
|
| |
| if (tgToken) { |
| config.channels = config.channels || {}; |
| config.channels.telegram = config.channels.telegram || {}; |
| config.channels.telegram.enabled = true; |
| config.channels.telegram.accounts = config.channels.telegram.accounts || {}; |
| config.channels.telegram.accounts.main = { |
| botToken: tgToken, |
| apiRoot: "https://api.telegram.org" |
| }; |
| } |
|
|
| return config; |
| } |
|
|
| |
| |
| |
| (async function() { |
| |
| fs.mkdirSync(STATE_DIR, { recursive: true }); |
| fs.mkdirSync(WORKSPACE, { recursive: true }); |
| fs.mkdirSync(path.join(WORKSPACE, "memory"), { recursive: true }); |
|
|
| |
| seedWorkspace(); |
|
|
| |
| var tgToken = envStr("TELEGRAM_BOT_TOKEN"); |
| if (tgToken) { |
| await setupWebhook(tgToken); |
| } else { |
| log("Telegram: disabled (no TELEGRAM_BOT_TOKEN)"); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| var config = null; |
| var mode = ""; |
|
|
| |
| if (fs.existsSync(CONFIG_PATH)) { |
| try { |
| config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8").trim()); |
| mode = "bucket (full config preserved)"; |
| } catch(e) { |
| log("Bucket config unreadable (" + e.message + ") - falling back to template"); |
| } |
| } |
|
|
| |
| if (!config && fs.existsSync(TEMPLATE)) { |
| try { |
| config = JSON.parse(fs.readFileSync(TEMPLATE, "utf-8").trim()); |
| mode = "template (first boot - seeding bucket)"; |
| } catch(e) { |
| log("Template unreadable (" + e.message + ")"); |
| } |
| } |
|
|
| if (!config) { |
| config = { gateway: {}, agents: { defaults: {} }, env: { vars: {} } }; |
| mode = "fresh (no template found)"; |
| } |
|
|
| |
| config = applyEnvPatches(config, tgToken); |
|
|
| |
| if (fs.existsSync(CONFIG_PATH)) { |
| fs.copyFileSync(CONFIG_PATH, CONFIG_PATH + ".bak"); |
| } |
| fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8"); |
|
|
| |
| var provCount = config.models && config.models.providers |
| ? Object.keys(config.models.providers).length : 0; |
| var modelCount = config.models && config.models.providers |
| ? Object.values(config.models.providers).reduce(function(n, p) { |
| return n + (p.models ? p.models.length : 0); |
| }, 0) : 0; |
|
|
| log("Done. Mode: " + mode); |
| log(" auth = " + (gatewayToken ? "token" : "password")); |
| log(" model = " + (config.agents.defaults.model || defaultModel)); |
| log(" workspace = " + WORKSPACE); |
| log(" proxies = " + trustedProxies.length); |
| log(" env.vars = " + providerKeys.length); |
| log(" providers = " + provCount + " (" + modelCount + " models)"); |
| log(" storage = bucket at /data (100 GB persistent)"); |
|
|
| })().catch(function(e) { |
| err("Fatal: " + e.message); |
| process.exit(0); |
| }); |
|
|