Download setup-hf-config.mjs from Supertbot/suing: direct link, hf CLI and curl.
- Browser
- Download file 8.73 kB
-
https://huggingface.co/spaces/Supertbot/suing/resolve/main/setup-hf-config.mjs
- Command line
-
hf download hf://spaces/Supertbot/suing/setup-hf-config.mjs
-
curl -L -o setup-hf-config.mjs https://huggingface.co/spaces/Supertbot/suing/resolve/main/setup-hf-config.mjs
8.73 kB
| import fs from "node:fs"; | |
| import path from "node:path"; | |
| // ============================================================ | |
| // 生成运行配置 (实时状态在本地磁盘 /home/user) | |
| // 基础配置优先级: | |
| // 1. /data/.openclaw/openclaw.json (bucket 里的旧配置, 只读, 已被 2026.9.7 doctor 验证过) | |
| // 2. /app/openclaw-template.json | |
| // 3. 最小空配置 | |
| // 每次启动都会用 Secrets 覆盖: 网关令牌 / 模型 API Key / 可选 Telegram 代理 | |
| // ============================================================ | |
| var HOME = process.env.OPENCLAW_HOME || "/home/user"; | |
| var STATE_DIR = path.join(HOME, ".openclaw"); | |
| var CONFIG_PATH = path.join(STATE_DIR, "openclaw.json"); | |
| var TEMPLATE = "/app/openclaw-template.json"; | |
| var LEGACY = process.env.LEGACY_CONFIG || "/data/.openclaw/openclaw.json"; | |
| var LEGACY_WS = "/data/.openclaw/workspace"; | |
| function log(m) { console.log("[setup] " + m); } | |
| function err(m) { console.error("[setup] ERROR: " + m); } | |
| function envStr(k) { return (process.env[k] || "").trim(); } | |
| function loadJson(p) { | |
| try { return JSON.parse(fs.readFileSync(p, "utf-8").trim()); } catch (e) { return null; } | |
| } | |
| function writableDir(d) { | |
| try { fs.mkdirSync(d, { recursive: true }); fs.accessSync(d, fs.constants.W_OK); return true; } | |
| catch (e) { return false; } | |
| } | |
| function stripKey(o, k) { | |
| if (Array.isArray(o)) { o.forEach(function (x) { stripKey(x, k); }); } | |
| else if (o && typeof o === "object") { | |
| delete o[k]; | |
| Object.keys(o).forEach(function (kk) { stripKey(o[kk], k); }); | |
| } | |
| } | |
| function mapStrings(o, from, to) { | |
| if (Array.isArray(o)) { o.forEach(function (x, i) { if (typeof x === "string") { if (x.indexOf(from) === 0) o[i] = to + x.slice(from.length); } else mapStrings(x, from, to); }); } | |
| else if (o && typeof o === "object") { | |
| Object.keys(o).forEach(function (k) { | |
| var v = o[k]; | |
| if (typeof v === "string") { if (v.indexOf(from) === 0) o[k] = to + v.slice(from.length); } | |
| else mapStrings(v, from, to); | |
| }); | |
| } | |
| } | |
| function collectStrings(o, prefix, out) { | |
| if (Array.isArray(o)) o.forEach(function (x) { collectStrings(x, prefix, out); }); | |
| else if (o && typeof o === "object") Object.keys(o).forEach(function (k) { collectStrings(o[k], prefix, out); }); | |
| else if (typeof o === "string" && o.indexOf(prefix) === 0) out.push(o); | |
| } | |
| log("Starting... HOME=" + HOME); | |
| var gatewayToken = envStr("OPENCLAW_GATEWAY_TOKEN"); | |
| var config = null, mode = ""; | |
| var legacy = loadJson(LEGACY); | |
| if (legacy) { config = legacy; mode = "legacy bucket config (read-only import)"; } | |
| else { | |
| var tpl = loadJson(TEMPLATE); | |
| if (tpl) { config = tpl; mode = "template"; } | |
| } | |
| if (!config) { config = { gateway: {}, agents: { defaults: {} }, env: { vars: {} } }; mode = "fresh"; } | |
| // ── gateway ── | |
| config.gateway = config.gateway || {}; | |
| config.gateway.mode = "local"; | |
| config.gateway.port = 7860; | |
| config.gateway.bind = "lan"; | |
| if (gatewayToken) { | |
| config.gateway.auth = { mode: "token", token: gatewayToken }; | |
| if (!/^[A-Za-z0-9]{32,}$/.test(gatewayToken)) log("WARN: OPENCLAW_GATEWAY_TOKEN should be 32+ letters/digits"); | |
| } else if (config.gateway.auth && (config.gateway.auth.token || config.gateway.auth.password) && | |
| config.gateway.auth.token !== "WILL_BE_SET_BY_ENV") { | |
| log("WARN: OPENCLAW_GATEWAY_TOKEN not set - keeping the token from the imported config (rotate it!)"); | |
| } else { | |
| err("FATAL: set OPENCLAW_GATEWAY_TOKEN in Space Secrets"); | |
| process.exit(0); | |
| } | |
| config.gateway.controlUi = config.gateway.controlUi || {}; | |
| config.gateway.controlUi.allowInsecureAuth = true; | |
| config.gateway.controlUi.allowedOrigins = ["*"]; | |
| config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true; | |
| delete config.gateway.controlUi.dangerouslyDisableDeviceAuth; // 新版已废弃 | |
| var host = envStr("SPACE_HOST"); | |
| if (host && !config.gateway.publicOrigin) config.gateway.publicOrigin = "https://" + host; | |
| config.gateway.trustedProxies = [ | |
| "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1" | |
| ]; | |
| // ── agents: 去掉 agentDir, 工作区(纯文件)可留在 bucket, 不可写则落本地 ── | |
| config.agents = config.agents || {}; | |
| config.agents.defaults = config.agents.defaults || {}; | |
| stripKey(config.agents, "agentDir"); | |
| var wsOnBucket = envStr("WORKSPACE_ON_BUCKET") !== "0" && writableDir(LEGACY_WS); | |
| var localWs = path.join(STATE_DIR, "workspace"); | |
| if (!wsOnBucket) mapStrings(config.agents, LEGACY_WS, localWs); | |
| if (!config.agents.defaults.workspace || config.agents.defaults.workspace === "WILL_BE_SET_BY_ENV") { | |
| config.agents.defaults.workspace = wsOnBucket ? LEGACY_WS : localWs; | |
| } | |
| var wsList = []; collectStrings(config.agents, wsOnBucket ? LEGACY_WS : localWs, wsList); | |
| wsList.forEach(function (d) { try { fs.mkdirSync(d, { recursive: true }); } catch (e) { /* ignore */ } }); | |
| if (!config.agents.defaults.model || config.agents.defaults.model === "WILL_BE_SET_BY_ENV") { | |
| config.agents.defaults.model = envStr("OPENCLAW_HF_DEFAULT_MODEL") || "groq/llama-3.3-70b-versatile"; | |
| } | |
| log("workspace on " + (wsOnBucket ? "bucket (/data)" : "local disk") + ", dirs: " + wsList.length); | |
| // seed SOUL/MEMORY if missing | |
| var mainEntry = config.agents.entries && config.agents.entries.main; | |
| var mainWs = (mainEntry && mainEntry.workspace) || config.agents.defaults.workspace; | |
| try { | |
| fs.mkdirSync(path.join(mainWs, "memory"), { recursive: true }); | |
| var soul = path.join(mainWs, "SOUL.md"); | |
| if (!fs.existsSync(soul)) { | |
| fs.writeFileSync(soul, "# Soul\n\nYou are a helpful, warm, concise AI assistant.\nDefault language: Simplified Chinese.\n", "utf-8"); | |
| } | |
| var mem = path.join(mainWs, "MEMORY.md"); | |
| if (!fs.existsSync(mem)) fs.writeFileSync(mem, "# Long-term Memory\n", "utf-8"); | |
| } catch (e) { log("seed skipped: " + e.message); } | |
| // ── 模型 Key: 只在 Secrets 里有值时才覆盖 ── | |
| var 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-copilot": "GITHUB_MODELS_API_KEY", github: "GITHUB_MODELS_API_KEY", | |
| openai: "OPENAI_API_KEY", anthropic: "ANTHROPIC_API_KEY" | |
| }; | |
| var providers = (config.models && config.models.providers) || {}; | |
| Object.keys(providers).forEach(function (name) { | |
| var ek = MAP[name.toLowerCase()]; | |
| var v = ek ? envStr(ek) : ""; | |
| if (v) { providers[name].apiKey = v; log("Provider key from Secrets: " + name); } | |
| else if (providers[name].apiKey === "WILL_BE_SET_BY_ENV") { delete providers[name].apiKey; log("Provider key missing: " + name); } | |
| }); | |
| config.env = config.env || {}; | |
| config.env.vars = config.env.vars || {}; | |
| Object.keys(process.env).forEach(function (k) { | |
| if (/^(OPENCLAW_|SPACE_|SYSTEM_|HF_|NODE_|npm_)/.test(k)) return; | |
| if (/(_API_KEY|_SECRET_KEY|_ACCESS_TOKEN|_AUTH_TOKEN|_APP_KEY)$/.test(k) && envStr(k)) config.env.vars[k] = envStr(k); | |
| }); | |
| // ── 没有对应插件的自定义提供商(如 siliconflow)必须声明 api, 否则 models.json 加载失败 ── | |
| var KNOWN_API = ["openrouter", "deepseek", "groq", "minimax", "zai", "anthropic", "openai", "google", "gemini", "github", "github-copilot"]; | |
| Object.keys(providers).forEach(function (name) { | |
| var p = providers[name]; | |
| if (KNOWN_API.indexOf(name.toLowerCase()) >= 0 || p.api) return; | |
| var ms = p.models || []; | |
| if (ms.length && ms.every(function (m) { return m && m.api; })) return; | |
| p.api = "openai-completions"; | |
| log("api=openai-completions set for provider: " + name); | |
| }); | |
| // ── Telegram ── | |
| var tg = config.channels && config.channels.telegram; | |
| var tgToken = envStr("TELEGRAM_BOT_TOKEN"); | |
| if (!tg && tgToken) { | |
| config.channels = config.channels || {}; | |
| tg = config.channels.telegram = { enabled: true, accounts: { main: { botToken: tgToken, apiRoot: "https://api.telegram.org" } } }; | |
| } | |
| var accNames = tg && tg.accounts ? Object.keys(tg.accounts) : []; | |
| var proxy = envStr("TELEGRAM_PROXY"); | |
| if (tg && proxy) { | |
| accNames.forEach(function (n) { tg.accounts[n].proxy = proxy; }); | |
| log("Telegram proxy set on " + accNames.length + " account(s)"); | |
| } | |
| var apiRoot = envStr("TELEGRAM_API_ROOT"); | |
| if (tg && apiRoot) accNames.forEach(function (n) { tg.accounts[n].apiRoot = apiRoot; }); | |
| if (tg && accNames.length && !tg.defaultAccount) tg.defaultAccount = accNames.indexOf("custom-1") >= 0 ? "custom-1" : accNames[0]; | |
| log("Telegram accounts: " + (accNames.join(", ") || "none")); | |
| // ── 写出 ── | |
| fs.mkdirSync(STATE_DIR, { recursive: true }); | |
| fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 0o600 }); | |
| log("Done. base = " + mode + " | auth = token(" + (gatewayToken ? "env" : "imported") + ") | providers = " + Object.keys(providers).length); | |