hugging-claw / setup-hf-config.mjs
ubix's picture
Update setup-hf-config.mjs
dc99709 verified
raw
history blame
10.4 kB
#!/usr/bin/env node
/**
* One-time setup for OpenClaw on Hugging Face Spaces.
* Runs at container startup; writes or merges openclaw.json from env (Secrets/Variables):
*
* Custom Ollama provider (hf-ollama-qwen3-vl) β€” PRIMARY default model:
* - providers["hf-ollama-qwen3-vl"] registered with baseUrl + apiKey
* - agents.defaults.model.primary = "hf-ollama-qwen3-vl/voytas26/openclaw-qwen3vl-8b-opt"
* - Configure via:
* OPENCLAW_OLLAMA_BASE_URL (default: https://ubix-Clawd.hf.space/v1)
* OPENCLAW_OLLAMA_API_KEY (default: "ollama")
* - Falls back to OPENCLAW_HF_DEFAULT_MODEL (or HF DeepSeek-R1) when OPENCLAW_OLLAMA_BASE_URL
* is explicitly set to empty string to disable.
*
* OpenRouter / Perplexity (search-augmented model):
* - providers.openrouter.apiKey from OPENROUTER_API_KEY
* - agents.defaults.model.search set to openrouter/perplexity/sonar
* (only written when OPENROUTER_API_KEY is present)
*
* Gateway auth:
* - gateway.auth: OPENCLAW_GATEWAY_TOKEN (token) or OPENCLAW_GATEWAY_PASSWORD (password)
* token wins if both are set
* - gateway.controlUi.dangerouslyDisableDeviceAuth when auth is set (no device pairing in Spaces)
*
* Networking:
* - gateway.trustedProxies from OPENCLAW_GATEWAY_TRUSTED_PROXIES, or default HF proxy IPs
* - gateway.controlUi.allowedOrigins from OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS (comma-separated)
*
* HF_TOKEN is read by the gateway at runtime; this script only writes the above into config.
*/
import fs from "node:fs";
import path from "node:path";
const home = process.env.OPENCLAW_HOME || process.env.HOME || "/home/user";
const stateDir = path.join(home, ".openclaw");
const configPath = path.join(stateDir, "openclaw.json");
// ── Gateway token (supports file-mounted secrets for platforms that mount secrets as files) ──
function readGatewayToken() {
const fromEnv = process.env.OPENCLAW_GATEWAY_TOKEN?.trim();
if (fromEnv) return fromEnv;
const filePath = process.env.OPENCLAW_GATEWAY_TOKEN_FILE?.trim();
if (filePath && fs.existsSync(filePath)) {
try {
return fs.readFileSync(filePath, "utf-8").trim();
} catch {
return "";
}
}
return "";
}
// ── Env reads ──────────────────────────────────────────────────────────────────────────────────
// Custom Ollama/OpenAI-compat provider hosted on HF Space
// Set OPENCLAW_OLLAMA_BASE_URL="" to disable and fall back to HF Inference
const ollamaBaseUrl = process.env.OPENCLAW_OLLAMA_BASE_URL !== undefined
? process.env.OPENCLAW_OLLAMA_BASE_URL.trim()
: "https://ubix-Clawd.hf.space/v1";
const ollamaApiKey = process.env.OPENCLAW_OLLAMA_API_KEY?.trim() || "ollama";
const ollamaEnabled = ollamaBaseUrl.length > 0;
// Fallback HF Inference model (used only when Ollama provider is disabled)
const hfFallbackModel =
process.env.OPENCLAW_HF_DEFAULT_MODEL?.trim() ||
"huggingface/deepseek-ai/DeepSeek-R1";
// Resolved primary model
const defaultModel = ollamaEnabled
? "hf-ollama-qwen3-vl/voytas26/openclaw-qwen3vl-8b-opt"
: hfFallbackModel;
const gatewayToken = readGatewayToken();
const gatewayPassword = process.env.OPENCLAW_GATEWAY_PASSWORD?.trim();
const openrouterKey = process.env.OPENROUTER_API_KEY?.trim();
// ── Trusted proxies ────────────────────────────────────────────────────────────────────────────
// Default HF Space proxy IPs so the Control UI works without extra config.
// Override with OPENCLAW_GATEWAY_TRUSTED_PROXIES (comma-separated) if you see
// "Proxy headers detected from untrusted address" in the logs.
const DEFAULT_HF_TRUSTED_PROXY_IPS = [
"10.16.4.123",
"10.16.34.155",
"10.20.1.9",
"10.20.1.222",
"10.20.26.157",
"10.20.31.87",
];
const trustedProxiesRaw = process.env.OPENCLAW_GATEWAY_TRUSTED_PROXIES?.trim();
const trustedProxies =
trustedProxiesRaw && trustedProxiesRaw.length > 0
? trustedProxiesRaw.split(",").map((s) => s.trim()).filter(Boolean)
: DEFAULT_HF_TRUSTED_PROXY_IPS;
// ── Allowed origins ────────────────────────────────────────────────────────────────────────────
const allowedOriginsRaw = process.env.OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS?.trim();
const allowedOrigins = allowedOriginsRaw
? allowedOriginsRaw.split(",").map((s) => s.trim()).filter(Boolean)
: [];
// ── Load existing config (merge, don't overwrite) ─────────────────────────────────────────────
let config = {};
if (fs.existsSync(configPath)) {
try {
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
} catch {
// keep config empty on parse error
}
}
// ── 1. Register providers & set default model ─────────────────────────────────────────────────
if (!config.providers) config.providers = {};
if (!config.agents) config.agents = {};
if (!config.agents.defaults) config.agents.defaults = {};
if (!config.agents.defaults.model) config.agents.defaults.model = {};
// 1a. Custom Ollama/OpenAI-compat provider (hf-ollama-qwen3-vl)
if (ollamaEnabled) {
config.providers["hf-ollama-qwen3-vl"] = {
baseUrl: ollamaBaseUrl,
apiKey: ollamaApiKey,
api: "openai-completions",
models: [
{
id: "voytas26/openclaw-qwen3vl-8b-opt",
name: "qwen3vl (HF Space - CPU)",
reasoning: true,
input: ["text"],
contextWindow: 128000,
maxTokens: 32000,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
],
};
console.log(
`[openclaw-hf-setup] hf-ollama-qwen3-vl provider registered -> ${ollamaBaseUrl}`
);
} else {
console.log(
"[openclaw-hf-setup] OPENCLAW_OLLAMA_BASE_URL is empty β€” Ollama provider disabled, " +
`falling back to: ${hfFallbackModel}`
);
}
// 1b. Set primary default model
config.agents.defaults.model.primary = defaultModel;
// ── 2. OpenRouter provider + Perplexity Sonar as search model ─────────────────────────────────
if (openrouterKey) {
config.providers.openrouter = {
apiKey: openrouterKey,
};
// Use Perplexity Sonar (web-search-augmented) for search/research tasks.
config.agents.defaults.model.search = "openrouter/perplexity/sonar";
console.log(
"[openclaw-hf-setup] OpenRouter configured -> search model: openrouter/perplexity/sonar"
);
} else {
console.warn(
"[openclaw-hf-setup] OPENROUTER_API_KEY not set. " +
"Perplexity Sonar search model will not be available. " +
"Add OPENROUTER_API_KEY in Space Secrets to enable web search."
);
}
// ── 3. Gateway auth ────────────────────────────────────────────────────────────────────────────
const useTokenAuth = Boolean(gatewayToken);
const usePasswordAuth = Boolean(gatewayPassword) && !useTokenAuth;
if (useTokenAuth || usePasswordAuth) {
if (!config.gateway) config.gateway = {};
if (!config.gateway.auth) config.gateway.auth = {};
if (useTokenAuth) {
config.gateway.auth.mode = "token";
config.gateway.auth.token = gatewayToken;
} else {
config.gateway.auth.mode = "password";
config.gateway.auth.password = gatewayPassword;
}
}
// Disable device pairing β€” Spaces have no CLI to approve pairing requests.
// Control UI will accept token/password only.
if (useTokenAuth || usePasswordAuth) {
if (!config.gateway) config.gateway = {};
if (!config.gateway.controlUi) config.gateway.controlUi = {};
config.gateway.controlUi.dangerouslyDisableDeviceAuth = true;
}
// ── 4. Trusted proxies ─────────────────────────────────────────────────────────────────────────
if (!config.gateway) config.gateway = {};
config.gateway.trustedProxies = trustedProxies;
// ── 5. Allowed origins ─────────────────────────────────────────────────────────────────────────
if (allowedOrigins.length > 0) {
if (!config.gateway.controlUi) config.gateway.controlUi = {};
config.gateway.controlUi.allowedOrigins = allowedOrigins;
}
// ── Write config ───────────────────────────────────────────────────────────────────────────────
fs.mkdirSync(stateDir, { recursive: true });
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
// ── Startup summary ────────────────────────────────────────────────────────────────────────────
const authKind = useTokenAuth ? "token" : usePasswordAuth ? "password" : "none";
const parts = [
`token_present=${useTokenAuth ? "1" : "0"}`,
`password_present=${usePasswordAuth ? "1" : "0"}`,
`auth=${authKind}`,
`ollama_provider=${ollamaEnabled ? "1" : "0"}`,
`default_model=${defaultModel}`,
`openrouter_configured=${openrouterKey ? "1" : "0"}`,
`search_model=${openrouterKey ? "openrouter/perplexity/sonar" : "none"}`,
`trustedProxies=${trustedProxies.length}`,
`allowedOrigins=${allowedOrigins.length}`,
];
console.log(`[openclaw-hf-setup] ${parts.join(" ")} -> ${configPath}`);
if (authKind === "none") {
console.warn(
"[openclaw-hf-setup] No auth set. " +
"Add OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD in Space Secrets, then restart."
);
}