-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathsetup-config.js
More file actions
273 lines (247 loc) · 9.24 KB
/
Copy pathsetup-config.js
File metadata and controls
273 lines (247 loc) · 9.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/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 });
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");