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

Add NixOS image URL handling and tests for image URL construction

+77 -2
+10
main.ts
··· 31 31 import tag from "./src/subcommands/tag.ts"; 32 32 import * as volumes from "./src/subcommands/volume.ts"; 33 33 import { 34 + constructNixOSImageURL, 34 35 createDriveImageIfNeeded, 35 36 downloadIso, 36 37 emptyDiskImage, ··· 186 187 } else { 187 188 isoPath = basename(coreOSImageURL).replace(".xz", ""); 188 189 } 190 + } 191 + 192 + const nixOSIsoURL = yield* pipe( 193 + constructNixOSImageURL(input), 194 + Effect.catchAll(() => Effect.succeed(null)), 195 + ); 196 + 197 + if (nixOSIsoURL) { 198 + isoPath = yield* downloadIso(nixOSIsoURL, options); 189 199 } 190 200 } 191 201
+6
src/constants.ts
··· 5 5 export const CONFIG_FILE_NAME: string = "vmconfig.toml"; 6 6 export const IMAGE_DIR: string = `${CONFIG_DIR}/images`; 7 7 export const VOLUME_DIR: string = `${CONFIG_DIR}/volumes`; 8 + 8 9 export const UBUNTU_ISO_URL: string = Deno.build.arch === "aarch64" 9 10 ? "https://cdimage.ubuntu.com/releases/24.04/release/ubuntu-24.04.3-live-server-arm64.iso" 10 11 : "https://releases.ubuntu.com/24.04.3/ubuntu-24.04.3-live-server-amd64.iso"; 12 + 11 13 export const FEDORA_COREOS_DEFAULT_VERSION: string = "43.20251024.3.0"; 12 14 export const FEDORA_COREOS_IMG_URL: string = 13 15 `https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/${FEDORA_COREOS_DEFAULT_VERSION}/${Deno.build.arch}/fedora-coreos-${FEDORA_COREOS_DEFAULT_VERSION}-qemu.${Deno.build.arch}.qcow2.xz`; 16 + 17 + export const NIXOS_DEFAULT_VERSION: string = "25.05"; 18 + export const NIXOS_ISO_URL: string = 19 + `https://channels.nixos.org/nixos-${NIXOS_DEFAULT_VERSION}/latest-nixos-minimal-${Deno.build.arch}-linux.iso`;
+23
src/utils.ts
··· 9 9 FEDORA_COREOS_DEFAULT_VERSION, 10 10 FEDORA_COREOS_IMG_URL, 11 11 LOGS_DIR, 12 + NIXOS_DEFAULT_VERSION, 13 + NIXOS_ISO_URL, 12 14 } from "./constants.ts"; 13 15 import type { Image } from "./db.ts"; 14 16 import { generateRandomMacAddress } from "./network.ts"; ··· 643 645 }, 644 646 catch: (error) => new LogCommandError({ cause: error }), 645 647 }); 648 + 649 + export const constructNixOSImageURL = ( 650 + image: string, 651 + ): Effect.Effect<string, InvalidImageNameError, never> => { 652 + // detect with regex if image matches NixOS pattern: nixos or nixos-<version> 653 + const nixosRegex = /^(nixos)(-(\d+\.\d+))?$/; 654 + const match = image.match(nixosRegex); 655 + if (match) { 656 + const version = match[3] || NIXOS_DEFAULT_VERSION; 657 + return Effect.succeed( 658 + NIXOS_ISO_URL.replaceAll(NIXOS_DEFAULT_VERSION, version), 659 + ); 660 + } 661 + 662 + return Effect.fail( 663 + new InvalidImageNameError({ 664 + image, 665 + cause: "Image name does not match NixOS naming conventions.", 666 + }), 667 + ); 668 + };
+38 -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 } from "./constants.ts"; 4 - import { constructCoreOSImageURL } from "./utils.ts"; 3 + import { FEDORA_COREOS_IMG_URL, NIXOS_ISO_URL } from "./constants.ts"; 4 + import { constructCoreOSImageURL, constructNixOSImageURL } from "./utils.ts"; 5 5 6 6 Deno.test("Test Default Fedora CoreOS Image URL", () => { 7 7 const url = Effect.runSync( ··· 50 50 51 51 assertEquals(url, null); 52 52 }); 53 + 54 + Deno.test("Test Default NixOS Image URL", () => { 55 + const url = Effect.runSync( 56 + pipe( 57 + constructNixOSImageURL("nixos"), 58 + Effect.catchAll((_error) => Effect.succeed(null as string | null)), 59 + ), 60 + ); 61 + 62 + assertEquals(url, NIXOS_ISO_URL); 63 + }); 64 + 65 + Deno.test("Test Specific NixOS Version", () => { 66 + const url = Effect.runSync( 67 + pipe( 68 + constructNixOSImageURL("nixos-24.05"), 69 + Effect.catchAll((_error) => Effect.succeed(null as string | null)), 70 + ), 71 + ); 72 + 73 + assertEquals( 74 + url, 75 + `https://channels.nixos.org/nixos-24.05/latest-nixos-minimal-${Deno.build.arch}-linux.iso`, 76 + ); 77 + }); 78 + 79 + Deno.test("Test invalid NixOS Image Name", () => { 80 + const url = Effect.runSync( 81 + pipe( 82 + constructNixOSImageURL("nixos-latest"), 83 + Effect.catchAll((_error) => Effect.succeed(null as string | null)), 84 + ), 85 + ); 86 + 87 + assertEquals(url, null); 88 + });