A simple, powerful CLI tool to spin up OpenIndiana virtual machines with QEMU
at main 6.1 kB view raw
1#!/usr/bin/env -S deno run --allow-run --allow-read --allow-env 2 3import { Command } from "@cliffy/command"; 4import pkg from "./deno.json" with { type: "json" }; 5import { createBridgeNetworkIfNeeded } from "./src/network.ts"; 6import inspect from "./src/subcommands/inspect.ts"; 7import logs from "./src/subcommands/logs.ts"; 8import ps from "./src/subcommands/ps.ts"; 9import restart from "./src/subcommands/restart.ts"; 10import rm from "./src/subcommands/rm.ts"; 11import start from "./src/subcommands/start.ts"; 12import stop from "./src/subcommands/stop.ts"; 13import { 14 createDriveImageIfNeeded, 15 downloadIso, 16 emptyDiskImage, 17 handleInput, 18 type Options, 19 runQemu, 20} from "./src/utils.ts"; 21 22if (import.meta.main) { 23 await new Command() 24 .name("openindiana-up") 25 .version(pkg.version) 26 .description("Start a OpenIndiana virtual machine using QEMU") 27 .arguments( 28 "[path-or-url-to-iso-or-version:string]", 29 ) 30 .option("-o, --output <path:string>", "Output path for downloaded ISO") 31 .option("-c, --cpu <type:string>", "Type of CPU to emulate", { 32 default: "host", 33 }) 34 .option("-C, --cpus <number:number>", "Number of CPU cores", { 35 default: 2, 36 }) 37 .option("-m, --memory <size:string>", "Amount of memory for the VM", { 38 default: "2G", 39 }) 40 .option("-i, --image <path:string>", "Path to VM disk image") 41 .option( 42 "--disk-format <format:string>", 43 "Disk image format (e.g., qcow2, raw)", 44 { 45 default: "raw", 46 }, 47 ) 48 .option( 49 "--size <size:string>", 50 "Size of the VM disk image to create if it does not exist (e.g., 20G)", 51 { 52 default: "20G", 53 }, 54 ) 55 .option( 56 "-b, --bridge <name:string>", 57 "Name of the network bridge to use for networking (e.g., br0)", 58 ) 59 .option( 60 "-d, --detach", 61 "Run VM in the background and print VM name", 62 ) 63 .option( 64 "-p, --port-forward <mappings:string>", 65 "Port forwarding rules in the format hostPort:guestPort (comma-separated for multiple)", 66 ) 67 .example( 68 "Default usage", 69 "openindiana-up", 70 ) 71 .example( 72 "Specific version", 73 "openindiana-up 20251026", 74 ) 75 .example( 76 "Local ISO file", 77 "openindiana-up /path/to/openindiana.iso", 78 ) 79 .example( 80 "Download URL", 81 "openindiana-up https://dlc.openindiana.org/isos/hipster/20251026/OI-hipster-text-20251026.iso", 82 ) 83 .example( 84 "List running VMs", 85 "openindiana-up ps", 86 ) 87 .example( 88 "List all VMs", 89 "openindiana-up ps --all", 90 ) 91 .example( 92 "Start a VM", 93 "openindiana-up start my-vm", 94 ) 95 .example( 96 "Stop a VM", 97 "openindiana-up stop my-vm", 98 ) 99 .example( 100 "Inspect a VM", 101 "openindiana-up inspect my-vm", 102 ) 103 .example( 104 "Remove a VM", 105 "openindiana-up rm my-vm", 106 ) 107 .action(async (options: Options, input?: string) => { 108 const resolvedInput = handleInput(input); 109 let isoPath: string | null = resolvedInput; 110 111 if ( 112 resolvedInput.startsWith("https://") || 113 resolvedInput.startsWith("http://") 114 ) { 115 isoPath = await downloadIso(resolvedInput, options); 116 } 117 118 if (options.image) { 119 await createDriveImageIfNeeded(options); 120 } 121 122 if (!input && options.image && !await emptyDiskImage(options.image)) { 123 isoPath = null; 124 } 125 126 if (options.bridge) { 127 await createBridgeNetworkIfNeeded(options.bridge); 128 } 129 130 await runQemu(isoPath, options); 131 }) 132 .command("ps", "List all virtual machines") 133 .option("--all, -a", "Show all virtual machines, including stopped ones") 134 .action(async (options: { all?: unknown }) => { 135 await ps(Boolean(options.all)); 136 }) 137 .command("start", "Start a virtual machine") 138 .arguments("<vm-name:string>") 139 .option("-c, --cpu <type:string>", "Type of CPU to emulate", { 140 default: "host", 141 }) 142 .option("-C, --cpus <number:number>", "Number of CPU cores", { 143 default: 2, 144 }) 145 .option("-m, --memory <size:string>", "Amount of memory for the VM", { 146 default: "2G", 147 }) 148 .option("-i, --image <path:string>", "Path to VM disk image") 149 .option( 150 "--disk-format <format:string>", 151 "Disk image format (e.g., qcow2, raw)", 152 { 153 default: "raw", 154 }, 155 ) 156 .option( 157 "--size <size:string>", 158 "Size of the VM disk image to create if it doesn't exist (e.g., 20G)", 159 { 160 default: "20G", 161 }, 162 ) 163 .option( 164 "-b, --bridge <name:string>", 165 "Name of the network bridge to use for networking (e.g., br0)", 166 ) 167 .option( 168 "-d, --detach", 169 "Run VM in the background and print VM name", 170 ) 171 .option( 172 "-p, --port-forward <mappings:string>", 173 "Port forwarding rules in the format hostPort:guestPort (comma-separated for multiple)", 174 ) 175 .action(async (options: unknown, vmName: string) => { 176 await start(vmName, Boolean((options as { detach: boolean }).detach)); 177 }) 178 .command("stop", "Stop a virtual machine") 179 .arguments("<vm-name:string>") 180 .action(async (_options: unknown, vmName: string) => { 181 await stop(vmName); 182 }) 183 .command("inspect", "Inspect a virtual machine") 184 .arguments("<vm-name:string>") 185 .action(async (_options: unknown, vmName: string) => { 186 await inspect(vmName); 187 }) 188 .command("rm", "Remove a virtual machine") 189 .arguments("<vm-name:string>") 190 .action(async (_options: unknown, vmName: string) => { 191 await rm(vmName); 192 }) 193 .command("logs", "View logs of a virtual machine") 194 .option("--follow, -f", "Follow log output") 195 .arguments("<vm-name:string>") 196 .action(async (options: unknown, vmName: string) => { 197 await logs(vmName, Boolean((options as { follow: boolean }).follow)); 198 }) 199 .command("restart", "Restart a virtual machine") 200 .arguments("<vm-name:string>") 201 .action(async (_options: unknown, vmName: string) => { 202 await restart(vmName); 203 }) 204 .parse(Deno.args); 205}