@buda-ai/connector 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/cli.js +138 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ printf '{"serverUrl":"https://buda.im","oauthToken":"..."}\n' \
|
|
|
57
57
|
Supported launch config fields:
|
|
58
58
|
|
|
59
59
|
- `serverUrl`
|
|
60
|
-
- `
|
|
60
|
+
- `token`
|
|
61
61
|
- `workdirRoot`
|
|
62
62
|
- `oauthToken`
|
|
63
63
|
- `bunnyDaemonHost`
|
|
@@ -65,11 +65,11 @@ Supported launch config fields:
|
|
|
65
65
|
|
|
66
66
|
## One-line install
|
|
67
67
|
|
|
68
|
-
Buda Space Settings can show a
|
|
69
|
-
|
|
68
|
+
Buda Space Settings can show a connector token command for the current space.
|
|
69
|
+
The token selects that Space's independent local connector profile:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
npx -y @buda-ai/connector@latest daemon --
|
|
72
|
+
npx -y @buda-ai/connector@latest daemon --token "cet_..."
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Desktop and other authenticated clients should use OAuth token launch config
|
|
@@ -83,17 +83,19 @@ printf '%s\n' '{"serverUrl":"https://buda.im","oauthToken":"..."}' \
|
|
|
83
83
|
You can also pass launch options directly:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
buda-connector daemon --
|
|
86
|
+
buda-connector daemon --token "cet_..." --workdir "$HOME/.buda/agents"
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
## Local Files
|
|
90
90
|
|
|
91
|
-
- `~/.buda/device.json` (versioned device
|
|
91
|
+
- `~/.buda/device.json` (versioned device profiles keyed by server origin and token fingerprint)
|
|
92
92
|
- `~/.buda/connector.log`
|
|
93
93
|
- `~/.buda/agents/`
|
|
94
94
|
|
|
95
95
|
Server URL paths and query strings do not create a new identity. Scheme and non-default port
|
|
96
|
-
changes do.
|
|
96
|
+
changes do. Connector tokens for different Spaces on the same server select independent profiles,
|
|
97
|
+
so their daemons can run concurrently. Raw tokens are never used in profile keys or lock paths.
|
|
98
|
+
The installation-level physical device ID remains shared across profiles.
|
|
97
99
|
|
|
98
100
|
New server profiles share a privacy-preserving device ID derived by hashing the operating
|
|
99
101
|
system's stable machine identifier. The raw machine identifier is never stored or sent. If the
|
package/dist/cli.js
CHANGED
|
@@ -147,6 +147,13 @@ function getServerOriginKey(serverUrl) {
|
|
|
147
147
|
const digest = createHash2("sha256").update(origin).digest("hex").slice(0, 12);
|
|
148
148
|
return `${hostnameKey}-${digest}`;
|
|
149
149
|
}
|
|
150
|
+
function getConnectorTokenFingerprint(token) {
|
|
151
|
+
return createHash2("sha256").update(`buda:connector-profile:v1:${token.trim()}`, "utf8").digest("hex");
|
|
152
|
+
}
|
|
153
|
+
function getConnectorProfileKey(serverUrl, token) {
|
|
154
|
+
const discriminator = token?.trim() ? getConnectorTokenFingerprint(token) : "legacy-no-token";
|
|
155
|
+
return `${getServerOriginKey(serverUrl)}--${discriminator}`;
|
|
156
|
+
}
|
|
150
157
|
function getConnectorPaths(serverUrl, configRoot = path.join(homedir(), ".buda")) {
|
|
151
158
|
const scopedLegacyDir = path.join(configRoot, "servers", getServerOriginKey(serverUrl));
|
|
152
159
|
return {
|
|
@@ -158,6 +165,16 @@ function getConnectorPaths(serverUrl, configRoot = path.join(homedir(), ".buda")
|
|
|
158
165
|
scopedLegacyWorkdirRoot: path.join(scopedLegacyDir, "agents")
|
|
159
166
|
};
|
|
160
167
|
}
|
|
168
|
+
function getConnectorDaemonLockTarget(serverUrl, connectorId, configRoot = path.join(homedir(), ".buda")) {
|
|
169
|
+
const connectorKey = connectorId.replace(/[^a-z0-9_-]+/gi, "-").toLowerCase();
|
|
170
|
+
return path.join(
|
|
171
|
+
configRoot,
|
|
172
|
+
"daemons",
|
|
173
|
+
getServerOriginKey(serverUrl),
|
|
174
|
+
connectorKey,
|
|
175
|
+
"connector-daemon"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
161
178
|
function getServerUrl(launchConfig) {
|
|
162
179
|
return (launchConfig?.serverUrl || process.env.BUDA_CONNECTOR_SERVER_URL || process.env.BUDA_SERVER_URL || process.env.NEXT_PUBLIC_APP_URL || DEFAULT_SERVER_URL).replace(/\/$/, "");
|
|
163
180
|
}
|
|
@@ -211,29 +228,50 @@ async function readJsonFile(configPath) {
|
|
|
211
228
|
return null;
|
|
212
229
|
}
|
|
213
230
|
}
|
|
231
|
+
function migrateLegacyProfiles(values) {
|
|
232
|
+
const profiles = {};
|
|
233
|
+
for (const value of values) {
|
|
234
|
+
if (!isDeviceConfig(value)) continue;
|
|
235
|
+
try {
|
|
236
|
+
profiles[getConnectorProfileKey(value.serverUrl, value.token)] = value;
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return profiles;
|
|
241
|
+
}
|
|
214
242
|
async function loadDeviceConfigStore(configPath) {
|
|
215
243
|
const parsed = await readJsonFile(configPath);
|
|
216
244
|
const candidate = parsed;
|
|
245
|
+
if (candidate?.version === 3 && candidate.profiles && typeof candidate.profiles === "object") {
|
|
246
|
+
return {
|
|
247
|
+
store: {
|
|
248
|
+
version: 3,
|
|
249
|
+
...typeof candidate.systemDeviceId === "string" ? { systemDeviceId: candidate.systemDeviceId } : {},
|
|
250
|
+
profiles: candidate.profiles
|
|
251
|
+
},
|
|
252
|
+
needsWrite: false
|
|
253
|
+
};
|
|
254
|
+
}
|
|
217
255
|
if (candidate && (candidate.version === 1 || candidate.version === 2) && candidate.servers && typeof candidate.servers === "object") {
|
|
218
256
|
return {
|
|
219
257
|
store: {
|
|
220
|
-
version:
|
|
258
|
+
version: 3,
|
|
221
259
|
...typeof candidate.systemDeviceId === "string" ? { systemDeviceId: candidate.systemDeviceId } : {},
|
|
222
|
-
|
|
260
|
+
profiles: migrateLegacyProfiles(Object.values(candidate.servers))
|
|
223
261
|
},
|
|
224
|
-
needsWrite:
|
|
262
|
+
needsWrite: true
|
|
225
263
|
};
|
|
226
264
|
}
|
|
227
265
|
if (isDeviceConfig(parsed)) {
|
|
228
266
|
return {
|
|
229
267
|
store: {
|
|
230
|
-
version:
|
|
231
|
-
|
|
268
|
+
version: 3,
|
|
269
|
+
profiles: migrateLegacyProfiles([parsed])
|
|
232
270
|
},
|
|
233
271
|
needsWrite: true
|
|
234
272
|
};
|
|
235
273
|
}
|
|
236
|
-
return { store: { version:
|
|
274
|
+
return { store: { version: 3, profiles: {} }, needsWrite: false };
|
|
237
275
|
}
|
|
238
276
|
function normalizeStoredWorkdirRoot(config, paths) {
|
|
239
277
|
const configuredRoot = config.workdirRoot?.trim();
|
|
@@ -264,33 +302,21 @@ async function writeDeviceConfigStore(configPath, store) {
|
|
|
264
302
|
async function readDeviceConfig(launchConfig, configRoot) {
|
|
265
303
|
const serverUrl = getServerUrl(launchConfig);
|
|
266
304
|
const paths = getConnectorPaths(serverUrl, configRoot);
|
|
267
|
-
const
|
|
305
|
+
const launchToken = getConnectorToken(launchConfig);
|
|
268
306
|
await mkdir(paths.configDir, { recursive: true });
|
|
269
307
|
return withExclusiveFileLock(paths.configPath, async () => {
|
|
270
308
|
const { store, needsWrite } = await loadDeviceConfigStore(paths.configPath);
|
|
271
|
-
const
|
|
309
|
+
const profileKey = getConnectorProfileKey(serverUrl, launchToken);
|
|
310
|
+
const storedConfig = store.profiles[profileKey];
|
|
272
311
|
let config = isDeviceConfig(storedConfig) ? storedConfig : null;
|
|
273
|
-
let migratedScopedConfig = false;
|
|
274
312
|
let normalizedWorkdirRoot = false;
|
|
275
|
-
if (!config) {
|
|
276
|
-
const scopedLegacy = await readJsonFile(paths.scopedLegacyConfigPath);
|
|
277
|
-
if (isDeviceConfig(scopedLegacy)) {
|
|
278
|
-
try {
|
|
279
|
-
if (normalizeServerOrigin(scopedLegacy.serverUrl) === origin) {
|
|
280
|
-
config = scopedLegacy;
|
|
281
|
-
migratedScopedConfig = true;
|
|
282
|
-
}
|
|
283
|
-
} catch {
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
313
|
if (config) {
|
|
288
314
|
const normalized = normalizeStoredWorkdirRoot(config, paths);
|
|
289
315
|
config = normalized.config;
|
|
290
316
|
normalizedWorkdirRoot = normalized.changed;
|
|
291
|
-
store.
|
|
317
|
+
store.profiles[getConnectorProfileKey(config.serverUrl, config.token)] = config;
|
|
292
318
|
}
|
|
293
|
-
if (needsWrite ||
|
|
319
|
+
if (needsWrite || normalizedWorkdirRoot) {
|
|
294
320
|
await writeDeviceConfigStore(paths.configPath, store);
|
|
295
321
|
}
|
|
296
322
|
return config;
|
|
@@ -302,7 +328,7 @@ async function writeDeviceConfig(config, configRoot) {
|
|
|
302
328
|
await mkdir(paths.configDir, { recursive: true });
|
|
303
329
|
await withExclusiveFileLock(paths.configPath, async () => {
|
|
304
330
|
const { store } = await loadDeviceConfigStore(paths.configPath);
|
|
305
|
-
store.
|
|
331
|
+
store.profiles[getConnectorProfileKey(config.serverUrl, config.token)] = config;
|
|
306
332
|
await writeDeviceConfigStore(paths.configPath, store);
|
|
307
333
|
});
|
|
308
334
|
}
|
|
@@ -315,7 +341,7 @@ async function getOrCreateSystemDeviceId(configRoot, resolver = resolveSystemDev
|
|
|
315
341
|
if (needsWrite) await writeDeviceConfigStore(paths.configPath, store);
|
|
316
342
|
return store.systemDeviceId;
|
|
317
343
|
}
|
|
318
|
-
const existingDeviceId = Object.values(store.
|
|
344
|
+
const existingDeviceId = Object.values(store.profiles).find(isDeviceConfig)?.deviceId;
|
|
319
345
|
store.systemDeviceId = resolver() || existingDeviceId || `dvc_${randomUUID()}`;
|
|
320
346
|
await writeDeviceConfigStore(paths.configPath, store);
|
|
321
347
|
return store.systemDeviceId;
|
|
@@ -325,19 +351,40 @@ async function createDeviceConfig(launchConfig, configRoot, resolver) {
|
|
|
325
351
|
const deviceId = await getOrCreateSystemDeviceId(configRoot, resolver);
|
|
326
352
|
return buildDeviceConfig(launchConfig, deviceId);
|
|
327
353
|
}
|
|
328
|
-
async function ensureDeviceConfig(launchConfig) {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
354
|
+
async function ensureDeviceConfig(launchConfig, configRoot, resolver = resolveSystemDeviceId) {
|
|
355
|
+
const serverUrl = getServerUrl(launchConfig);
|
|
356
|
+
const paths = getConnectorPaths(serverUrl, configRoot);
|
|
357
|
+
const token = getConnectorToken(launchConfig);
|
|
358
|
+
const profileKey = getConnectorProfileKey(serverUrl, token);
|
|
359
|
+
await mkdir(paths.configDir, { recursive: true });
|
|
360
|
+
const config = await withExclusiveFileLock(paths.configPath, async () => {
|
|
361
|
+
const { store, needsWrite } = await loadDeviceConfigStore(paths.configPath);
|
|
362
|
+
const storedConfig = store.profiles[profileKey];
|
|
363
|
+
if (isDeviceConfig(storedConfig)) {
|
|
364
|
+
const normalized = normalizeStoredWorkdirRoot(storedConfig, paths);
|
|
365
|
+
if (needsWrite || normalized.changed) {
|
|
366
|
+
store.profiles[profileKey] = normalized.config;
|
|
367
|
+
await writeDeviceConfigStore(paths.configPath, store);
|
|
368
|
+
}
|
|
369
|
+
const existing = normalized.config;
|
|
370
|
+
const overrideToken = getConnectorToken(launchConfig);
|
|
371
|
+
return {
|
|
372
|
+
...existing,
|
|
373
|
+
serverUrl,
|
|
374
|
+
workdirRoot: launchConfig?.workdirRoot || process.env.BUDA_CONNECTOR_WORKDIR || existing.workdirRoot || getWorkdirRoot(launchConfig),
|
|
375
|
+
...overrideToken ? { token: overrideToken } : {}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (!store.systemDeviceId) {
|
|
379
|
+
const existingDeviceId = Object.values(store.profiles).find(isDeviceConfig)?.deviceId;
|
|
380
|
+
store.systemDeviceId = resolver() || existingDeviceId || `dvc_${randomUUID()}`;
|
|
381
|
+
}
|
|
382
|
+
const created = buildDeviceConfig(launchConfig, store.systemDeviceId);
|
|
383
|
+
store.profiles[profileKey] = created;
|
|
384
|
+
await writeDeviceConfigStore(paths.configPath, store);
|
|
385
|
+
return created;
|
|
386
|
+
});
|
|
387
|
+
await mkdir(config.workdirRoot, { recursive: true });
|
|
341
388
|
return config;
|
|
342
389
|
}
|
|
343
390
|
function parseLaunchConfig(raw) {
|
|
@@ -1034,6 +1081,7 @@ var BASE_CONNECTOR_CAPABILITIES = [
|
|
|
1034
1081
|
"git"
|
|
1035
1082
|
];
|
|
1036
1083
|
var browserAvailabilityWarningLogged = false;
|
|
1084
|
+
var getBrowserAvailabilityWarning = (browserAvailability) => browserAvailability.available ? null : `${browserAvailability.error} Browser automation is disabled, but shell, filesystem, Git, and coding capabilities remain available.`;
|
|
1037
1085
|
var getAdvertisedCapabilities = (browserAvailability = resolveBrowserAvailability(), deviceToken) => [
|
|
1038
1086
|
...BASE_CONNECTOR_CAPABILITIES,
|
|
1039
1087
|
...browserAvailability.available ? ["browser"] : [],
|
|
@@ -1042,9 +1090,10 @@ var getAdvertisedCapabilities = (browserAvailability = resolveBrowserAvailabilit
|
|
|
1042
1090
|
async function buildPayload(config, extra) {
|
|
1043
1091
|
const bundle = await readBundledManifest();
|
|
1044
1092
|
const browserAvailability = resolveBrowserAvailability();
|
|
1045
|
-
|
|
1093
|
+
const browserWarning = getBrowserAvailabilityWarning(browserAvailability);
|
|
1094
|
+
if (browserWarning && !browserAvailabilityWarningLogged) {
|
|
1046
1095
|
browserAvailabilityWarningLogged = true;
|
|
1047
|
-
await writeLog("warn",
|
|
1096
|
+
await writeLog("warn", browserWarning);
|
|
1048
1097
|
}
|
|
1049
1098
|
return {
|
|
1050
1099
|
connectorId: config.connectorId,
|
|
@@ -2066,6 +2115,7 @@ var isAlreadyExists3 = (error) => error instanceof Error && "code" in error && e
|
|
|
2066
2115
|
// src/relay.ts
|
|
2067
2116
|
var RELAY_PING_INTERVAL_MS = 2e4;
|
|
2068
2117
|
var RELAY_PONG_TIMEOUT_MS = 1e4;
|
|
2118
|
+
var RELAY_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
2069
2119
|
var RELAY_RECONNECT_MAX_MS = 3e4;
|
|
2070
2120
|
var RELAY_RECONNECT_JITTER = 0.2;
|
|
2071
2121
|
var RELAY_FAST_RECONNECT_MS = 500;
|
|
@@ -2073,6 +2123,22 @@ var CONNECTOR_SOCKET_REPLACED_CLOSE_CODE = 4001;
|
|
|
2073
2123
|
var CONNECTOR_SOCKET_REVOKED_CLOSE_CODE = 4003;
|
|
2074
2124
|
var shouldReconnectConnectorRelay = (closeCode) => closeCode !== CONNECTOR_SOCKET_REPLACED_CLOSE_CODE && closeCode !== CONNECTOR_SOCKET_REVOKED_CLOSE_CODE;
|
|
2075
2125
|
var getConnectorRelayReconnectBaseDelay = (hadOpenConnection, backoffMs) => hadOpenConnection ? RELAY_FAST_RECONNECT_MS : backoffMs;
|
|
2126
|
+
var formatConnectorRelayError = (error, depth = 0) => {
|
|
2127
|
+
if (depth >= 3) return String(error);
|
|
2128
|
+
const details = [String(error)];
|
|
2129
|
+
if (error && typeof error === "object") {
|
|
2130
|
+
const candidate = error;
|
|
2131
|
+
if (candidate.cause !== void 0) {
|
|
2132
|
+
details.push(`cause=${formatConnectorRelayError(candidate.cause, depth + 1)}`);
|
|
2133
|
+
}
|
|
2134
|
+
if (Array.isArray(candidate.errors) && candidate.errors.length > 0) {
|
|
2135
|
+
details.push(
|
|
2136
|
+
`errors=[${candidate.errors.map((nestedError) => formatConnectorRelayError(nestedError, depth + 1)).join(", ")}]`
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
return [...new Set(details.filter(Boolean))].join(" ") || "Unknown relay error";
|
|
2141
|
+
};
|
|
2076
2142
|
function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegistry, launchConfig) {
|
|
2077
2143
|
let socket = null;
|
|
2078
2144
|
let reconnectTimer = null;
|
|
@@ -2114,10 +2180,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
2114
2180
|
try {
|
|
2115
2181
|
connect();
|
|
2116
2182
|
} catch (error) {
|
|
2117
|
-
void writeLog(
|
|
2118
|
-
"warn",
|
|
2119
|
-
`relay reconnect threw: ${error instanceof Error ? error.message : error}`
|
|
2120
|
-
);
|
|
2183
|
+
void writeLog("warn", `relay reconnect threw: ${formatConnectorRelayError(error)}`);
|
|
2121
2184
|
scheduleReconnect("connect-throw");
|
|
2122
2185
|
}
|
|
2123
2186
|
}, delay);
|
|
@@ -2133,7 +2196,8 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
2133
2196
|
headers["X-Buda-Connector-Id"] = config.connectorId;
|
|
2134
2197
|
if (config.deviceToken) headers["X-Buda-Connector-Device-Token"] = config.deviceToken;
|
|
2135
2198
|
const ws = new WebSocket(relayUrl, {
|
|
2136
|
-
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
2199
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
2200
|
+
handshakeTimeout: RELAY_HANDSHAKE_TIMEOUT_MS
|
|
2137
2201
|
});
|
|
2138
2202
|
socket = ws;
|
|
2139
2203
|
const inflight = /* @__PURE__ */ new Map();
|
|
@@ -2154,10 +2218,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
2154
2218
|
try {
|
|
2155
2219
|
ws.ping();
|
|
2156
2220
|
} catch (error) {
|
|
2157
|
-
void writeLog(
|
|
2158
|
-
"warn",
|
|
2159
|
-
`relay ping failed: ${error instanceof Error ? error.message : error}`
|
|
2160
|
-
);
|
|
2221
|
+
void writeLog("warn", `relay ping failed: ${formatConnectorRelayError(error)}`);
|
|
2161
2222
|
ws.terminate();
|
|
2162
2223
|
return;
|
|
2163
2224
|
}
|
|
@@ -2200,7 +2261,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
2200
2261
|
scheduleReconnect("close");
|
|
2201
2262
|
});
|
|
2202
2263
|
ws.on("error", (error) => {
|
|
2203
|
-
void writeLog("warn", `relay error: ${error
|
|
2264
|
+
void writeLog("warn", `relay error: ${formatConnectorRelayError(error)}`);
|
|
2204
2265
|
detach();
|
|
2205
2266
|
scheduleReconnect("error");
|
|
2206
2267
|
});
|
|
@@ -2208,10 +2269,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
|
|
|
2208
2269
|
try {
|
|
2209
2270
|
connect();
|
|
2210
2271
|
} catch (error) {
|
|
2211
|
-
void writeLog(
|
|
2212
|
-
"warn",
|
|
2213
|
-
`relay initial connect threw: ${error instanceof Error ? error.message : error}`
|
|
2214
|
-
);
|
|
2272
|
+
void writeLog("warn", `relay initial connect threw: ${formatConnectorRelayError(error)}`);
|
|
2215
2273
|
scheduleReconnect("connect-throw");
|
|
2216
2274
|
}
|
|
2217
2275
|
return {
|
|
@@ -2916,7 +2974,7 @@ import { mkdir as mkdir9 } from "fs/promises";
|
|
|
2916
2974
|
import path9 from "path";
|
|
2917
2975
|
async function runNewConnector(launchConfig) {
|
|
2918
2976
|
const existing = await readDeviceConfig(launchConfig);
|
|
2919
|
-
const config =
|
|
2977
|
+
const config = await ensureDeviceConfig(launchConfig);
|
|
2920
2978
|
const paths = getConnectorPaths(config.serverUrl);
|
|
2921
2979
|
await migrateLegacyWorkspaceRoot(config.workdirRoot);
|
|
2922
2980
|
await provisionWorkdirLayout(config.workdirRoot);
|
|
@@ -2932,22 +2990,15 @@ async function runNewConnector(launchConfig) {
|
|
|
2932
2990
|
}
|
|
2933
2991
|
async function runConnectorDaemon(launchConfig) {
|
|
2934
2992
|
const existing = await readDeviceConfig(launchConfig);
|
|
2935
|
-
const config =
|
|
2993
|
+
const config = await ensureDeviceConfig(launchConfig);
|
|
2936
2994
|
const paths = getConnectorPaths(config.serverUrl);
|
|
2937
|
-
const daemonLockTarget =
|
|
2938
|
-
paths.configDir,
|
|
2939
|
-
"servers",
|
|
2940
|
-
getServerOriginKey(config.serverUrl),
|
|
2941
|
-
"connector-daemon"
|
|
2942
|
-
);
|
|
2995
|
+
const daemonLockTarget = getConnectorDaemonLockTarget(config.serverUrl, config.connectorId);
|
|
2943
2996
|
await mkdir9(path9.dirname(daemonLockTarget), { recursive: true, mode: 448 });
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
}
|
|
2950
|
-
);
|
|
2997
|
+
let daemonLock = await acquireExclusiveFileLock(daemonLockTarget, { timeoutMs: 0 }).catch(() => {
|
|
2998
|
+
throw new Error(
|
|
2999
|
+
`Another Buda Connector daemon is already running for this profile on ${config.serverUrl}. Stop it before starting another instance.`
|
|
3000
|
+
);
|
|
3001
|
+
});
|
|
2951
3002
|
let bunnyDaemon;
|
|
2952
3003
|
let relayProtocol;
|
|
2953
3004
|
try {
|
|
@@ -2959,9 +3010,26 @@ async function runConnectorDaemon(launchConfig) {
|
|
|
2959
3010
|
const registration = await registerConnector(config, launchConfig, activeBunnyDaemon, {
|
|
2960
3011
|
recoverMissingProfile: !existing
|
|
2961
3012
|
});
|
|
3013
|
+
const connectorIdChanged = config.connectorId !== registration.connectorId;
|
|
3014
|
+
if (connectorIdChanged) {
|
|
3015
|
+
const registeredLockTarget = getConnectorDaemonLockTarget(
|
|
3016
|
+
config.serverUrl,
|
|
3017
|
+
registration.connectorId
|
|
3018
|
+
);
|
|
3019
|
+
await mkdir9(path9.dirname(registeredLockTarget), { recursive: true, mode: 448 });
|
|
3020
|
+
const registeredLock = await acquireExclusiveFileLock(registeredLockTarget, {
|
|
3021
|
+
timeoutMs: 0
|
|
3022
|
+
}).catch(() => {
|
|
3023
|
+
throw new Error(
|
|
3024
|
+
`Another Buda Connector daemon is already running for this profile on ${config.serverUrl}. Stop it before starting another instance.`
|
|
3025
|
+
);
|
|
3026
|
+
});
|
|
3027
|
+
await daemonLock.release();
|
|
3028
|
+
daemonLock = registeredLock;
|
|
3029
|
+
}
|
|
2962
3030
|
config.connectorId = registration.connectorId;
|
|
2963
3031
|
relayProtocol = registration.relayProtocol;
|
|
2964
|
-
if (!existing) await writeDeviceConfig(config);
|
|
3032
|
+
if (!existing || connectorIdChanged) await writeDeviceConfig(config);
|
|
2965
3033
|
const relay = startConnectorRelay(
|
|
2966
3034
|
config,
|
|
2967
3035
|
activeBunnyDaemon,
|