import { homedir } from "node:os";import { FileSystem, Path } from "@effect/platform";import type { PlatformError } from "@effect/platform/Error";import * as Effect from "effect/Effect"; // ---------------------------------------------------------------------------// Types// --------------------------------------------------------------------------- export interface DaemonRecord { readonly version: 1; readonly hostname: string; readonly port: number; readonly pid: number; readonly startedAt: string; readonly scopeDir: string | null;} export interface DaemonPointer { readonly version: 1; readonly hostname: string; readonly port: number; readonly pid: number; readonly startedAt: string; readonly scopeId: string; readonly scopeDir: string | null; readonly token: string;} export interface DaemonStartLock { readonly path: string; readonly hostname: string; readonly scopeId: string;} // ---------------------------------------------------------------------------// Host normalization// --------------------------------------------------------------------------- const LOCAL_HOST_ALIASES = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]); export const canonicalDaemonHost = (hostname: string): string => { const normalized = hostname.trim().toLowerCase(); return LOCAL_HOST_ALIASES.has(normalized) ? "localhost" : normalized;}; export const currentDaemonScopeId = (): string => { const explicitScope = process.env.EXECUTOR_SCOPE_DIR?.trim(); if (explicitScope && explicitScope.length > 0) { return `scope:${explicitScope}`; } return `cwd:${process.cwd()}`;}; // ---------------------------------------------------------------------------// Paths// --------------------------------------------------------------------------- const resolveDaemonDataDir = (path: Path.Path): string => process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor"); const sanitizeHostForPath = (hostname: string): string => hostname.replaceAll(/[^a-z0-9.-]+/gi, "_");const sanitizeScopeForPath = (scopeId: string): string => scopeId.replaceAll(/[^a-z0-9.-]+/gi, "_"); const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: number }): string => { const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname)); return path.join(resolveDaemonDataDir(path), `daemon-${host}-${input.port}.json`);}; const daemonPointerPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string => { const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname)); const scope = sanitizeScopeForPath(input.scopeId); return path.join(resolveDaemonDataDir(path), `daemon-active-${host}-${scope}.json`);}; const daemonStartLockPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string => `${daemonPointerPath(path, input)}.lock`; // ---------------------------------------------------------------------------// Persistence// --------------------------------------------------------------------------- export const writeDaemonRecord = (input: { hostname: string; port: number; pid: number; scopeDir: string | null;}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dataDir = resolveDaemonDataDir(path); yield* fs.makeDirectory(dataDir, { recursive: true }); const payload: DaemonRecord = { version: 1, hostname: canonicalDaemonHost(input.hostname), port: input.port, pid: input.pid, startedAt: new Date().toISOString(), scopeDir: input.scopeDir, }; yield* fs.writeFileString( daemonRecordPath(path, { hostname: input.hostname, port: input.port }), `${JSON.stringify(payload, null, 2)}\n`, ); }); const parseRecord = (raw: string): DaemonRecord | null => { let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return null; } if ( typeof parsed !== "object" || parsed === null || !("version" in parsed) || (parsed as { version?: unknown }).version !== 1 ) { return null; } const r = parsed as Record<string, unknown>; if ( typeof r.hostname !== "string" || typeof r.port !== "number" || typeof r.pid !== "number" || typeof r.startedAt !== "string" || !(typeof r.scopeDir === "string" || r.scopeDir === null) ) { return null; } return { version: 1, hostname: canonicalDaemonHost(r.hostname), port: r.port, pid: r.pid, startedAt: r.startedAt, scopeDir: r.scopeDir, };}; const parsePointer = (raw: string): DaemonPointer | null => { let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return null; } if ( typeof parsed !== "object" || parsed === null || !("version" in parsed) || (parsed as { version?: unknown }).version !== 1 ) { return null; } const r = parsed as Record<string, unknown>; if ( typeof r.hostname !== "string" || typeof r.port !== "number" || typeof r.pid !== "number" || typeof r.startedAt !== "string" || typeof r.scopeId !== "string" || !(typeof r.scopeDir === "string" || r.scopeDir === null) || typeof r.token !== "string" ) { return null; } return { version: 1, hostname: canonicalDaemonHost(r.hostname), port: r.port, pid: r.pid, startedAt: r.startedAt, scopeId: r.scopeId, scopeDir: r.scopeDir, token: r.token, };}; export const readDaemonRecord = (input: { hostname: string; port: number;}): Effect.Effect<DaemonRecord | null, never, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const raw = yield* fs.readFileString(daemonRecordPath(path, input)).pipe( Effect.catchAll(() => Effect.succeed(null)), ); if (raw === null) return null; return parseRecord(raw); }); export const removeDaemonRecord = (input: { hostname: string; port: number;}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.remove(daemonRecordPath(path, input), { force: true }); }); export const writeDaemonPointer = (input: { hostname: string; port: number; pid: number; scopeId: string; scopeDir: string | null; token: string;}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dataDir = resolveDaemonDataDir(path); yield* fs.makeDirectory(dataDir, { recursive: true }); const payload: DaemonPointer = { version: 1, hostname: canonicalDaemonHost(input.hostname), port: input.port, pid: input.pid, startedAt: new Date().toISOString(), scopeId: input.scopeId, scopeDir: input.scopeDir, token: input.token, }; yield* fs.writeFileString( daemonPointerPath(path, { hostname: input.hostname, scopeId: input.scopeId }), `${JSON.stringify(payload, null, 2)}\n`, ); }); export const readDaemonPointer = (input: { hostname: string; scopeId: string;}): Effect.Effect<DaemonPointer | null, never, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const raw = yield* fs .readFileString(daemonPointerPath(path, input)) .pipe(Effect.catchAll(() => Effect.succeed(null))); if (raw === null) return null; return parsePointer(raw); }); export const removeDaemonPointer = (input: { hostname: string; scopeId: string;}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.remove(daemonPointerPath(path, input), { force: true }); }); const parseLockPid = (raw: string): number | null => { let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return null; } if ( typeof parsed !== "object" || parsed === null || typeof (parsed as Record<string, unknown>).pid !== "number" ) { return null; } return (parsed as Record<string, number>).pid;}; export const acquireDaemonStartLock = (input: { hostname: string; scopeId: string;}): Effect.Effect<DaemonStartLock, Error, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dataDir = resolveDaemonDataDir(path); yield* fs.makeDirectory(dataDir, { recursive: true }); const lockPath = daemonStartLockPath(path, input); const lockPayload = JSON.stringify( { pid: process.pid, hostname: canonicalDaemonHost(input.hostname), scopeId: input.scopeId, startedAt: new Date().toISOString(), }, null, 2, ); const tryAcquire = () => fs.writeFileString(lockPath, `${lockPayload}\n`, { flag: "wx" }).pipe( Effect.as(true), Effect.catchAll(() => Effect.succeed(false)), ); if (yield* tryAcquire()) { return { path: lockPath, hostname: canonicalDaemonHost(input.hostname), scopeId: input.scopeId, }; } const existingRaw = yield* fs.readFileString(lockPath).pipe(Effect.catchAll(() => Effect.succeed(null))); if (existingRaw !== null) { const existingPid = parseLockPid(existingRaw); if (existingPid !== null && !isPidAlive(existingPid)) { yield* fs.remove(lockPath, { force: true }); if (yield* tryAcquire()) { return { path: lockPath, hostname: canonicalDaemonHost(input.hostname), scopeId: input.scopeId, }; } } } return yield* Effect.fail( new Error( `Another daemon startup is already in progress for ${canonicalDaemonHost(input.hostname)} (${input.scopeId}).`, ), ); }); export const releaseDaemonStartLock = (input: DaemonStartLock): Effect.Effect< void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* fs.remove(input.path, { force: true }); }); // ---------------------------------------------------------------------------// Process helpers// --------------------------------------------------------------------------- export const isPidAlive = (pid: number): boolean => { if (!Number.isInteger(pid) || pid <= 0) return false; try { process.kill(pid, 0); return true; } catch { return false; }}; export const terminatePid = (pid: number): Effect.Effect<void, Error> => Effect.try({ try: () => { process.kill(pid, "SIGTERM"); }, catch: (cause) => cause instanceof Error ? cause : new Error(`Failed to terminate pid ${pid}: ${String(cause)}`), });
import { homedir } from "node:os";
import { FileSystem, Path } from "@effect/platform";
import type { PlatformError } from "@effect/platform/Error";
import * as Effect from "effect/Effect";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface DaemonRecord {
readonly version: 1;
readonly hostname: string;
readonly port: number;
readonly pid: number;
readonly startedAt: string;
readonly scopeDir: string | null;
}
export interface DaemonPointer {
readonly version: 1;
readonly hostname: string;
readonly port: number;
readonly pid: number;
readonly startedAt: string;
readonly scopeId: string;
readonly scopeDir: string | null;
readonly token: string;
}
export interface DaemonStartLock {
readonly path: string;
readonly hostname: string;
readonly scopeId: string;
}
// ---------------------------------------------------------------------------
// Host normalization
// ---------------------------------------------------------------------------
const LOCAL_HOST_ALIASES = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
export const canonicalDaemonHost = (hostname: string): string => {
const normalized = hostname.trim().toLowerCase();
return LOCAL_HOST_ALIASES.has(normalized) ? "localhost" : normalized;
};
export const currentDaemonScopeId = (): string => {
const explicitScope = process.env.EXECUTOR_SCOPE_DIR?.trim();
if (explicitScope && explicitScope.length > 0) {
return `scope:${explicitScope}`;
}
return `cwd:${process.cwd()}`;
};
// ---------------------------------------------------------------------------
// Paths
// ---------------------------------------------------------------------------
const resolveDaemonDataDir = (path: Path.Path): string =>
process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor");
const sanitizeHostForPath = (hostname: string): string => hostname.replaceAll(/[^a-z0-9.-]+/gi, "_");
const sanitizeScopeForPath = (scopeId: string): string => scopeId.replaceAll(/[^a-z0-9.-]+/gi, "_");
const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: number }): string => {
const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname));
return path.join(resolveDaemonDataDir(path), `daemon-${host}-${input.port}.json`);
};
const daemonPointerPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string => {
const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname));
const scope = sanitizeScopeForPath(input.scopeId);
return path.join(resolveDaemonDataDir(path), `daemon-active-${host}-${scope}.json`);
};
const daemonStartLockPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string =>
`${daemonPointerPath(path, input)}.lock`;
// ---------------------------------------------------------------------------
// Persistence
// ---------------------------------------------------------------------------
export const writeDaemonRecord = (input: {
hostname: string;
port: number;
pid: number;
scopeDir: string | null;
}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const dataDir = resolveDaemonDataDir(path);
yield* fs.makeDirectory(dataDir, { recursive: true });
const payload: DaemonRecord = {
version: 1,
hostname: canonicalDaemonHost(input.hostname),
port: input.port,
pid: input.pid,
startedAt: new Date().toISOString(),
scopeDir: input.scopeDir,
};
yield* fs.writeFileString(
daemonRecordPath(path, { hostname: input.hostname, port: input.port }),
`${JSON.stringify(payload, null, 2)}\n`,
);
});
const parseRecord = (raw: string): DaemonRecord | null => {
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch {
return null;
}
if (
typeof parsed !== "object" ||
parsed === null ||
!("version" in parsed) ||
(parsed as { version?: unknown }).version !== 1
) {
return null;
}
const r = parsed as Record<string, unknown>;
if (
typeof r.hostname !== "string" ||
typeof r.port !== "number" ||
typeof r.pid !== "number" ||
typeof r.startedAt !== "string" ||
!(typeof r.scopeDir === "string" || r.scopeDir === null)
) {
return null;
}
return {
version: 1,
hostname: canonicalDaemonHost(r.hostname),
port: r.port,
pid: r.pid,
startedAt: r.startedAt,
scopeDir: r.scopeDir,
};
};
const parsePointer = (raw: string): DaemonPointer | null => {
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch {
return null;
}
if (
typeof parsed !== "object" ||
parsed === null ||
!("version" in parsed) ||
(parsed as { version?: unknown }).version !== 1
) {
return null;
}
const r = parsed as Record<string, unknown>;
if (
typeof r.hostname !== "string" ||
typeof r.port !== "number" ||
typeof r.pid !== "number" ||
typeof r.startedAt !== "string" ||
typeof r.scopeId !== "string" ||
!(typeof r.scopeDir === "string" || r.scopeDir === null) ||
typeof r.token !== "string"
) {
return null;
}
return {
version: 1,
hostname: canonicalDaemonHost(r.hostname),
port: r.port,
pid: r.pid,
startedAt: r.startedAt,
scopeId: r.scopeId,
scopeDir: r.scopeDir,
token: r.token,
};
};
export const readDaemonRecord = (input: {
hostname: string;
port: number;
}): Effect.Effect<DaemonRecord | null, never, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const raw = yield* fs.readFileString(daemonRecordPath(path, input)).pipe(
Effect.catchAll(() => Effect.succeed(null)),
);
if (raw === null) return null;
return parseRecord(raw);
});
export const removeDaemonRecord = (input: {
hostname: string;
port: number;
}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fs.remove(daemonRecordPath(path, input), { force: true });
});
export const writeDaemonPointer = (input: {
hostname: string;
port: number;
pid: number;
scopeId: string;
scopeDir: string | null;
token: string;
}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const dataDir = resolveDaemonDataDir(path);
yield* fs.makeDirectory(dataDir, { recursive: true });
const payload: DaemonPointer = {
version: 1,
hostname: canonicalDaemonHost(input.hostname),
port: input.port,
pid: input.pid,
startedAt: new Date().toISOString(),
scopeId: input.scopeId,
scopeDir: input.scopeDir,
token: input.token,
};
yield* fs.writeFileString(
daemonPointerPath(path, { hostname: input.hostname, scopeId: input.scopeId }),
`${JSON.stringify(payload, null, 2)}\n`,
);
});
export const readDaemonPointer = (input: {
hostname: string;
scopeId: string;
}): Effect.Effect<DaemonPointer | null, never, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const raw = yield* fs
.readFileString(daemonPointerPath(path, input))
.pipe(Effect.catchAll(() => Effect.succeed(null)));
if (raw === null) return null;
return parsePointer(raw);
});
export const removeDaemonPointer = (input: {
hostname: string;
scopeId: string;
}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fs.remove(daemonPointerPath(path, input), { force: true });
});
const parseLockPid = (raw: string): number | null => {
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch {
return null;
}
if (
typeof parsed !== "object" ||
parsed === null ||
typeof (parsed as Record<string, unknown>).pid !== "number"
) {
return null;
}
return (parsed as Record<string, number>).pid;
};
export const acquireDaemonStartLock = (input: {
hostname: string;
scopeId: string;
}): Effect.Effect<DaemonStartLock, Error, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const dataDir = resolveDaemonDataDir(path);
yield* fs.makeDirectory(dataDir, { recursive: true });
const lockPath = daemonStartLockPath(path, input);
const lockPayload = JSON.stringify(
{
pid: process.pid,
hostname: canonicalDaemonHost(input.hostname),
scopeId: input.scopeId,
startedAt: new Date().toISOString(),
},
null,
2,
);
const tryAcquire = () =>
fs.writeFileString(lockPath, `${lockPayload}\n`, { flag: "wx" }).pipe(
Effect.as(true),
Effect.catchAll(() => Effect.succeed(false)),
);
if (yield* tryAcquire()) {
return {
path: lockPath,
hostname: canonicalDaemonHost(input.hostname),
scopeId: input.scopeId,
};
}
const existingRaw = yield* fs.readFileString(lockPath).pipe(Effect.catchAll(() => Effect.succeed(null)));
if (existingRaw !== null) {
const existingPid = parseLockPid(existingRaw);
if (existingPid !== null && !isPidAlive(existingPid)) {
yield* fs.remove(lockPath, { force: true });
if (yield* tryAcquire()) {
return {
path: lockPath,
hostname: canonicalDaemonHost(input.hostname),
scopeId: input.scopeId,
};
}
}
}
return yield* Effect.fail(
new Error(
`Another daemon startup is already in progress for ${canonicalDaemonHost(input.hostname)} (${input.scopeId}).`,
),
);
});
export const releaseDaemonStartLock = (input: DaemonStartLock): Effect.Effect<
void,
PlatformError,
FileSystem.FileSystem | Path.Path
> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.remove(input.path, { force: true });
});
// ---------------------------------------------------------------------------
// Process helpers
// ---------------------------------------------------------------------------
export const isPidAlive = (pid: number): boolean => {
if (!Number.isInteger(pid) || pid <= 0) return false;
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
};
export const terminatePid = (pid: number): Effect.Effect<void, Error> =>
Effect.try({
try: () => {
process.kill(pid, "SIGTERM");
},
catch: (cause) =>
cause instanceof Error ? cause : new Error(`Failed to terminate pid ${pid}: ${String(cause)}`),
});