A convenient CLI tool to quickly spin up DragonflyBSD virtual machines using QEMU with sensible defaults.
at main 125 lines 3.6 kB view raw
1import { parseFlags } from "@cliffy/flags"; 2import _ from "@es-toolkit/es-toolkit/compat"; 3import * as toml from "@std/toml"; 4import z from "@zod/zod"; 5import { Data, Effect } from "effect"; 6import { 7 constructDownloadUrl, 8 DEFAULT_VERSION, 9 type Options, 10} from "./utils.ts"; 11 12export const VmConfigSchema = z.object({ 13 vm: z.object({ 14 iso: z.string(), 15 output: z.string(), 16 cpu: z.string(), 17 cpus: z.number(), 18 memory: z.string(), 19 image: z.string(), 20 disk_format: z.enum(["qcow2", "raw"]), 21 size: z.string(), 22 }).partial(), 23 network: z.object({ 24 bridge: z.string(), 25 port_forward: z.string(), 26 }).partial(), 27 options: z.object({ 28 detach: z.boolean(), 29 }).partial(), 30}); 31 32export type VmConfig = z.infer<typeof VmConfigSchema>; 33 34class VmConfigError extends Data.TaggedError("VmConfigError")<{ 35 cause?: string; 36}> {} 37 38export const initVmFile = ( 39 path: string, 40): Effect.Effect<void, VmConfigError, never> => 41 Effect.tryPromise({ 42 try: async () => { 43 const defaultConfig: VmConfig = { 44 vm: { 45 iso: constructDownloadUrl(DEFAULT_VERSION), 46 cpu: Deno.build.os === "darwin" && Deno.build.arch === "aarch64" 47 ? "max" 48 : "host", 49 cpus: 2, 50 memory: "2G", 51 }, 52 network: { 53 port_forward: "2222:22", 54 }, 55 options: { 56 detach: false, 57 }, 58 }; 59 const tomlString = toml.stringify(defaultConfig); 60 await Deno.writeTextFile(path, tomlString); 61 }, 62 catch: (error) => new VmConfigError({ cause: String(error) }), 63 }); 64 65export const parseVmFile = ( 66 path: string, 67): Effect.Effect<VmConfig, VmConfigError, never> => 68 Effect.tryPromise({ 69 try: async () => { 70 const fileContent = await Deno.readTextFile(path); 71 const parsedToml = toml.parse(fileContent); 72 return VmConfigSchema.parse(parsedToml); 73 }, 74 catch: (error) => new VmConfigError({ cause: String(error) }), 75 }); 76 77export const mergeConfig = ( 78 config: VmConfig | null, 79 options: Options, 80): Effect.Effect<Options, never, never> => { 81 const { flags } = parseFlags(Deno.args); 82 const defaultConfig: VmConfig = { 83 vm: { 84 iso: _.get(config, "vm.iso"), 85 cpu: _.get( 86 config, 87 "vm.cpu", 88 Deno.build.os === "darwin" && Deno.build.arch === "aarch64" 89 ? "max" 90 : "host", 91 ), 92 cpus: _.get(config, "vm.cpus", 2), 93 memory: _.get(config, "vm.memory", "2G"), 94 image: _.get(config, "vm.image", options.image), 95 disk_format: _.get(config, "vm.disk_format", "raw"), 96 size: _.get(config, "vm.size", "20G"), 97 }, 98 network: { 99 bridge: _.get(config, "network.bridge"), 100 port_forward: _.get(config, "network.port_forward", "2222:22"), 101 }, 102 options: { 103 detach: _.get(config, "options.detach", false), 104 }, 105 }; 106 return Effect.succeed({ 107 memory: _.get(flags, "memory", defaultConfig.vm.memory!) as string, 108 cpus: _.get(flags, "cpus", defaultConfig.vm.cpus!) as number, 109 cpu: _.get(flags, "cpu", defaultConfig.vm.cpu!) as string, 110 diskFormat: _.get( 111 flags, 112 "diskFormat", 113 defaultConfig.vm.disk_format!, 114 ) as string, 115 portForward: _.get( 116 flags, 117 "portForward", 118 defaultConfig.network.port_forward!, 119 ) as string, 120 image: _.get(flags, "image", defaultConfig.vm.image!) as string, 121 bridge: _.get(flags, "bridge", defaultConfig.network.bridge!) as string, 122 size: _.get(flags, "size", defaultConfig.vm.size!) as string, 123 install: _.get(flags, "install", false) as boolean, 124 }); 125};