import { homedir } from "node:os";import { resolve } from "node:path";import { FileSystem, Option, Path, Schema } from "effect";import type { PlatformError } from "effect/PlatformError";import * as Effect from "effect/Effect"; import { parseExecutorLocalServerManifest, serializeExecutorLocalServerManifest, type ExecutorLocalServerManifest,} from "@executor-js/sdk/shared";import { isPidAlive } from "./daemon-state"; export interface LocalServerStartLock { readonly path: string;} export const resolveExecutorDataDir = (path: Path.Path): string => resolve(process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor")); const serverControlDir = (path: Path.Path): string => path.join(resolveExecutorDataDir(path), "server-control"); const localServerManifestPath = (path: Path.Path): string => path.join(serverControlDir(path), "server.json"); const localServerStartLockPath = (path: Path.Path): string => path.join(serverControlDir(path), "startup.lock"); export const readLocalServerManifest = (): Effect.Effect< ExecutorLocalServerManifest | null, never, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const raw = yield* fs .readFileString(localServerManifestPath(path)) .pipe(Effect.catchCause(() => Effect.succeed(null))); if (raw === null) return null; return parseExecutorLocalServerManifest(raw); }); export const writeLocalServerManifest = ( manifest: ExecutorLocalServerManifest,): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.makeDirectory(serverControlDir(path), { recursive: true }); const manifestPath = localServerManifestPath(path); // The manifest embeds the bearer token; create it owner-only so there's no // window where it exists world-readable (mode applies only on create). The // chmod after covers overwriting a pre-existing world-readable file, where // the create mode is ignored. yield* fs.writeFileString(manifestPath, serializeExecutorLocalServerManifest(manifest), { mode: 0o600, }); yield* fs.chmod(manifestPath, 0o600).pipe(Effect.ignore); }); export const removeLocalServerManifestIfOwnedBy = (input: { readonly pid: number;}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const manifestPath = localServerManifestPath(path); const raw = yield* fs .readFileString(manifestPath) .pipe(Effect.catchCause(() => Effect.succeed(null))); if (raw === null) return; const manifest = parseExecutorLocalServerManifest(raw); if (manifest?.pid !== input.pid) return; yield* fs.remove(manifestPath, { force: true }); }); /** * Remove the server manifest unconditionally. Used by an OS-supervised daemon * to reclaim a stale `server.json` left by a previous boot: across a reboot the * recorded pid is meaningless (pids recycle, so it may now belong to an * unrelated process), and launchd/systemd already guarantee a single supervised * instance — so any pre-existing manifest is stale and the supervised daemon * owns it. */export const removeLocalServerManifest = (): Effect.Effect< void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.remove(localServerManifestPath(path), { force: true }); }); const StartupLockPayload = Schema.Struct({ pid: Schema.Number,}); const decodeStartupLockPayload = Schema.decodeUnknownOption( Schema.fromJsonString(StartupLockPayload),); const parseLockPid = (raw: string): number | null => { const decoded = decodeStartupLockPayload(raw); return Option.isSome(decoded) ? decoded.value.pid : null;}; export const acquireLocalServerStartLock = (): Effect.Effect< LocalServerStartLock, Error, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.makeDirectory(serverControlDir(path), { recursive: true }); const lockPath = localServerStartLockPath(path); const lockPayload = `${JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2)}\n`; const tryAcquire = () => fs.writeFileString(lockPath, lockPayload, { flag: "wx" }).pipe( Effect.as(true), Effect.catchCause(() => Effect.succeed(false)), ); if (yield* tryAcquire()) return { path: lockPath }; const existingRaw = yield* fs .readFileString(lockPath) .pipe(Effect.catchCause(() => 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 }; } } return yield* Effect.fail( new Error("Another local Executor server startup is already in progress."), ); }); export const releaseLocalServerStartLock = ( lock: LocalServerStartLock,): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* fs.remove(lock.path, { force: true }); });
import { homedir } from "node:os";
import { resolve } from "node:path";
import { FileSystem, Option, Path, Schema } from "effect";
import type { PlatformError } from "effect/PlatformError";
import * as Effect from "effect/Effect";
import {
parseExecutorLocalServerManifest,
serializeExecutorLocalServerManifest,
type ExecutorLocalServerManifest,
} from "@executor-js/sdk/shared";
import { isPidAlive } from "./daemon-state";
export interface LocalServerStartLock {
readonly path: string;
}
export const resolveExecutorDataDir = (path: Path.Path): string =>
resolve(process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor"));
const serverControlDir = (path: Path.Path): string =>
path.join(resolveExecutorDataDir(path), "server-control");
const localServerManifestPath = (path: Path.Path): string =>
path.join(serverControlDir(path), "server.json");
const localServerStartLockPath = (path: Path.Path): string =>
path.join(serverControlDir(path), "startup.lock");
export const readLocalServerManifest = (): Effect.Effect<
ExecutorLocalServerManifest | null,
never,
FileSystem.FileSystem | Path.Path
> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const raw = yield* fs
.readFileString(localServerManifestPath(path))
.pipe(Effect.catchCause(() => Effect.succeed(null)));
if (raw === null) return null;
return parseExecutorLocalServerManifest(raw);
});
export const writeLocalServerManifest = (
manifest: ExecutorLocalServerManifest,
): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fs.makeDirectory(serverControlDir(path), { recursive: true });
const manifestPath = localServerManifestPath(path);
// The manifest embeds the bearer token; create it owner-only so there's no
// window where it exists world-readable (mode applies only on create). The
// chmod after covers overwriting a pre-existing world-readable file, where
// the create mode is ignored.
yield* fs.writeFileString(manifestPath, serializeExecutorLocalServerManifest(manifest), {
mode: 0o600,
});
yield* fs.chmod(manifestPath, 0o600).pipe(Effect.ignore);
});
export const removeLocalServerManifestIfOwnedBy = (input: {
readonly pid: number;
}): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const manifestPath = localServerManifestPath(path);
const raw = yield* fs
.readFileString(manifestPath)
.pipe(Effect.catchCause(() => Effect.succeed(null)));
if (raw === null) return;
const manifest = parseExecutorLocalServerManifest(raw);
if (manifest?.pid !== input.pid) return;
yield* fs.remove(manifestPath, { force: true });
});
/**
* Remove the server manifest unconditionally. Used by an OS-supervised daemon
* to reclaim a stale `server.json` left by a previous boot: across a reboot the
* recorded pid is meaningless (pids recycle, so it may now belong to an
* unrelated process), and launchd/systemd already guarantee a single supervised
* instance — so any pre-existing manifest is stale and the supervised daemon
* owns it.
*/
export const removeLocalServerManifest = (): Effect.Effect<
void,
PlatformError,
FileSystem.FileSystem | Path.Path
> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fs.remove(localServerManifestPath(path), { force: true });
});
const StartupLockPayload = Schema.Struct({
pid: Schema.Number,
});
const decodeStartupLockPayload = Schema.decodeUnknownOption(
Schema.fromJsonString(StartupLockPayload),
);
const parseLockPid = (raw: string): number | null => {
const decoded = decodeStartupLockPayload(raw);
return Option.isSome(decoded) ? decoded.value.pid : null;
};
export const acquireLocalServerStartLock = (): Effect.Effect<
LocalServerStartLock,
Error,
FileSystem.FileSystem | Path.Path
> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fs.makeDirectory(serverControlDir(path), { recursive: true });
const lockPath = localServerStartLockPath(path);
const lockPayload = `${JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }, null, 2)}\n`;
const tryAcquire = () =>
fs.writeFileString(lockPath, lockPayload, { flag: "wx" }).pipe(
Effect.as(true),
Effect.catchCause(() => Effect.succeed(false)),
);
if (yield* tryAcquire()) return { path: lockPath };
const existingRaw = yield* fs
.readFileString(lockPath)
.pipe(Effect.catchCause(() => 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 };
}
}
return yield* Effect.fail(
new Error("Another local Executor server startup is already in progress."),
);
});
export const releaseLocalServerStartLock = (
lock: LocalServerStartLock,
): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.remove(lock.path, { force: true });
});