A convenient CLI tool to quickly spin up DragonflyBSD virtual machines using QEMU with sensible defaults.
1import chalk from "chalk";
2import { Data, Effect } from "effect";
3
4export class NetworkError extends Data.TaggedError("NetworkError")<{
5 cause?: unknown;
6}> {}
7
8export class BridgeSetupError extends Data.TaggedError("BridgeSetupError")<{
9 cause?: unknown;
10}> {}
11
12export const setupQemuBridge = (bridgeName: string) =>
13 Effect.tryPromise({
14 try: async () => {
15 const bridgeConfPath = "/etc/qemu/bridge.conf";
16 const bridgeConfContent = await Deno.readTextFile(bridgeConfPath).catch(
17 () => "",
18 );
19 if (bridgeConfContent.includes(`allow ${bridgeName}`)) {
20 console.log(
21 chalk.greenBright(
22 `QEMU bridge configuration for ${bridgeName} already exists.`,
23 ),
24 );
25 return;
26 }
27
28 console.log(
29 chalk.blueBright(
30 `Adding QEMU bridge configuration for ${bridgeName}...`,
31 ),
32 );
33
34 const cmd = new Deno.Command("sudo", {
35 args: [
36 "sh",
37 "-c",
38 `mkdir -p /etc/qemu && echo "allow ${bridgeName}" >> ${bridgeConfPath}`,
39 ],
40 stdin: "inherit",
41 stdout: "inherit",
42 stderr: "inherit",
43 });
44 const status = await cmd.spawn().status;
45
46 if (!status.success) {
47 console.error(
48 chalk.redBright(
49 `Failed to add QEMU bridge configuration for ${bridgeName}.`,
50 ),
51 );
52 Deno.exit(status.code);
53 }
54
55 console.log(
56 chalk.greenBright(
57 `QEMU bridge configuration for ${bridgeName} added successfully.`,
58 ),
59 );
60 },
61 catch: (error) => new BridgeSetupError({ cause: error }),
62 });
63
64export const createBridgeNetworkIfNeeded = (
65 bridgeName: string,
66) =>
67 Effect.tryPromise({
68 try: async () => {
69 const bridgeExistsCmd = new Deno.Command("ip", {
70 args: ["link", "show", bridgeName],
71 stdout: "null",
72 stderr: "null",
73 });
74
75 const bridgeExistsStatus = await bridgeExistsCmd.spawn().status;
76 if (bridgeExistsStatus.success) {
77 console.log(
78 chalk.greenBright(`Network bridge ${bridgeName} already exists.`),
79 );
80 await setupQemuBridge(bridgeName);
81 return;
82 }
83
84 console.log(chalk.blueBright(`Creating network bridge ${bridgeName}...`));
85 const createBridgeCmd = new Deno.Command("sudo", {
86 args: ["ip", "link", "add", bridgeName, "type", "bridge"],
87 stdin: "inherit",
88 stdout: "inherit",
89 stderr: "inherit",
90 });
91
92 let status = await createBridgeCmd.spawn().status;
93 if (!status.success) {
94 console.error(
95 chalk.redBright(`Failed to create network bridge ${bridgeName}.`),
96 );
97 Deno.exit(status.code);
98 }
99
100 const bringUpBridgeCmd = new Deno.Command("sudo", {
101 args: ["ip", "link", "set", "dev", bridgeName, "up"],
102 stdin: "inherit",
103 stdout: "inherit",
104 stderr: "inherit",
105 });
106 status = await bringUpBridgeCmd.spawn().status;
107 if (!status.success) {
108 console.error(
109 chalk.redBright(`Failed to bring up network bridge ${bridgeName}.`),
110 );
111 Deno.exit(status.code);
112 }
113
114 console.log(
115 chalk.greenBright(`Network bridge ${bridgeName} created and up.`),
116 );
117
118 await setupQemuBridge(bridgeName);
119 },
120 catch: (error) => new NetworkError({ cause: error }),
121 });
122
123export const generateRandomMacAddress = () =>
124 Effect.sync(() => {
125 const hexDigits = "0123456789ABCDEF";
126 let macAddress = "52:54:00";
127
128 for (let i = 0; i < 3; i++) {
129 macAddress += ":";
130 for (let j = 0; j < 2; j++) {
131 macAddress += hexDigits.charAt(
132 Math.floor(Math.random() * hexDigits.length),
133 );
134 }
135 }
136
137 return macAddress;
138 });