A Docker-like CLI and HTTP API for managing headless VMs

Refactor code for improved readability by adjusting formatting and removing unnecessary line breaks in run.ts and start.ts.

+67 -61
+13 -13
src/subcommands/run.ts
··· 22 22 pulledImg 23 23 ? Effect.succeed(pulledImg) 24 24 : Effect.fail(new PullImageError({ cause: "Failed to pull image" })) 25 - ), 25 + ) 26 26 ); 27 - }), 27 + }) 28 28 ); 29 29 30 30 const createVolumeIfNeeded = ( 31 - image: Image, 31 + image: Image 32 32 ): Effect.Effect<[Image, Volume?], Error, never> => 33 33 parseFlags(Deno.args).flags.volume 34 34 ? Effect.gen(function* () { 35 - const volumeName = parseFlags(Deno.args).flags.volume as string; 36 - const volume = yield* getVolume(volumeName); 37 - if (volume) { 38 - return [image, volume]; 39 - } 40 - const newVolume = yield* createVolume(volumeName, image); 41 - return [image, newVolume]; 42 - }) 35 + const volumeName = parseFlags(Deno.args).flags.volume as string; 36 + const volume = yield* getVolume(volumeName); 37 + if (volume) { 38 + return [image, volume]; 39 + } 40 + const newVolume = yield* createVolume(volumeName, image); 41 + return [image, newVolume]; 42 + }) 43 43 : Effect.succeed([image]); 44 44 45 45 const runImage = ([image, volume]: [Image, Volume?]) => ··· 73 73 console.error(`Failed to run image: ${error.cause} ${image}`); 74 74 Deno.exit(1); 75 75 }) 76 - ), 77 - ), 76 + ) 77 + ) 78 78 ); 79 79 } 80 80
+54 -48
src/subcommands/start.ts
··· 17 17 }> {} 18 18 19 19 export class VmAlreadyRunningError extends Data.TaggedError( 20 - "VmAlreadyRunningError", 20 + "VmAlreadyRunningError" 21 21 )<{ 22 22 name: string; 23 23 }> {} ··· 31 31 getInstanceState(name), 32 32 Effect.flatMap((vm) => 33 33 vm ? Effect.succeed(vm) : Effect.fail(new VmNotFoundError({ name })) 34 - ), 34 + ) 35 35 ); 36 36 37 37 const logStarting = (vm: VirtualMachine) => ··· 44 44 export const setupFirmware = () => setupFirmwareFilesIfNeeded(); 45 45 46 46 export const buildQemuArgs = (vm: VirtualMachine, firmwareArgs: string[]) => { 47 - const qemu = Deno.build.arch === "aarch64" 48 - ? "qemu-system-aarch64" 49 - : "qemu-system-x86_64"; 47 + const qemu = 48 + Deno.build.arch === "aarch64" 49 + ? "qemu-system-aarch64" 50 + : "qemu-system-x86_64"; 50 51 51 52 let coreosArgs: string[] = Effect.runSync(setupCoreOSArgs(vm.drivePath)); 52 53 ··· 83 84 vm.drivePath && [ 84 85 "-drive", 85 86 `file=${vm.drivePath},format=${vm.diskFormat},if=virtio`, 86 - ], 87 + ] 87 88 ), 88 89 ...coreosArgs, 90 + ...(vm.volume ? [] : ["-snapshot"]), 89 91 ]); 90 92 }; 91 93 ··· 98 100 export const startDetachedQemu = ( 99 101 name: string, 100 102 vm: VirtualMachine, 101 - qemuArgs: string[], 103 + qemuArgs: string[] 102 104 ) => { 103 - const qemu = Deno.build.arch === "aarch64" 104 - ? "qemu-system-aarch64" 105 - : "qemu-system-x86_64"; 105 + const qemu = 106 + Deno.build.arch === "aarch64" 107 + ? "qemu-system-aarch64" 108 + : "qemu-system-x86_64"; 106 109 107 110 const logPath = `${LOGS_DIR}/${vm.name}.log`; 108 111 109 112 const fullCommand = vm.bridge 110 - ? `sudo ${qemu} ${ 111 - qemuArgs 113 + ? `sudo ${qemu} ${qemuArgs 112 114 .slice(1) 113 - .join(" ") 114 - } >> "${logPath}" 2>&1 & echo $!` 115 + .join(" ")} >> "${logPath}" 2>&1 & echo $!` 115 116 : `${qemu} ${qemuArgs.join(" ")} >> "${logPath}" 2>&1 & echo $!`; 116 117 117 118 return Effect.tryPromise({ ··· 142 143 Effect.flatMap(({ qemuPid, logPath }) => 143 144 pipe( 144 145 updateInstanceState(name, "RUNNING", qemuPid), 145 - Effect.map(() => ({ vm, qemuPid, logPath })), 146 + Effect.map(() => ({ vm, qemuPid, logPath })) 146 147 ) 147 - ), 148 + ) 148 149 ); 149 150 }; 150 151 ··· 159 160 }) => 160 161 Effect.sync(() => { 161 162 console.log( 162 - `Virtual machine ${vm.name} started in background (PID: ${qemuPid})`, 163 + `Virtual machine ${vm.name} started in background (PID: ${qemuPid})` 163 164 ); 164 165 console.log(`Logs will be written to: ${logPath}`); 165 166 }); ··· 167 168 const startInteractiveQemu = ( 168 169 name: string, 169 170 vm: VirtualMachine, 170 - qemuArgs: string[], 171 + qemuArgs: string[] 171 172 ) => { 172 - const qemu = Deno.build.arch === "aarch64" 173 - ? "qemu-system-aarch64" 174 - : "qemu-system-x86_64"; 173 + const qemu = 174 + Deno.build.arch === "aarch64" 175 + ? "qemu-system-aarch64" 176 + : "qemu-system-x86_64"; 175 177 176 178 return Effect.tryPromise({ 177 179 try: async () => { ··· 207 209 }); 208 210 209 211 export const createVolumeIfNeeded = ( 210 - vm: VirtualMachine, 212 + vm: VirtualMachine 211 213 ): Effect.Effect<[VirtualMachine, Volume?], Error, never> => 212 214 Effect.gen(function* () { 213 215 const { flags } = parseFlags(Deno.args); 214 216 if (!flags.volume) { 217 + console.log("No volume flag provided, proceeding without volume."); 215 218 return [vm]; 216 219 } 217 220 const volume = yield* getVolume(flags.volume as string); ··· 221 224 222 225 if (!vm.drivePath) { 223 226 throw new Error( 224 - `Cannot create volume: Virtual machine ${vm.name} has no drivePath defined.`, 227 + `Cannot create volume: Virtual machine ${vm.name} has no drivePath defined.` 225 228 ); 226 229 } 227 230 ··· 262 265 ...vm, 263 266 drivePath: volume ? volume.path : vm.drivePath, 264 267 diskFormat: volume ? "qcow2" : vm.diskFormat, 268 + volume: volume?.path, 265 269 }, 266 - firmwareArgs, 270 + firmwareArgs 267 271 ) 268 272 ), 269 273 Effect.flatMap((qemuArgs) => 270 274 pipe( 271 275 createLogsDir(), 272 - Effect.flatMap(() => startDetachedQemu(name, vm, qemuArgs)), 276 + Effect.flatMap(() => 277 + startDetachedQemu(name, { ...vm, volume: volume?.path }, qemuArgs) 278 + ), 273 279 Effect.tap(logDetachedSuccess), 274 - Effect.map(() => 0), // Exit code 0 280 + Effect.map(() => 0) // Exit code 0 275 281 ) 276 - ), 282 + ) 277 283 ) 278 284 ), 279 - Effect.catchAll(handleError), 285 + Effect.catchAll(handleError) 280 286 ); 281 287 282 288 const startInteractiveEffect = (name: string) => ··· 295 301 ...vm, 296 302 drivePath: volume ? volume.path : vm.drivePath, 297 303 diskFormat: volume ? "qcow2" : vm.diskFormat, 304 + volume: volume?.path, 298 305 }, 299 - firmwareArgs, 306 + firmwareArgs 300 307 ) 301 308 ), 302 - Effect.flatMap((qemuArgs) => startInteractiveQemu(name, vm, qemuArgs)), 303 - Effect.map((status) => (status.success ? 0 : status.code || 1)), 309 + Effect.flatMap((qemuArgs) => 310 + startInteractiveQemu(name, { ...vm, volume: volume?.path }, qemuArgs) 311 + ), 312 + Effect.map((status) => (status.success ? 0 : status.code || 1)) 304 313 ) 305 314 ), 306 - Effect.catchAll(handleError), 315 + Effect.catchAll(handleError) 307 316 ); 308 317 309 318 export default async function (name: string, detach: boolean = false) { 310 319 const exitCode = await Effect.runPromise( 311 - detach ? startDetachedEffect(name) : startInteractiveEffect(name), 320 + detach ? startDetachedEffect(name) : startInteractiveEffect(name) 312 321 ); 313 322 314 323 if (detach) { ··· 322 331 const { flags } = parseFlags(Deno.args); 323 332 return { 324 333 ...vm, 325 - memory: flags.memory || flags.m 326 - ? String(flags.memory || flags.m) 327 - : vm.memory, 334 + memory: 335 + flags.memory || flags.m ? String(flags.memory || flags.m) : vm.memory, 328 336 cpus: flags.cpus || flags.C ? Number(flags.cpus || flags.C) : vm.cpus, 329 337 cpu: flags.cpu || flags.c ? String(flags.cpu || flags.c) : vm.cpu, 330 338 diskFormat: flags.diskFormat ? String(flags.diskFormat) : vm.diskFormat, 331 - portForward: flags.portForward || flags.p 332 - ? String(flags.portForward || flags.p) 333 - : vm.portForward, 334 - drivePath: flags.image || flags.i 335 - ? String(flags.image || flags.i) 336 - : vm.drivePath, 337 - bridge: flags.bridge || flags.b 338 - ? String(flags.bridge || flags.b) 339 - : vm.bridge, 340 - diskSize: flags.size || flags.s 341 - ? String(flags.size || flags.s) 342 - : vm.diskSize, 339 + portForward: 340 + flags.portForward || flags.p 341 + ? String(flags.portForward || flags.p) 342 + : vm.portForward, 343 + drivePath: 344 + flags.image || flags.i ? String(flags.image || flags.i) : vm.drivePath, 345 + bridge: 346 + flags.bridge || flags.b ? String(flags.bridge || flags.b) : vm.bridge, 347 + diskSize: 348 + flags.size || flags.s ? String(flags.size || flags.s) : vm.diskSize, 343 349 }; 344 350 }