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

Refactor code for improved readability and consistency by adjusting formatting and removing unnecessary line breaks in run.ts, utils.ts, and volumes.ts.

+49 -57
+22 -25
src/subcommands/run.ts
··· 7 import { type Options, runQemu, validateImage } from "../utils.ts"; 8 import { createVolume, getVolume } from "../volumes.ts"; 9 10 - const pullImageOnMissing = ( 11 - name: string, 12 - ): Effect.Effect<Image, Error, never> => 13 pipe( 14 getImage(name), 15 Effect.flatMap((img) => { ··· 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 32 const 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 47 const runImage = ([image, volume]: [Image, Volume?]) => ··· 56 options.image = volume.path; 57 options.install = true; 58 options.diskFormat = "qcow2"; 59 } 60 61 yield* runQemu(null, options); 62 }); 63 64 - export default async function ( 65 - image: string, 66 - ): Promise<void> { 67 await Effect.runPromise( 68 pipe( 69 Effect.promise(() => setupOrasBinary()), ··· 76 console.error(`Failed to run image: ${error.cause} ${image}`); 77 Deno.exit(1); 78 }) 79 - ), 80 - ), 81 ); 82 } 83 84 function 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,
··· 7 import { type Options, runQemu, validateImage } from "../utils.ts"; 8 import { createVolume, getVolume } from "../volumes.ts"; 9 10 + const pullImageOnMissing = (name: string): Effect.Effect<Image, Error, never> => 11 pipe( 12 getImage(name), 13 Effect.flatMap((img) => { ··· 19 pullImage(name), 20 Effect.flatMap(() => getImage(name)), 21 Effect.flatMap((pulledImg) => 22 + pulledImg 23 + ? Effect.succeed(pulledImg) 24 + : Effect.fail(new PullImageError({ cause: "Failed to pull image" })) 25 + ) 26 ); 27 + }) 28 ); 29 30 const createVolumeIfNeeded = ( 31 + image: Image 32 ): Effect.Effect<[Image, Volume?], Error, never> => 33 parseFlags(Deno.args).flags.volume 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 + }) 43 : Effect.succeed([image]); 44 45 const runImage = ([image, volume]: [Image, Volume?]) => ··· 54 options.image = volume.path; 55 options.install = true; 56 options.diskFormat = "qcow2"; 57 + options.volume = undefined; 58 } 59 60 yield* runQemu(null, options); 61 }); 62 63 + export default async function (image: string): Promise<void> { 64 await Effect.runPromise( 65 pipe( 66 Effect.promise(() => setupOrasBinary()), ··· 73 console.error(`Failed to run image: ${error.cause} ${image}`); 74 Deno.exit(1); 75 }) 76 + ) 77 + ) 78 ); 79 } 80 81 function mergeFlags(image: Image): Options { 82 const { flags } = parseFlags(Deno.args); 83 return { 84 + cpu: flags.cpu || flags.c ? flags.cpu || flags.c : "host", 85 + cpus: flags.cpus || flags.C ? flags.cpus || flags.C : 2, 86 + memory: flags.memory || flags.m ? flags.memory || flags.m : "2G", 87 image: image.path, 88 bridge: flags.bridge || flags.b, 89 portForward: flags.portForward || flags.p,
+5 -3
src/utils.ts
··· 333 : "qemu-system-x86_64"; 334 335 const firmwareFiles = yield* setupFirmwareFilesIfNeeded(); 336 - const coreosArgs: string[] = yield* setupCoreOSArgs( 337 - isoPath || options.image 338 - ); 339 340 const qemuArgs = [ 341 ..._.compact([options.bridge && qemu]),
··· 333 : "qemu-system-x86_64"; 334 335 const firmwareFiles = yield* setupFirmwareFilesIfNeeded(); 336 + let coreosArgs: string[] = yield* setupCoreOSArgs(isoPath || options.image); 337 + 338 + if (coreosArgs.length > 0) { 339 + coreosArgs = coreosArgs.slice(2); 340 + } 341 342 const qemuArgs = [ 343 ..._.compact([options.bridge && qemu]),
+22 -29
src/volumes.ts
··· 19 }); 20 21 export const getVolume = ( 22 - id: string, 23 ): Effect.Effect<Volume | undefined, VolumeError, never> => 24 Effect.tryPromise({ 25 try: () => ··· 27 .selectFrom("volumes") 28 .selectAll() 29 .where((eb) => 30 - eb.or([ 31 - eb("name", "=", id), 32 - eb("id", "=", id), 33 - eb("path", "=", id), 34 - ]) 35 ) 36 .executeTakeFirst(), 37 catch: (error) => ··· 41 }); 42 43 export const saveVolume = ( 44 - volume: Volume, 45 ): Effect.Effect<InsertResult[], VolumeError, never> => 46 Effect.tryPromise({ 47 - try: () => 48 - ctx.db.insertInto("volumes") 49 - .values(volume) 50 - .execute(), 51 catch: (error) => 52 new VolumeError({ 53 message: error instanceof Error ? error.message : String(error), ··· 55 }); 56 57 export const deleteVolume = ( 58 - id: string, 59 ): Effect.Effect<DeleteResult[], VolumeError, never> => 60 Effect.tryPromise({ 61 try: () => 62 - ctx.db.deleteFrom("volumes").where((eb) => 63 - eb.or([ 64 - eb("name", "=", id), 65 - eb("id", "=", id), 66 - ]) 67 - ).execute(), 68 catch: (error) => 69 new VolumeError({ 70 message: error instanceof Error ? error.message : String(error), ··· 74 export const createVolume = ( 75 name: string, 76 baseImage: Image, 77 - size?: string, 78 ): Effect.Effect<Volume, VolumeError, never> => 79 Effect.tryPromise({ 80 try: async () => { ··· 86 args: [ 87 "create", 88 "-F", 89 - "raw", 90 "-f", 91 "qcow2", 92 "-b", ··· 96 ], 97 stdout: "inherit", 98 stderr: "inherit", 99 - }) 100 - .spawn(); 101 const status = await qemu.status; 102 if (!status.success) { 103 throw new Error( 104 - `Failed to create volume: qemu-img exited with code ${status.code}`, 105 ); 106 } 107 } 108 109 - ctx.db.insertInto("volumes").values({ 110 - id: createId(), 111 - name, 112 - path, 113 - baseImageId: baseImage.id, 114 - }).execute(); 115 const volume = await ctx.db 116 .selectFrom("volumes") 117 .selectAll()
··· 19 }); 20 21 export const getVolume = ( 22 + id: string 23 ): Effect.Effect<Volume | undefined, VolumeError, never> => 24 Effect.tryPromise({ 25 try: () => ··· 27 .selectFrom("volumes") 28 .selectAll() 29 .where((eb) => 30 + eb.or([eb("name", "=", id), eb("id", "=", id), eb("path", "=", id)]) 31 ) 32 .executeTakeFirst(), 33 catch: (error) => ··· 37 }); 38 39 export const saveVolume = ( 40 + volume: Volume 41 ): Effect.Effect<InsertResult[], VolumeError, never> => 42 Effect.tryPromise({ 43 + try: () => ctx.db.insertInto("volumes").values(volume).execute(), 44 catch: (error) => 45 new VolumeError({ 46 message: error instanceof Error ? error.message : String(error), ··· 48 }); 49 50 export const deleteVolume = ( 51 + id: string 52 ): Effect.Effect<DeleteResult[], VolumeError, never> => 53 Effect.tryPromise({ 54 try: () => 55 + ctx.db 56 + .deleteFrom("volumes") 57 + .where((eb) => eb.or([eb("name", "=", id), eb("id", "=", id)])) 58 + .execute(), 59 catch: (error) => 60 new VolumeError({ 61 message: error instanceof Error ? error.message : String(error), ··· 65 export const createVolume = ( 66 name: string, 67 baseImage: Image, 68 + size?: string 69 ): Effect.Effect<Volume, VolumeError, never> => 70 Effect.tryPromise({ 71 try: async () => { ··· 77 args: [ 78 "create", 79 "-F", 80 + baseImage.path.endsWith(".qcow2") ? "qcow2" : "raw", 81 "-f", 82 "qcow2", 83 "-b", ··· 87 ], 88 stdout: "inherit", 89 stderr: "inherit", 90 + }).spawn(); 91 const status = await qemu.status; 92 if (!status.success) { 93 throw new Error( 94 + `Failed to create volume: qemu-img exited with code ${status.code}` 95 ); 96 } 97 } 98 99 + ctx.db 100 + .insertInto("volumes") 101 + .values({ 102 + id: createId(), 103 + name, 104 + path, 105 + baseImageId: baseImage.id, 106 + }) 107 + .execute(); 108 const volume = await ctx.db 109 .selectFrom("volumes") 110 .selectAll()