A simple, zero-configuration script to quickly boot FreeBSD ISO images using QEMU

feat: add VM running check and error handling for starting VMs

+33 -2
+2
src/api/machines.ts
··· 13 13 import { 14 14 buildQemuArgs, 15 15 createLogsDir, 16 + failIfVMRunning, 16 17 setupFirmware, 17 18 startDetachedQemu, 18 19 } from "../subcommands/start.ts"; ··· 55 56 ) => Effect.all([findVm(id), Effect.succeed(startRequest)])), 56 57 Effect.flatMap(([vm, startRequest]) => 57 58 Effect.gen(function* () { 59 + yield* failIfVMRunning(vm); 58 60 const firmwareArgs = yield* setupFirmware(); 59 61 const qemuArgs = yield* buildQemuArgs({ 60 62 ...vm,
+12
src/api/utils.ts
··· 5 5 StopCommandError, 6 6 VmNotFoundError, 7 7 } from "../subcommands/stop.ts"; 8 + import { VmAlreadyRunningError } from "../subcommands/start.ts"; 8 9 import { MachineParamsSchema } from "../types.ts"; 9 10 10 11 export const parseQueryParams = (c: Context) => Effect.succeed(c.req.query()); ··· 25 26 | StopCommandError 26 27 | CommandError 27 28 | ParseRequestError 29 + | VmAlreadyRunningError 28 30 | Error, 29 31 c: Context, 30 32 ) => ··· 51 53 { 52 54 message: error.message || "Failed to parse request body", 53 55 code: "PARSE_BODY_ERROR", 56 + }, 57 + 400, 58 + ); 59 + } 60 + 61 + if (error instanceof VmAlreadyRunningError) { 62 + return c.json( 63 + { 64 + message: `VM ${error.name} is already running`, 65 + code: "VM_ALREADY_RUNNING", 54 66 }, 55 67 400, 56 68 );
+19 -2
src/subcommands/start.ts
··· 8 8 import { setupFirmwareFilesIfNeeded, setupNATNetworkArgs } from "../utils.ts"; 9 9 import { createVolume, getVolume } from "../volumes.ts"; 10 10 11 - class VmNotFoundError extends Data.TaggedError("VmNotFoundError")<{ 11 + export class VmNotFoundError extends Data.TaggedError("VmNotFoundError")<{ 12 12 name: string; 13 13 }> {} 14 14 15 - class CommandError extends Data.TaggedError("CommandError")<{ 15 + export class VmAlreadyRunningError 16 + extends Data.TaggedError("VmAlreadyRunningError")<{ 17 + name: string; 18 + }> {} 19 + 20 + export class CommandError extends Data.TaggedError("CommandError")<{ 16 21 cause?: unknown; 17 22 }> {} 18 23 ··· 208 213 return [vm, newVolume]; 209 214 }); 210 215 216 + export const failIfVMRunning = (vm: VirtualMachine) => 217 + Effect.gen(function* () { 218 + if (vm.status === "RUNNING") { 219 + return yield* Effect.fail( 220 + new VmAlreadyRunningError({ name: vm.name }), 221 + ); 222 + } 223 + return vm; 224 + }); 225 + 211 226 const startDetachedEffect = (name: string) => 212 227 pipe( 213 228 findVm(name), 229 + Effect.flatMap(failIfVMRunning), 214 230 Effect.tap(logStarting), 215 231 Effect.flatMap(applyFlags), 216 232 Effect.flatMap(createVolumeIfNeeded), ··· 240 256 const startInteractiveEffect = (name: string) => 241 257 pipe( 242 258 findVm(name), 259 + Effect.flatMap(failIfVMRunning), 243 260 Effect.tap(logStarting), 244 261 Effect.flatMap(applyFlags), 245 262 Effect.flatMap(createVolumeIfNeeded),