A simple command-line tool to start NetBSD virtual machines using QEMU with sensible defaults.
1import { Data, Effect, pipe } from "effect";
2import { LOGS_DIR } from "../constants.ts";
3
4class LogCommandError extends Data.TaggedError("LogCommandError")<{
5 vmName: string;
6 exitCode: number;
7}> {}
8
9class CommandError extends Data.TaggedError("CommandError")<{
10 cause?: unknown;
11}> {}
12
13const createLogsDir = () =>
14 Effect.tryPromise({
15 try: () => Deno.mkdir(LOGS_DIR, { recursive: true }),
16 catch: (error) => new CommandError({ cause: error }),
17 });
18
19const buildLogPath = (name: string) =>
20 Effect.succeed(`${LOGS_DIR}/${name}.log`);
21
22const viewLogs = (name: string, follow: boolean, logPath: string) =>
23 Effect.tryPromise({
24 try: async () => {
25 const cmd = new Deno.Command(follow ? "tail" : "cat", {
26 args: [
27 ...(follow ? ["-n", "100", "-f"] : []),
28 logPath,
29 ],
30 stdin: "inherit",
31 stdout: "inherit",
32 stderr: "inherit",
33 });
34
35 const status = await cmd.spawn().status;
36 return { name, status };
37 },
38 catch: (error) => new CommandError({ cause: error }),
39 }).pipe(
40 Effect.flatMap(({ name, status }) =>
41 status.success ? Effect.succeed(undefined) : Effect.fail(
42 new LogCommandError({
43 vmName: name,
44 exitCode: status.code || 1,
45 }),
46 )
47 ),
48 );
49
50const handleError = (error: LogCommandError | CommandError | Error) =>
51 Effect.sync(() => {
52 if (error instanceof LogCommandError) {
53 console.error(`Failed to view logs for virtual machine ${error.vmName}.`);
54 Deno.exit(error.exitCode);
55 } else {
56 console.error(`An error occurred: ${error}`);
57 Deno.exit(1);
58 }
59 });
60
61const logsEffect = (name: string, follow: boolean) =>
62 pipe(
63 createLogsDir(),
64 Effect.flatMap(() => buildLogPath(name)),
65 Effect.flatMap((logPath) => viewLogs(name, follow, logPath)),
66 Effect.catchAll(handleError),
67 );
68
69export default async function (name: string, follow: boolean) {
70 await Effect.runPromise(logsEffect(name, follow));
71}