Ctrl K
ComunidadGuías…

setup-config.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
// setup-config.js — Generates all config files for OpenVoiceUI Pinokio install.
// Reads API keys from PINOKIO_* environment variables (set by install.js env: block).
// This avoids the broken json.set / pinokio-input.json flow entirely.

const fs = require("fs");
const path = require("path");
const crypto = require("crypto");

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function getKey(envName) {
  // install.js passes keys as PINOKIO_<KEY_NAME> via env
  const v = process.env["PINOKIO_" + envName] || "";
  if (!v || v === "undefined" || v === "null" || v.startsWith("{{")) return "";
  return v.trim();
}

const PORT = getKey("PORT") || "5001";
const token = crypto.randomBytes(24).toString("hex");
const secret = crypto.randomBytes(32).toString("hex");

// ---------------------------------------------------------------------------
// 1. Write openclaw.json (nested gateway/agents format for v2026.3.2+)
// ---------------------------------------------------------------------------

const openclawConfig = {
  gateway: {
    mode: "local",
    port: 18791,
    bind: "lan",
    auth: { mode: "token", token: token },
    trustedProxies: ["127.0.0.1", "172.16.0.0/12", "10.0.0.0/8"],
    controlUi: {
      allowInsecureAuth: true,
      dangerouslyDisableDeviceAuth: true,
    },
  },
  agents: {
    defaults: {
      // No model forced here — OpenClaw auto-selects based on available API keys
      // in auth-profiles.json. User's provided keys drive model selection.
      thinkingDefault: "off",
      blockStreamingDefault: "on",
      blockStreamingBreak: "text_end",
      timeoutSeconds: 120,
      memorySearch: { enabled: true },
      compaction: {
        mode: "default",
        reserveTokens: 80000,
        keepRecentTokens: 8000,
        reserveTokensFloor: 80000,
        memoryFlush: { enabled: true, softThresholdTokens: 6000 },
      },
      contextPruning: {
        mode: "cache-ttl",
        ttl: "30m",
        keepLastAssistants: 3,
        softTrimRatio: 0.3,
        hardClearRatio: 0.5,
      },
    },
    list: [{ id: "main", default: true, workspace: "/root/.openclaw/workspace" }],
  },
  // Plugins configured by individual plugin installers
};

fs.mkdirSync("openclaw-data/workspace", { recursive: true });
// Mountpoint for the bundled agent skills (docker-compose mounts ./skills here read-only).
// Created now so Docker does not create openclaw-data/skills root-owned on Linux, which would
// stop you adding your own skills next to it. See docs/SKILLS.md.
fs.mkdirSync("openclaw-data/skills/openvoiceui", { recursive: true });
fs.writeFileSync(
  "openclaw-data/openclaw.json",
  JSON.stringify(openclawConfig, null, 2) + "\n"
);
console.log("  Wrote openclaw-data/openclaw.json");

// ---------------------------------------------------------------------------
// 2. Write .env
// ---------------------------------------------------------------------------

const envLines = [
  "# OpenVoiceUI — generated by Pinokio installer",
  `PORT=${PORT}`,
  "DOMAIN=localhost",
  `SECRET_KEY=${secret}`,
  "",
  "# OpenClaw Gateway",
  "CLAWDBOT_GATEWAY_URL=ws://127.0.0.1:18791",
  `CLAWDBOT_AUTH_TOKEN=${token}`,
  "GATEWAY_SESSION_KEY=voice-main-1",
  "",
  "# AI Provider Keys",
];

const envKeyList = [
  "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY",
  "OPENROUTER_API_KEY", "MISTRAL_API_KEY", "XAI_API_KEY", "ZAI_API_KEY",
  "CEREBRAS_API_KEY", "TOGETHER_API_KEY", "HF_TOKEN",
  "MOONSHOT_API_KEY", "KIMI_API_KEY", "MINIMAX_API_KEY", "QIANFAN_API_KEY",
  "MODELSTUDIO_API_KEY", "XIAOMI_API_KEY", "VOLCANO_ENGINE_API_KEY",
  "BYTEPLUS_API_KEY", "SYNTHETIC_API_KEY", "VENICE_API_KEY",
  "OPENCODE_ZEN_API_KEY", "KILOCODE_API_KEY", "AI_GATEWAY_API_KEY",
  "CLOUDFLARE_AI_GATEWAY_API_KEY", "LITELLM_API_KEY", "SUNO_API_KEY",
  "ELEVENLABS_API_KEY", "ELEVENLABS_VOICE_ID",
  "RESEMBLE_API_KEY", "RESEMBLE_VOICE_UUID",
];
for (const k of envKeyList) envLines.push(`${k}=${getKey(k)}`);

envLines.push(
  "",
  "# TTS",
  `GROQ_API_KEY=${getKey("GROQ_API_KEY")}`,
  "USE_GROQ=true",
  "USE_GROQ_TTS=true",
  "",
  "# STT",
  `DEEPGRAM_API_KEY=${getKey("DEEPGRAM_API_KEY")}`,
  "",
  "# Supertonic TTS",
  "SUPERTONIC_API_URL=http://supertonic:8765"
);

fs.writeFileSync(".env", envLines.join("\n") + "\n");
console.log("  Wrote .env");

// ---------------------------------------------------------------------------
// 3. Write auth-profiles.json (so OpenClaw has working providers on first start)
// ---------------------------------------------------------------------------

const providerMap = {
  ANTHROPIC_API_KEY: "anthropic",
  OPENAI_API_KEY: "openai",
  GEMINI_API_KEY: "google",
  OPENROUTER_API_KEY: "openrouter",
  MISTRAL_API_KEY: "mistral",
  XAI_API_KEY: "xai",
  ZAI_API_KEY: "zai",
  CEREBRAS_API_KEY: "cerebras",
  TOGETHER_API_KEY: "together",
  HF_TOKEN: "huggingface",
  GROQ_API_KEY: "groq",
  DEEPGRAM_API_KEY: "deepgram",
  MOONSHOT_API_KEY: "moonshot",
  KIMI_API_KEY: "kimi-coding",
  MINIMAX_API_KEY: "minimax",
  QIANFAN_API_KEY: "qianfan",
  MODELSTUDIO_API_KEY: "modelstudio",
  XIAOMI_API_KEY: "xiaomi",
  VOLCANO_ENGINE_API_KEY: "volcano-engine",
  BYTEPLUS_API_KEY: "byteplus",
  SYNTHETIC_API_KEY: "synthetic",
  VENICE_API_KEY: "venice",
  OPENCODE_ZEN_API_KEY: "opencode",
  KILOCODE_API_KEY: "kilocode",
  AI_GATEWAY_API_KEY: "ai-gateway",
  CLOUDFLARE_AI_GATEWAY_API_KEY: "cloudflare-ai-gateway",
  LITELLM_API_KEY: "litellm",
  SUNO_API_KEY: "suno",
  ELEVENLABS_API_KEY: "elevenlabs",
};

const authProfiles = {};
let keyCount = 0;
for (const [envVar, providerId] of Object.entries(providerMap)) {
  const key = getKey(envVar);
  if (key) {
    authProfiles[providerId] = [
      {
        id: "default",
        type: "api_key",
        key,
        lastUsed: null,
        disabled: false,
        cooldown: null,
      },
    ];
    keyCount++;
  }
}

const agentDir = "openclaw-data/agents/main/agent";
fs.mkdirSync(agentDir, { recursive: true });
fs.writeFileSync(
  path.join(agentDir, "auth-profiles.json"),
  JSON.stringify(authProfiles, null, 2) + "\n"
);
console.log(`  Wrote auth-profiles.json (${keyCount} provider(s))`);

// ---------------------------------------------------------------------------
// 4. Pre-generate device identity and pre-pair it
// ---------------------------------------------------------------------------
// OpenClaw requires device pairing for WebSocket connections. Even with
// dangerouslyDisableDeviceAuth:true, the gateway still requires pairing for
// the WS protocol (that flag only affects the control UI).
//
// Fix: generate an Ed25519 keypair at install time, register the public key
// in OpenClaw's devices/paired.json, and save the full identity so start.js
// can inject it into the OpenVoiceUI container via docker exec.

// Generate Ed25519 keypair
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");

// Raw 32-byte public key — extract via JWK.x (most reliable cross-platform)
const devJwk = publicKey.export({ format: "jwk" });
const rawPub = Buffer.from(devJwk.x, "base64url");

// deviceId = SHA256(raw public key) — matches Python's hashlib.sha256(raw_pub).hexdigest()
const deviceId = crypto.createHash("sha256").update(rawPub).digest("hex");

// PEM formats — OpenVoiceUI Python client uses PEM for signing
const pubPem = publicKey.export({ type: "spki", format: "pem" });
const privPem = privateKey.export({ type: "pkcs8", format: "pem" });

// base64url of raw Ed25519 bytes — this is what the gateway compares during WS handshake
const pubB64url = rawPub.toString("base64url");

// Save full identity for injection into OpenVoiceUI container at start time
// (Python client reads PEM format for Ed25519 signing)
const deviceIdentity = {
  deviceId: deviceId,
  publicKeyPem: pubPem,
  privateKeyPem: privPem,
};
fs.writeFileSync(
  "openclaw-data/pre-paired-device.json",
  JSON.stringify(deviceIdentity, null, 2) + "\n"
);
console.log(`  Generated device identity: ${deviceId.slice(0, 16)}...`);

// Pre-register in OpenClaw's devices/paired.json so it accepts this device.
// Format must match what approveDevicePairing() writes:
//   - publicKey: base64url of raw Ed25519 bytes (NOT PEM — gateway compares literally)
//   - role/roles/scopes/approvedScopes: authorization metadata
//   - tokens: per-role auth tokens for verifyDeviceToken() calls
fs.mkdirSync("openclaw-data/devices", { recursive: true });
const nowMs = Date.now();
const pairingToken = crypto.randomBytes(32).toString("hex");
const pairedDevices = {};
pairedDevices[deviceId] = {
  deviceId: deviceId,
  publicKey: pubB64url,
  displayName: "pinokio-openvoiceui",
  platform: "linux",
  clientId: "cli",
  clientMode: "cli",
  role: "operator",
  roles: ["operator"],
  scopes: ["operator.admin", "operator.read", "operator.write"],
  approvedScopes: ["operator.admin", "operator.read", "operator.write"],
  tokens: {
    operator: {
      token: pairingToken,
      role: "operator",
      scopes: ["operator.admin", "operator.read", "operator.write"],
      createdAtMs: nowMs,
    },
  },
  createdAtMs: nowMs,
  approvedAtMs: nowMs,
};
fs.writeFileSync(
  "openclaw-data/devices/paired.json",
  JSON.stringify(pairedDevices, null, 2) + "\n"
);
// Clear pending.json — stale entries with silent:false permanently block auto-approval
fs.writeFileSync("openclaw-data/devices/pending.json", "{}\n");
console.log("  Wrote devices/paired.json (pre-paired)");

// ---------------------------------------------------------------------------
// Done
// ---------------------------------------------------------------------------

console.log("\n  Configuration complete!\n");