mirror of
https://github.com/feder-cr/Jobs_Applier_AI_Agent.git
synced 2026-11-07 10:36:05 -10:00
vm-manager, api: one logger and one pathExists, from shared
vm-manager's own Logger, silentLogger and stderrLogger repeated the shared ones; it now takes them from packages/shared and logs as the rest of the process does (prefixedStderrLogger). vm-manager and the API's VM driver each had their own exists(): one pathExists in shared replaces both.
This commit is contained in:
1 parent
ac26cec1bf
commit
528be98890
10 files changed
+29
-61
No files matched your search
@@ -9,10 +9,10 @@
|
||||
* - every start uses the newest `runtime-<version>.iso`, so a new version of
|
||||
* our code reaches a Dot with a restart (section 3.3).
|
||||
*/
|
||||
import { access, readdir } from "node:fs/promises";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { type ComputerDriver, type ComputerSpecInput, type ComputerState, type CreatedComputer, type GuestApi, type GuestEndpoint, type StartedComputer, type WaitForHealthOptions } from "@invisible-dots/scheduler";
|
||||
import { type HealthAnswer, type HostPaths, type ImageKind, imageLabel, imageVersionOf, type Logger } from "@invisible-dots/shared";
|
||||
import { type HealthAnswer, type HostPaths, type ImageKind, imageLabel, imageVersionOf, type Logger, pathExists } from "@invisible-dots/shared";
|
||||
import { VmManager, type StartVmResult, type VmSpec } from "@invisible-dots/vm-manager";
|
||||
|
||||
/** Compare versions such as "2026.10.1" and "2026.9.3" numerically, part by part. */
|
||||
@@ -112,7 +112,7 @@ export class VmManagerDriver implements ComputerDriver {
|
||||
}
|
||||
|
||||
async state(dotId: string): Promise<ComputerState> {
|
||||
const [info, exists] = await Promise.all([this.#vm.state(dotId), fileExists(this.#vm.paths.diskPath(dotId))]);
|
||||
const [info, exists] = await Promise.all([this.#vm.state(dotId), pathExists(this.#vm.paths.diskPath(dotId))]);
|
||||
return {
|
||||
exists,
|
||||
state: info.state,
|
||||
@@ -130,14 +130,6 @@ export class VmManagerDriver implements ComputerDriver {
|
||||
async close(): Promise<void> {}
|
||||
}
|
||||
|
||||
async function fileExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** The driver `invisible-dots server` uses: VmManager over this host's QEMU and INVISIBLE_DOTS_HOME. */
|
||||
export function createVmDriver(options: { env: Record<string, string | undefined>; paths: HostPaths; logger: Logger }): VmManagerDriver {
|
||||
|
||||
@@ -12,8 +12,8 @@ import { join } from "node:path";
|
||||
import { createTestDatabase, type TestDatabase } from "@invisible-dots/database/testing";
|
||||
import { Scheduler } from "@invisible-dots/scheduler";
|
||||
import { FAKE_MAX_IDENTITIES, FakeGuest, waitFor } from "@invisible-dots/scheduler/testing";
|
||||
import { computerResources, hostPaths, type HostPaths, parseDotConfig, prefixedStderrLogger } from "@invisible-dots/shared";
|
||||
import { silentLogger, VmManager } from "@invisible-dots/vm-manager";
|
||||
import { computerResources, hostPaths, type HostPaths, parseDotConfig, prefixedStderrLogger, silentLogger } from "@invisible-dots/shared";
|
||||
import { VmManager } from "@invisible-dots/vm-manager";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { FakeQemuHost, FakeRunner, type FakeVm } from "../../vm-manager/test/fakes.js";
|
||||
import { compareVersions, latestImage, VmManagerDriver } from "../src/index.js";
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
* and a stop closes the web client first, then the control plane.
|
||||
*/
|
||||
import type { ListenAddress, ListenSetting, StartServerOptions } from "@invisible-dots/api";
|
||||
import { DEFAULT_WEB_LISTEN, ENV, type HostPaths } from "@invisible-dots/shared";
|
||||
import type { Logger } from "@invisible-dots/vm-manager";
|
||||
import { DEFAULT_WEB_LISTEN, ENV, type HostPaths, type Logger } from "@invisible-dots/shared";
|
||||
import { locateWebBuild, type WebServer, type WebServerOptions } from "./web.js";
|
||||
|
||||
/** What `serve` needs from the packages that own the processes; tests pass fakes. */
|
||||
|
||||
@@ -41,9 +41,10 @@ import {
|
||||
type SecretsRequest,
|
||||
type SkillListAnswer,
|
||||
type ToolListAnswer,
|
||||
silentLogger,
|
||||
type Logger,
|
||||
} from "@invisible-dots/shared";
|
||||
import { GuestRequestError } from "./errors.js";
|
||||
import { silentLogger, type Logger } from "./logger.js";
|
||||
import { GUEST_PORT_HOST } from "./ports.js";
|
||||
|
||||
export interface GuestClientOptions {
|
||||
|
||||
@@ -76,7 +76,6 @@ export * from "./errors.js";
|
||||
export * from "./guest-client.js";
|
||||
export * from "./host-doctor.js";
|
||||
export * from "./host.js";
|
||||
export * from "./logger.js";
|
||||
export * from "./ports.js";
|
||||
export * from "./powershell.js";
|
||||
export * from "./process-file.js";
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/** A minimal structured logger; the API process passes its own (pino-compatible shape). */
|
||||
export interface Logger {
|
||||
debug(message: string, fields?: Record<string, unknown>): void;
|
||||
info(message: string, fields?: Record<string, unknown>): void;
|
||||
warn(message: string, fields?: Record<string, unknown>): void;
|
||||
error(message: string, fields?: Record<string, unknown>): void;
|
||||
}
|
||||
|
||||
function write(level: string, message: string, fields?: Record<string, unknown>): void {
|
||||
const suffix = fields && Object.keys(fields).length > 0 ? ` ${JSON.stringify(fields)}` : "";
|
||||
process.stderr.write(`[vm-manager] ${level} ${message}${suffix}\n`);
|
||||
}
|
||||
|
||||
/** Logs info and above to stderr, one line each. */
|
||||
export const stderrLogger: Logger = {
|
||||
debug: () => {},
|
||||
info: (message, fields) => write("info", message, fields),
|
||||
warn: (message, fields) => write("warn", message, fields),
|
||||
error: (message, fields) => write("error", message, fields),
|
||||
};
|
||||
|
||||
export const silentLogger: Logger = {
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
@@ -14,7 +14,7 @@
|
||||
* restart the pid file stands in for it, checked as `processIsOurs` says.
|
||||
*/
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { access, appendFile, mkdir, open, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { appendFile, mkdir, open, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { isAbsolute, join, relative, resolve } from "node:path";
|
||||
import {
|
||||
allowlistedEnvironment,
|
||||
@@ -23,9 +23,12 @@ import {
|
||||
hostRestartedSince,
|
||||
hostUptimeSeconds,
|
||||
isFatalGuestError,
|
||||
pathExists,
|
||||
prefixedStderrLogger,
|
||||
replaceFile,
|
||||
type DotConfig,
|
||||
type HostPaths,
|
||||
type Logger,
|
||||
type VmState,
|
||||
} from "@invisible-dots/shared";
|
||||
import {
|
||||
@@ -40,7 +43,6 @@ import {
|
||||
} from "./errors.js";
|
||||
import { GuestClient, waitForGuestHealth, type GuestClientOptions, type WaitForGuestHealthOptions } from "./guest-client.js";
|
||||
import { accelerator as hostAccelerator, findQemu, type Accelerator, type QemuInstallation } from "./host.js";
|
||||
import { stderrLogger, type Logger } from "./logger.js";
|
||||
import { pickFreePort, portListens } from "./ports.js";
|
||||
import { readProcessFile, writeProcessFile, type ProcessFile } from "./process-file.js";
|
||||
import { checkVmResources, CPU_MODEL, qemuArgs, qemuPathArg } from "./qemu-args.js";
|
||||
@@ -183,15 +185,6 @@ function assertDotId(dotId: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** QEMU wrote this when it could not bind the host side of the port forward. */
|
||||
const PORT_TAKEN = /Could not set up host forwarding rule/i;
|
||||
|
||||
@@ -257,7 +250,7 @@ export class VmManager {
|
||||
this.options = options;
|
||||
this.env = options.env ?? process.env;
|
||||
this.paths = options.paths ?? hostPaths(this.env);
|
||||
this.logger = options.logger ?? stderrLogger;
|
||||
this.logger = options.logger ?? prefixedStderrLogger("vm-manager");
|
||||
this.runner = options.runner ?? new NodeCommandRunner();
|
||||
this.processes = options.processes ?? new NodeProcessControl();
|
||||
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
||||
@@ -372,7 +365,7 @@ export class VmManager {
|
||||
["golden image", spec.goldenImage],
|
||||
["runtime ISO", spec.runtimeImage],
|
||||
] as const) {
|
||||
if (!(await exists(path))) throw new VmManagerError(`${label} ${path} does not exist: build it with "invisible-dots image build" first`);
|
||||
if (!(await pathExists(path))) throw new VmManagerError(`${label} ${path} does not exist: build it with "invisible-dots image build" first`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +404,7 @@ export class VmManager {
|
||||
await mkdir(dir, { recursive: true, mode: 0o700 });
|
||||
const diskPath = this.paths.diskPath(spec.dotId);
|
||||
let diskCreated = false;
|
||||
if (await exists(diskPath)) {
|
||||
if (await pathExists(diskPath)) {
|
||||
this.logger.warn("overlay already exists, keeping it", { dotId: spec.dotId, disk: diskPath });
|
||||
} else {
|
||||
await this.qemuImg(["create", "-f", "qcow2", "-F", "qcow2", "-b", qemuPathArg("golden image", spec.goldenImage), qemuPathArg("disk", diskPath), String(spec.diskBytes)]);
|
||||
@@ -540,7 +533,7 @@ export class VmManager {
|
||||
}
|
||||
await this.forget(dotId);
|
||||
const diskPath = this.paths.diskPath(dotId);
|
||||
if (!(await exists(diskPath))) throw new VmStateError(dotId, `${diskPath} does not exist; create the VM first`);
|
||||
if (!(await pathExists(diskPath))) throw new VmStateError(dotId, `${diskPath} does not exist; create the VM first`);
|
||||
await this.assertImages(spec);
|
||||
await this.writeSeed(spec);
|
||||
const qemu = await this.qemu();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { createServer, type Server } from "node:net";
|
||||
import { tmpdir, uptime } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { hostPaths, type HostPaths } from "@invisible-dots/shared";
|
||||
import { hostPaths, type HostPaths, silentLogger } from "@invisible-dots/shared";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
AcceleratorUnavailableError,
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
VmStateError,
|
||||
generateToken,
|
||||
qemuArgs,
|
||||
silentLogger,
|
||||
vmSpecFromConfig,
|
||||
type VmManagerOptions,
|
||||
type VmSpec,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./api-token.js";
|
||||
export * from "./browser.js";
|
||||
export * from "./files.js";
|
||||
export * from "./path-exists.js";
|
||||
export * from "./paths.js";
|
||||
export * from "./sockets.js";
|
||||
export * from "./stderr-logger.js";
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { access } from "node:fs/promises";
|
||||
|
||||
/** Whether something is at `path` that this process may see. Node-only. */
|
||||
export async function pathExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user