A simple command-line tool to start NetBSD virtual machines using QEMU with sensible defaults.
at main 98 lines 3.0 kB view raw
1import { parseFlags } from "@cliffy/flags"; 2import { Effect, pipe } from "effect"; 3import type { Image, Volume } from "../db.ts"; 4import { getImage } from "../images.ts"; 5import { createBridgeNetworkIfNeeded } from "../network.ts"; 6import { pullImage, PullImageError, setupOrasBinary } from "../oras.ts"; 7import { type Options, runQemu, validateImage } from "../utils.ts"; 8import { createVolume, getVolume } from "../volumes.ts"; 9 10const pullImageOnMissing = ( 11 name: string, 12): Effect.Effect<Image, Error, never> => 13 pipe( 14 getImage(name), 15 Effect.flatMap((img) => { 16 if (img) { 17 return Effect.succeed(img); 18 } 19 console.log(`Image ${name} not found locally`); 20 return pipe( 21 pullImage(name), 22 Effect.flatMap(() => getImage(name)), 23 Effect.flatMap((pulledImg) => 24 pulledImg ? Effect.succeed(pulledImg) : Effect.fail( 25 new PullImageError({ cause: "Failed to pull image" }), 26 ) 27 ), 28 ); 29 }), 30 ); 31 32const createVolumeIfNeeded = ( 33 image: Image, 34): Effect.Effect<[Image, Volume?], Error, never> => 35 parseFlags(Deno.args).flags.volume 36 ? Effect.gen(function* () { 37 const volumeName = parseFlags(Deno.args).flags.volume as string; 38 const volume = yield* getVolume(volumeName); 39 if (volume) { 40 return [image, volume]; 41 } 42 const newVolume = yield* createVolume(volumeName, image); 43 return [image, newVolume]; 44 }) 45 : Effect.succeed([image]); 46 47const runImage = ([image, volume]: [Image, Volume?]) => 48 Effect.gen(function* () { 49 console.log(`Running image ${image.repository}...`); 50 const options = mergeFlags(image); 51 if (options.bridge) { 52 yield* createBridgeNetworkIfNeeded(options.bridge); 53 } 54 55 if (volume) { 56 options.image = volume.path; 57 options.install = true; 58 options.diskFormat = "qcow2"; 59 } 60 61 yield* runQemu(null, options); 62 }); 63 64export default async function ( 65 image: string, 66): Promise<void> { 67 await Effect.runPromise( 68 pipe( 69 Effect.promise(() => setupOrasBinary()), 70 Effect.tap(() => validateImage(image)), 71 Effect.flatMap(() => pullImageOnMissing(image)), 72 Effect.flatMap(createVolumeIfNeeded), 73 Effect.flatMap(runImage), 74 Effect.catchAll((error) => 75 Effect.sync(() => { 76 console.error(`Failed to run image: ${error.cause} ${image}`); 77 Deno.exit(1); 78 }) 79 ), 80 ), 81 ); 82} 83 84function mergeFlags(image: Image): Options { 85 const { flags } = parseFlags(Deno.args); 86 return { 87 cpu: (flags.cpu || flags.c) ? (flags.cpu || flags.c) : "host", 88 cpus: (flags.cpus || flags.C) ? (flags.cpus || flags.C) : 2, 89 memory: (flags.memory || flags.m) ? (flags.memory || flags.m) : "2G", 90 image: image.path, 91 bridge: flags.bridge || flags.b, 92 portForward: flags.portForward || flags.p, 93 detach: flags.detach || flags.d, 94 install: false, 95 diskFormat: image.format, 96 volume: flags.volume || flags.v, 97 }; 98}