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

Merge pull request #4 from tsirysndr/feat/gentoo

feat: add support for gentoo linux distribution

authored by tsiry-sandratraina.com and committed by

GitHub bdade28e f4a4d633

+120 -2
+20
main.ts
··· 32 32 import * as volumes from "./src/subcommands/volume.ts"; 33 33 import { 34 34 constructFedoraImageURL, 35 + constructGentooImageURL, 35 36 constructNixOSImageURL, 36 37 createDriveImageIfNeeded, 37 38 downloadIso, ··· 225 226 isoPath = yield* downloadIso(fedoraImageURL, options); 226 227 } else { 227 228 isoPath = basename(fedoraImageURL); 229 + } 230 + } 231 + 232 + const gentooImageURL = yield* pipe( 233 + constructGentooImageURL(input), 234 + Effect.catchAll(() => Effect.succeed(null)), 235 + ); 236 + 237 + if (gentooImageURL) { 238 + const cached = yield* pipe( 239 + basename(gentooImageURL), 240 + fileExists, 241 + Effect.flatMap(() => Effect.succeed(true)), 242 + Effect.catchAll(() => Effect.succeed(false)), 243 + ); 244 + if (!cached) { 245 + isoPath = yield* downloadIso(gentooImageURL, options); 246 + } else { 247 + isoPath = basename(gentooImageURL); 228 248 } 229 249 } 230 250 }
+4
src/constants.ts
··· 20 20 21 21 export const FEDORA_IMG_URL: string = 22 22 `https://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/${Deno.build.arch}/images/Fedora-Server-Guest-Generic-43-1.6.${Deno.build.arch}.qcow2`; 23 + 24 + export const GENTOO_IMG_URL: string = Deno.build.arch === "aarch64" 25 + ? "https://distfiles.gentoo.org/releases/arm64/autobuilds/20251116T233105Z/di-arm64-console-20251116T233105Z.qcow2" 26 + : "https://distfiles.gentoo.org/releases/amd64/autobuilds/20251116T161545Z/di-amd64-console-20251116T161545Z.qcow2";
+49
src/utils.ts
··· 9 9 FEDORA_COREOS_DEFAULT_VERSION, 10 10 FEDORA_COREOS_IMG_URL, 11 11 FEDORA_IMG_URL, 12 + GENTOO_IMG_URL, 12 13 LOGS_DIR, 13 14 NIXOS_DEFAULT_VERSION, 14 15 NIXOS_ISO_URL, ··· 339 340 return []; 340 341 }); 341 342 343 + export const setupGentooArgs = (imagePath?: string | null) => 344 + Effect.sync(() => { 345 + if ( 346 + imagePath && 347 + imagePath.endsWith(".qcow2") && 348 + imagePath.startsWith( 349 + `di-${Deno.build.arch === "aarch64" ? "arm64" : "amd64"}-console-`, 350 + ) 351 + ) { 352 + return ["-drive", `file=${imagePath},format=qcow2,if=virtio`]; 353 + } 354 + 355 + return []; 356 + }); 357 + 342 358 export const runQemu = (isoPath: string | null, options: Options) => 343 359 Effect.gen(function* () { 344 360 const macAddress = yield* generateRandomMacAddress(); ··· 350 366 const firmwareFiles = yield* setupFirmwareFilesIfNeeded(); 351 367 let coreosArgs: string[] = yield* setupCoreOSArgs(isoPath || options.image); 352 368 let fedoraArgs: string[] = yield* setupFedoraArgs(isoPath || options.image); 369 + let gentooArgs: string[] = yield* setupGentooArgs(isoPath || options.image); 353 370 354 371 if (coreosArgs.length > 0 && !isoPath) { 355 372 coreosArgs = coreosArgs.slice(2); ··· 357 374 358 375 if (fedoraArgs.length > 0 && !isoPath) { 359 376 fedoraArgs = []; 377 + } 378 + 379 + if (gentooArgs.length > 0 && !isoPath) { 380 + gentooArgs = []; 360 381 } 361 382 362 383 const qemuArgs = [ ··· 387 408 ...firmwareFiles, 388 409 ...coreosArgs, 389 410 ...fedoraArgs, 411 + ...gentooArgs, 390 412 ..._.compact( 391 413 options.image && [ 392 414 "-drive", ··· 704 726 }), 705 727 ); 706 728 }; 729 + 730 + export const constructGentooImageURL = ( 731 + image: string, 732 + ): Effect.Effect<string, InvalidImageNameError, never> => { 733 + // detect with regex if image matches genroo pattern: gentoo-20251116T161545Z or gentoo 734 + const gentooRegex = /^(gentoo)(-(\d{8}T\d{6}Z))?$/; 735 + const match = image.match(gentooRegex); 736 + if (match?.[3]) { 737 + return Effect.succeed( 738 + GENTOO_IMG_URL.replaceAll("20251116T161545Z", match[3]).replaceAll( 739 + "20251116T233105Z", 740 + match[3], 741 + ), 742 + ); 743 + } 744 + 745 + if (match) { 746 + return Effect.succeed(GENTOO_IMG_URL); 747 + } 748 + 749 + return Effect.fail( 750 + new InvalidImageNameError({ 751 + image, 752 + cause: "Image name does not match Gentoo naming conventions.", 753 + }), 754 + ); 755 + };
+47 -2
src/utils_test.ts
··· 1 1 import { assertEquals } from "@std/assert"; 2 2 import { Effect, pipe } from "effect"; 3 - import { FEDORA_COREOS_IMG_URL, NIXOS_ISO_URL } from "./constants.ts"; 4 - import { constructCoreOSImageURL, constructNixOSImageURL } from "./utils.ts"; 3 + import { 4 + FEDORA_COREOS_IMG_URL, 5 + GENTOO_IMG_URL, 6 + NIXOS_ISO_URL, 7 + } from "./constants.ts"; 8 + import { 9 + constructCoreOSImageURL, 10 + constructGentooImageURL, 11 + constructNixOSImageURL, 12 + } from "./utils.ts"; 5 13 6 14 Deno.test("Test Default Fedora CoreOS Image URL", () => { 7 15 const url = Effect.runSync( ··· 86 94 87 95 assertEquals(url, null); 88 96 }); 97 + 98 + Deno.test("Test valid Gentoo Image Name", () => { 99 + const url = Effect.runSync( 100 + pipe( 101 + constructGentooImageURL("gentoo-20251116T161545Z"), 102 + Effect.catchAll((_error) => Effect.succeed(null as string | null)), 103 + ), 104 + ); 105 + 106 + const arch = Deno.build.arch === "aarch64" ? "arm64" : "amd64"; 107 + assertEquals( 108 + url, 109 + `https://distfiles.gentoo.org/releases/${arch}/autobuilds/20251116T161545Z/di-${arch}-console-20251116T161545Z.qcow2`, 110 + ); 111 + }); 112 + 113 + Deno.test("Test valid Gentoo Image Name", () => { 114 + const url = Effect.runSync( 115 + pipe( 116 + constructGentooImageURL("gentoo"), 117 + Effect.catchAll((_error) => Effect.succeed(null as string | null)), 118 + ), 119 + ); 120 + 121 + assertEquals(url, GENTOO_IMG_URL); 122 + }); 123 + 124 + Deno.test("Test invalid Gentoo Image Name", () => { 125 + const url = Effect.runSync( 126 + pipe( 127 + constructGentooImageURL("gentoo-latest"), 128 + Effect.catchAll((_error) => Effect.succeed(null as string | null)), 129 + ), 130 + ); 131 + 132 + assertEquals(url, null); 133 + });