A simple command-line tool to start NetBSD virtual machines using QEMU with sensible defaults.
1import { Table } from "@cliffy/table";
2import dayjs from "dayjs";
3import relativeTime from "dayjs/plugin/relativeTime.js";
4import utc from "dayjs/plugin/utc.js";
5import { Data, Effect, pipe } from "effect";
6import { ctx } from "../context.ts";
7import type { VirtualMachine } from "../db.ts";
8
9dayjs.extend(relativeTime);
10dayjs.extend(utc);
11
12class DbQueryError extends Data.TaggedError("DbQueryError")<{
13 cause?: unknown;
14}> {}
15
16const fetchVMs = (all: boolean) =>
17 Effect.tryPromise({
18 try: () =>
19 ctx.db.selectFrom("virtual_machines")
20 .selectAll()
21 .where((eb) => {
22 if (all) {
23 return eb("id", "!=", "");
24 }
25 return eb("status", "=", "RUNNING");
26 })
27 .execute(),
28 catch: (error) => new DbQueryError({ cause: error }),
29 });
30
31const createTable = () =>
32 Effect.succeed(
33 new Table(
34 ["NAME", "VCPU", "MEMORY", "STATUS", "PID", "BRIDGE", "PORTS", "CREATED"],
35 ),
36 );
37
38const populateTable = (table: Table, vms: VirtualMachine[]) =>
39 Effect.sync(() => {
40 for (const vm of vms) {
41 table.push([
42 vm.name,
43 vm.cpus.toString(),
44 vm.memory,
45 formatStatus(vm),
46 vm.pid?.toString() ?? "-",
47 vm.bridge ?? "-",
48 formatPorts(vm.portForward),
49 dayjs.utc(vm.createdAt).local().fromNow(),
50 ]);
51 }
52 return table;
53 });
54
55const displayTable = (table: Table) =>
56 Effect.sync(() => {
57 console.log(table.padding(2).toString());
58 });
59
60const handleError = (error: DbQueryError | Error) =>
61 Effect.sync(() => {
62 console.error(`Failed to fetch virtual machines: ${error}`);
63 Deno.exit(1);
64 });
65
66const psEffect = (all: boolean) =>
67 pipe(
68 Effect.all([fetchVMs(all), createTable()]),
69 Effect.flatMap(([vms, table]) => populateTable(table, vms)),
70 Effect.flatMap(displayTable),
71 Effect.catchAll(handleError),
72 );
73
74export default async function (all: boolean) {
75 await Effect.runPromise(psEffect(all));
76}
77
78function formatStatus(vm: VirtualMachine) {
79 switch (vm.status) {
80 case "RUNNING":
81 return `Up ${
82 dayjs.utc(vm.updatedAt).local().fromNow().replace("ago", "")
83 }`;
84 case "STOPPED":
85 return `Exited ${dayjs.utc(vm.updatedAt).local().fromNow()}`;
86 default:
87 return vm.status;
88 }
89}
90
91function formatPorts(portForward?: string) {
92 if (!portForward) {
93 return "-";
94 }
95
96 const mappings = portForward.split(",");
97 return mappings.map((mapping) => {
98 const [hostPort, guestPort] = mapping.split(":");
99 return `${hostPort}->${guestPort}`;
100 }).join(", ");
101}