chore: rename relay to gateway
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import chalk from "chalk";
|
||||
import { loadConfig, type WarelayConfig } from "../config/config.js";
|
||||
import { loadConfig, type ClawdisConfig } from "../config/config.js";
|
||||
import { normalizeE164 } from "../utils.js";
|
||||
import {
|
||||
getWebAuthAgeMs,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
const DEFAULT_WEBCHAT_PORT = 18788;
|
||||
|
||||
export async function buildProviderSummary(
|
||||
cfg?: WarelayConfig,
|
||||
cfg?: ClawdisConfig,
|
||||
): Promise<string[]> {
|
||||
const effective = cfg ?? loadConfig();
|
||||
const lines: string[] = [];
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { acquireRelayLock, RelayLockError } from "./relay-lock.js";
|
||||
|
||||
const newLockPath = () =>
|
||||
path.join(
|
||||
os.tmpdir(),
|
||||
`clawdis-relay-lock-test-${process.pid}-${Math.random().toString(16).slice(2)}.sock`,
|
||||
);
|
||||
|
||||
describe("relay-lock", () => {
|
||||
it("prevents concurrent relay instances and releases cleanly", async () => {
|
||||
const lockPath = newLockPath();
|
||||
|
||||
const release1 = await acquireRelayLock(lockPath);
|
||||
expect(fs.existsSync(lockPath)).toBe(true);
|
||||
|
||||
await expect(acquireRelayLock(lockPath)).rejects.toBeInstanceOf(
|
||||
RelayLockError,
|
||||
);
|
||||
|
||||
await release1();
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
|
||||
// After release, lock can be reacquired.
|
||||
const release2 = await acquireRelayLock(lockPath);
|
||||
await release2();
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const DEFAULT_LOCK_PATH = path.join(os.tmpdir(), "clawdis-relay.lock");
|
||||
|
||||
export class RelayLockError extends Error {}
|
||||
|
||||
type ReleaseFn = () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Acquire an exclusive single-instance lock for the relay using a Unix domain socket.
|
||||
*
|
||||
* Why a socket? If the process crashes or is SIGKILLed, the socket file remains but
|
||||
* the next start will detect ECONNREFUSED when connecting and clean the stale path
|
||||
* before retrying. This keeps the lock self-healing without manual pidfile cleanup.
|
||||
*/
|
||||
export async function acquireRelayLock(
|
||||
lockPath = DEFAULT_LOCK_PATH,
|
||||
): Promise<ReleaseFn> {
|
||||
// Fast path: try to listen on the lock path.
|
||||
const attemptListen = (): Promise<net.Server> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
|
||||
server.once("error", async (err: NodeJS.ErrnoException) => {
|
||||
if (err.code !== "EADDRINUSE") {
|
||||
reject(new RelayLockError(`lock listen failed: ${err.message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Something is already bound. Try to connect to see if it is alive.
|
||||
const client = net.connect({ path: lockPath });
|
||||
|
||||
client.once("connect", () => {
|
||||
client.destroy();
|
||||
reject(
|
||||
new RelayLockError("another relay instance is already running"),
|
||||
);
|
||||
});
|
||||
|
||||
client.once("error", (connErr: NodeJS.ErrnoException) => {
|
||||
// Nothing is listening -> stale socket file. Remove and retry once.
|
||||
if (connErr.code === "ECONNREFUSED" || connErr.code === "ENOENT") {
|
||||
try {
|
||||
fs.rmSync(lockPath, { force: true });
|
||||
} catch (rmErr) {
|
||||
reject(
|
||||
new RelayLockError(
|
||||
`failed to clean stale lock at ${lockPath}: ${String(rmErr)}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
attemptListen().then(resolve, reject);
|
||||
return;
|
||||
}
|
||||
|
||||
reject(
|
||||
new RelayLockError(
|
||||
`failed to connect to existing lock (${lockPath}): ${connErr.message}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(lockPath, () => resolve(server));
|
||||
});
|
||||
|
||||
const server = await attemptListen();
|
||||
|
||||
let released = false;
|
||||
const release = async (): Promise<void> => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
try {
|
||||
fs.rmSync(lockPath, { force: true });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupSignals: NodeJS.Signals[] = ["SIGINT", "SIGTERM", "SIGHUP"];
|
||||
const handleSignal = async () => {
|
||||
await release();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
for (const sig of cleanupSignals) {
|
||||
process.once(sig, () => {
|
||||
void handleSignal();
|
||||
});
|
||||
}
|
||||
process.once("exit", () => {
|
||||
// Exit handler must be sync-safe; release is async but close+rm are fast.
|
||||
void release();
|
||||
});
|
||||
|
||||
return release;
|
||||
}
|
||||
@@ -2,9 +2,8 @@ import { spawn } from "node:child_process";
|
||||
|
||||
const DEFAULT_LAUNCHD_LABEL = "com.steipete.clawdis";
|
||||
|
||||
export function triggerWarelayRestart(): void {
|
||||
export function triggerClawdisRestart(): void {
|
||||
const label =
|
||||
process.env.WARELAY_LAUNCHD_LABEL ||
|
||||
process.env.CLAWDIS_LAUNCHD_LABEL ||
|
||||
DEFAULT_LAUNCHD_LABEL;
|
||||
const uid =
|
||||
|
||||
@@ -56,7 +56,7 @@ function initSelfPresence() {
|
||||
function ensureSelfPresence() {
|
||||
// If the map was somehow cleared (e.g., hot reload or a new worker spawn that
|
||||
// skipped module evaluation), re-seed with a local entry so UIs always show
|
||||
// at least the current relay.
|
||||
// at least the current gateway.
|
||||
if (entries.size === 0) {
|
||||
initSelfPresence();
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ export async function ensureFunnel(
|
||||
);
|
||||
runtime.error(
|
||||
info(
|
||||
"Tip: Funnel is optional for CLAWDIS. You can keep running the web relay without it: `pnpm clawdis gateway`",
|
||||
"Tip: Funnel is optional for CLAWDIS. You can keep running the web gateway without it: `pnpm clawdis gateway`",
|
||||
),
|
||||
);
|
||||
if (isVerbose()) {
|
||||
|
||||
Reference in New Issue
Block a user