A simple, powerful CLI tool to spin up OpenIndiana virtual machines with QEMU
at main 119 lines 3.4 kB view raw
1import { parseFlags } from "@cliffy/flags"; 2import _ from "@es-toolkit/es-toolkit/compat"; 3import { LOGS_DIR } from "../constants.ts"; 4import type { VirtualMachine } from "../db.ts"; 5import { getInstanceState, updateInstanceState } from "../state.ts"; 6 7export default async function (name: string, detach: boolean = false) { 8 let vm = await getInstanceState(name); 9 if (!vm) { 10 console.error( 11 `Virtual machine with name or ID ${name} not found.`, 12 ); 13 Deno.exit(1); 14 } 15 16 console.log(`Starting virtual machine ${vm.name} (ID: ${vm.id})...`); 17 18 vm = mergeFlags(vm); 19 20 const qemuArgs = [ 21 ..._.compact([vm.bridge && "qemu-system-x86_64"]), 22 ...Deno.build.os === "linux" ? ["-enable-kvm"] : [], 23 "-cpu", 24 vm.cpu, 25 "-m", 26 vm.memory, 27 "-smp", 28 vm.cpus.toString(), 29 ..._.compact([vm.isoPath && "-cdrom", vm.isoPath]), 30 "-netdev", 31 vm.bridge 32 ? `bridge,id=net0,br=${vm.bridge}` 33 : "user,id=net0,hostfwd=tcp::2222-:22", 34 "-device", 35 `e1000,netdev=net0,mac=${vm.macAddress}`, 36 "-device", 37 "ahci,id=ahci0", 38 "-nographic", 39 "-monitor", 40 "none", 41 "-chardev", 42 "stdio,id=con0,signal=off", 43 "-serial", 44 "chardev:con0", 45 ..._.compact( 46 vm.drivePath && [ 47 "-drive", 48 `file=${vm.drivePath},format=${vm.diskFormat},if=none,id=disk0`, 49 "-device", 50 "ide-hd,drive=disk0,bus=ahci0.0", 51 ], 52 ), 53 ]; 54 55 if (detach) { 56 await Deno.mkdir(LOGS_DIR, { recursive: true }); 57 const logPath = `${LOGS_DIR}/${vm.name}.log`; 58 59 const fullCommand = vm.bridge 60 ? `sudo qemu-system-x86_64 ${ 61 qemuArgs.slice(1).join(" ") 62 } >> "${logPath}" 2>&1 & echo $!` 63 : `qemu-system-x86_64 ${ 64 qemuArgs.join(" ") 65 } >> "${logPath}" 2>&1 & echo $!`; 66 67 const cmd = new Deno.Command("sh", { 68 args: ["-c", fullCommand], 69 stdin: "null", 70 stdout: "piped", 71 }); 72 73 const { stdout } = await cmd.spawn().output(); 74 const qemuPid = parseInt(new TextDecoder().decode(stdout).trim(), 10); 75 76 await updateInstanceState(name, "RUNNING", qemuPid); 77 78 console.log( 79 `Virtual machine ${vm.name} started in background (PID: ${qemuPid})`, 80 ); 81 console.log(`Logs will be written to: ${logPath}`); 82 83 // Exit successfully while keeping VM running in background 84 Deno.exit(0); 85 } else { 86 const cmd = new Deno.Command(vm.bridge ? "sudo" : "qemu-system-x86_64", { 87 args: qemuArgs, 88 stdin: "inherit", 89 stdout: "inherit", 90 stderr: "inherit", 91 }); 92 93 const child = cmd.spawn(); 94 await updateInstanceState(name, "RUNNING", child.pid); 95 96 const status = await child.status; 97 98 await updateInstanceState(name, "STOPPED", child.pid); 99 100 if (!status.success) { 101 Deno.exit(status.code); 102 } 103 } 104} 105 106function mergeFlags(vm: VirtualMachine): VirtualMachine { 107 const { flags } = parseFlags(Deno.args); 108 return { 109 ...vm, 110 memory: flags.memory ? String(flags.memory) : vm.memory, 111 cpus: flags.cpus ? Number(flags.cpus) : vm.cpus, 112 cpu: flags.cpu ? String(flags.cpu) : vm.cpu, 113 diskFormat: flags.diskFormat ? String(flags.diskFormat) : vm.diskFormat, 114 portForward: flags.portForward ? String(flags.portForward) : vm.portForward, 115 drivePath: flags.image ? String(flags.image) : vm.drivePath, 116 bridge: flags.bridge ? String(flags.bridge) : vm.bridge, 117 diskSize: flags.size ? String(flags.size) : vm.diskSize, 118 }; 119}