chore: portless setup
This commit is contained in:
parent
e4898b97ea
commit
4ea9f34154
8 changed files with 42 additions and 13 deletions
|
|
@ -4,6 +4,6 @@ RESTIC_CACHE_DIR=./data/restic/cache
|
||||||
ZEROBYTE_REPOSITORIES_DIR=./data/repositories
|
ZEROBYTE_REPOSITORIES_DIR=./data/repositories
|
||||||
ZEROBYTE_VOLUMES_DIR=./data/volumes
|
ZEROBYTE_VOLUMES_DIR=./data/volumes
|
||||||
APP_SECRET=<openssl rand -hex 32>
|
APP_SECRET=<openssl rand -hex 32>
|
||||||
BASE_URL=http://localhost:300
|
BASE_URL=http://*.localhost
|
||||||
TRUST_PROXY=false
|
TRUST_PROXY=false
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -15,7 +15,7 @@ notes.md
|
||||||
smb-password.txt
|
smb-password.txt
|
||||||
cache.db
|
cache.db
|
||||||
|
|
||||||
data/
|
/data
|
||||||
|
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
@ -34,8 +34,11 @@ node_modules/
|
||||||
|
|
||||||
playwright/.auth
|
playwright/.auth
|
||||||
playwright/temp
|
playwright/temp
|
||||||
|
playwright/data
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
.deepsec/
|
||||||
|
.codex/
|
||||||
|
|
||||||
# OpenAPI error logs
|
# OpenAPI error logs
|
||||||
openapi-ts-error-*.log
|
openapi-ts-error-*.log
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,11 @@ const processWithAgentRuntime = process as ProcessWithAgentRuntime;
|
||||||
|
|
||||||
const setAgentRuntime = () => {
|
const setAgentRuntime = () => {
|
||||||
processWithAgentRuntime.__zerobyteAgentRuntime = {
|
processWithAgentRuntime.__zerobyteAgentRuntime = {
|
||||||
agentManager: fromAny({ stop: Effect.void, waitForAgentReady: vi.fn(async () => true) }),
|
agentManager: fromAny({
|
||||||
|
stop: Effect.void,
|
||||||
|
getControllerUrl: vi.fn(() => "ws://127.0.0.1:4567"),
|
||||||
|
waitForAgentReady: vi.fn(async () => true),
|
||||||
|
}),
|
||||||
localAgent: null,
|
localAgent: null,
|
||||||
isStoppingLocalAgent: false,
|
isStoppingLocalAgent: false,
|
||||||
localAgentRestartTimeout: null,
|
localAgentRestartTimeout: null,
|
||||||
|
|
@ -88,6 +92,13 @@ test("respawns the local agent after an unexpected exit", async () => {
|
||||||
await vi.advanceTimersByTimeAsync(1_000);
|
await vi.advanceTimersByTimeAsync(1_000);
|
||||||
|
|
||||||
expect(spawnMock).toHaveBeenCalledTimes(2);
|
expect(spawnMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(spawnMock).toHaveBeenLastCalledWith(
|
||||||
|
"bun",
|
||||||
|
expect.any(Array),
|
||||||
|
expect.objectContaining({
|
||||||
|
env: expect.objectContaining({ ZEROBYTE_CONTROLLER_URL: "ws://127.0.0.1:4567" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("does not respawn the local agent after an intentional stop", async () => {
|
test("does not respawn the local agent after an intentional stop", async () => {
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ afterEach(() => {
|
||||||
test("websocket fetch rejects requests without a bearer token", async () => {
|
test("websocket fetch rejects requests without a bearer token", async () => {
|
||||||
const serve = vi
|
const serve = vi
|
||||||
.spyOn(Bun, "serve")
|
.spyOn(Bun, "serve")
|
||||||
.mockReturnValue(fromPartial({ port: 3001, stop: vi.fn(() => Promise.resolve()) }));
|
.mockReturnValue(fromPartial({ port: 4567, stop: vi.fn(() => Promise.resolve()) }));
|
||||||
const { runtime } = await startRuntime();
|
const { runtime } = await startRuntime();
|
||||||
const fetch = serve.mock.calls[0]?.[0].fetch;
|
const fetch = serve.mock.calls[0]?.[0].fetch;
|
||||||
const upgrade = vi.fn();
|
const upgrade = vi.fn();
|
||||||
|
|
@ -120,6 +120,7 @@ test("websocket fetch rejects requests without a bearer token", async () => {
|
||||||
const response = await invokeFetch(fetch, new Request("http://localhost:3001/agent"), srv);
|
const response = await invokeFetch(fetch, new Request("http://localhost:3001/agent"), srv);
|
||||||
await Effect.runPromise(runtime.stop);
|
await Effect.runPromise(runtime.stop);
|
||||||
|
|
||||||
|
expect(runtime.getControllerUrl()).toBeNull();
|
||||||
expect(response?.status).toBe(401);
|
expect(response?.status).toBe(401);
|
||||||
expect(await response?.text()).toBe("Missing token");
|
expect(await response?.text()).toBe("Missing token");
|
||||||
expect(upgrade).not.toHaveBeenCalled();
|
expect(upgrade).not.toHaveBeenCalled();
|
||||||
|
|
@ -129,8 +130,9 @@ test("websocket fetch rejects invalid bearer tokens", async () => {
|
||||||
tokenMocks.validateAgentToken.mockResolvedValue(undefined);
|
tokenMocks.validateAgentToken.mockResolvedValue(undefined);
|
||||||
const serve = vi
|
const serve = vi
|
||||||
.spyOn(Bun, "serve")
|
.spyOn(Bun, "serve")
|
||||||
.mockReturnValue(fromPartial({ port: 3001, stop: vi.fn(() => Promise.resolve()) }));
|
.mockReturnValue(fromPartial({ port: 4567, stop: vi.fn(() => Promise.resolve()) }));
|
||||||
const { runtime } = await startRuntime();
|
const { runtime } = await startRuntime();
|
||||||
|
expect(runtime.getControllerUrl()).toBe("ws://127.0.0.1:4567");
|
||||||
const fetch = serve.mock.calls[0]?.[0].fetch;
|
const fetch = serve.mock.calls[0]?.[0].fetch;
|
||||||
const upgrade = vi.fn();
|
const upgrade = vi.fn();
|
||||||
const srv = fromPartial<Parameters<NonNullable<typeof fetch>>[1]>({ upgrade });
|
const srv = fromPartial<Parameters<NonNullable<typeof fetch>>[1]>({ upgrade });
|
||||||
|
|
|
||||||
|
|
@ -311,14 +311,20 @@ export const agentManager = {
|
||||||
|
|
||||||
export const startLocalAgent = async () => {
|
export const startLocalAgent = async () => {
|
||||||
const runtime = getAgentRuntimeState();
|
const runtime = getAgentRuntimeState();
|
||||||
await spawnLocalAgentProcess(runtime);
|
|
||||||
|
|
||||||
if (!runtime.agentManager) {
|
if (!runtime.agentManager) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`startLocalAgent spawned ${LOCAL_AGENT_ID} via spawnLocalAgentProcess, but runtime.agentManager is missing; waitForAgentReady cannot check readiness`,
|
`startLocalAgent cannot spawn ${LOCAL_AGENT_ID} because runtime.agentManager is missing; waitForAgentReady cannot check readiness`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const controllerUrl = runtime.agentManager.getControllerUrl();
|
||||||
|
if (!controllerUrl) {
|
||||||
|
throw new Error(`startLocalAgent cannot spawn ${LOCAL_AGENT_ID} because the controller URL is not available`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await spawnLocalAgentProcess(runtime, controllerUrl);
|
||||||
|
|
||||||
if (!(await runtime.agentManager.waitForAgentReady(LOCAL_AGENT_ID))) {
|
if (!(await runtime.agentManager.waitForAgentReady(LOCAL_AGENT_ID))) {
|
||||||
throw new Error("Local agent did not become ready before startup");
|
throw new Error("Local agent did not become ready before startup");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class StopAgentManagerServerError extends Data.TaggedError("StopAgentManagerServ
|
||||||
export function createAgentManagerRuntime(onEvent: (event: AgentManagerEvent) => void) {
|
export function createAgentManagerRuntime(onEvent: (event: AgentManagerEvent) => void) {
|
||||||
let sessions = new Map<string, ControllerAgentSessionHandle>();
|
let sessions = new Map<string, ControllerAgentSessionHandle>();
|
||||||
let runtimeScope: Scope.CloseableScope | null = null;
|
let runtimeScope: Scope.CloseableScope | null = null;
|
||||||
|
let controllerUrl: string | null = null;
|
||||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
const closeSession = (sessionHandle: ControllerAgentSessionHandle) =>
|
const closeSession = (sessionHandle: ControllerAgentSessionHandle) =>
|
||||||
|
|
@ -215,7 +216,8 @@ export function createAgentManagerRuntime(onEvent: (event: AgentManagerEvent) =>
|
||||||
const acquireServer = Effect.acquireRelease(
|
const acquireServer = Effect.acquireRelease(
|
||||||
Effect.sync(() =>
|
Effect.sync(() =>
|
||||||
Bun.serve<AgentConnectionData>({
|
Bun.serve<AgentConnectionData>({
|
||||||
port: 3001,
|
hostname: "127.0.0.1",
|
||||||
|
port: 0,
|
||||||
async fetch(req, srv) {
|
async fetch(req, srv) {
|
||||||
const authorizationHeader = req.headers.get("authorization");
|
const authorizationHeader = req.headers.get("authorization");
|
||||||
const token = authorizationHeader?.slice("Bearer ".length);
|
const token = authorizationHeader?.slice("Bearer ".length);
|
||||||
|
|
@ -279,6 +281,7 @@ export function createAgentManagerRuntime(onEvent: (event: AgentManagerEvent) =>
|
||||||
logger.info("Stopping Agent Manager...");
|
logger.info("Stopping Agent Manager...");
|
||||||
const scope = runtimeScope;
|
const scope = runtimeScope;
|
||||||
runtimeScope = null;
|
runtimeScope = null;
|
||||||
|
controllerUrl = null;
|
||||||
yield* Scope.close(scope, Exit.succeed(undefined));
|
yield* Scope.close(scope, Exit.succeed(undefined));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -296,11 +299,13 @@ export function createAgentManagerRuntime(onEvent: (event: AgentManagerEvent) =>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
runtimeScope = scope;
|
runtimeScope = scope;
|
||||||
|
controllerUrl = `ws://127.0.0.1:${server.port}`;
|
||||||
logger.info(`Agent Manager listening on port ${server.port}`);
|
logger.info(`Agent Manager listening on port ${server.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start,
|
start,
|
||||||
|
getControllerUrl: () => controllerUrl,
|
||||||
waitForAgentReady: async (agentId: string, timeoutMs = 10_000) => {
|
waitForAgentReady: async (agentId: string, timeoutMs = 10_000) => {
|
||||||
const deadline = Date.now() + timeoutMs;
|
const deadline = Date.now() + timeoutMs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ type LocalAgentState = {
|
||||||
localAgentRestartTimeout: ReturnType<typeof setTimeout> | null;
|
localAgentRestartTimeout: ReturnType<typeof setTimeout> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function spawnLocalAgentProcess(runtime: LocalAgentState) {
|
export async function spawnLocalAgentProcess(runtime: LocalAgentState, controllerUrl: string) {
|
||||||
await stopLocalAgentProcess(runtime);
|
await stopLocalAgentProcess(runtime);
|
||||||
|
|
||||||
if (!config.flags.enableLocalAgent) {
|
if (!config.flags.enableLocalAgent) {
|
||||||
|
|
@ -31,7 +31,7 @@ export async function spawnLocalAgentProcess(runtime: LocalAgentState) {
|
||||||
const agentProcess = spawn("bun", args, {
|
const agentProcess = spawn("bun", args, {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
ZEROBYTE_CONTROLLER_URL: "ws://localhost:3001",
|
ZEROBYTE_CONTROLLER_URL: controllerUrl,
|
||||||
ZEROBYTE_AGENT_TOKEN: agentToken,
|
ZEROBYTE_AGENT_TOKEN: agentToken,
|
||||||
},
|
},
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
|
@ -62,8 +62,10 @@ export async function spawnLocalAgentProcess(runtime: LocalAgentState) {
|
||||||
|
|
||||||
runtime.localAgentRestartTimeout = setTimeout(() => {
|
runtime.localAgentRestartTimeout = setTimeout(() => {
|
||||||
runtime.localAgentRestartTimeout = null;
|
runtime.localAgentRestartTimeout = null;
|
||||||
void spawnLocalAgentProcess(runtime).catch((error) => {
|
void spawnLocalAgentProcess(runtime, controllerUrl).catch((error) => {
|
||||||
logger.error(`Failed to restart local agent: ${error instanceof Error ? error.message : String(error)}`);
|
logger.error(
|
||||||
|
`Failed to restart local agent: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}, 1_000);
|
}, 1_000);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"fmt": "vp fmt",
|
"fmt": "vp fmt",
|
||||||
"lint": "vp lint --type-aware --type-check",
|
"lint": "vp lint --type-aware --type-check",
|
||||||
"check": "vp check",
|
"check": "vp check",
|
||||||
"dev": "NODE_ENV=development bunx --bun vite",
|
"dev": "NODE_ENV=development portless run bunx --bun vite",
|
||||||
"build": "bunx --bun vite build",
|
"build": "bunx --bun vite build",
|
||||||
"start": "bun run .output/server/index.mjs",
|
"start": "bun run .output/server/index.mjs",
|
||||||
"build:backend-integration": "bun build app/test/backend-integration/index.ts --outdir .output/backend-integration --target bun",
|
"build:backend-integration": "bun build app/test/backend-integration/index.ts --outdir .output/backend-integration --target bun",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue