diff options
| author | Yuqian Yang <crupest@crupest.life> | 2025-06-13 15:41:02 +0800 | 
|---|---|---|
| committer | Yuqian Yang <crupest@crupest.life> | 2025-06-13 17:29:39 +0800 | 
| commit | 87e041cd350b76cdfb9bb440e31c25e0d58ab6fb (patch) | |
| tree | baeaa937bb30dfe59b3670f99d9993128af9b82a /deno/tools | |
| parent | 0c174baec106266462eede0bb82b1ada19db0908 (diff) | |
| download | crupest-87e041cd350b76cdfb9bb440e31c25e0d58ab6fb.tar.gz crupest-87e041cd350b76cdfb9bb440e31c25e0d58ab6fb.tar.bz2 crupest-87e041cd350b76cdfb9bb440e31c25e0d58ab6fb.zip  | |
deno: add manage-vm.
Diffstat (limited to 'deno/tools')
| -rw-r--r-- | deno/tools/deno.json | 3 | ||||
| -rw-r--r-- | deno/tools/manage-vm.ts | 147 | 
2 files changed, 149 insertions, 1 deletions
diff --git a/deno/tools/deno.json b/deno/tools/deno.json index 355046a..cda933f 100644 --- a/deno/tools/deno.json +++ b/deno/tools/deno.json @@ -3,6 +3,7 @@    "tasks": {    },    "imports": { -    "mustache": "npm:mustache@^4.2.0" +    "mustache": "npm:mustache@^4.2.0", +    "yargs": "npm:yargs@^18.0.0"    }  } diff --git a/deno/tools/manage-vm.ts b/deno/tools/manage-vm.ts new file mode 100644 index 0000000..a1388b1 --- /dev/null +++ b/deno/tools/manage-vm.ts @@ -0,0 +1,147 @@ +import os from "node:os" +import { join } from "@std/path"; +// @ts-types="npm:@types/yargs" +import yargs from "yargs"; + + +type ArchAliasMap = { [name: string]: string[] }; +const arches = { +  x86_64: ["x86_64", "amd64"], +  i386: ["i386", "x86", "i686"], +} as const satisfies ArchAliasMap; +type Arch = keyof typeof arches; +type GeneralArch = (typeof arches)[Arch][number]; + +function normalizeArch(generalName: GeneralArch): Arch { +  for (const [name, aliases] of Object.entries( +    arches as ArchAliasMap, +  )) { +    if (aliases.includes(generalName)) return name as Arch; +  } +  throw Error("Unknown architecture name."); +} + +interface GeneralVmSetup { +  name?: string[]; +  arch: GeneralArch; +  disk: string; +  sshForwardPort: number; +  kvm?: boolean; +} + +interface VmSetup { +  arch: Arch; +  disk: string; +  sshForwardPort: number; +  kvm: boolean; +} + +const MY_VMS: GeneralVmSetup[] = [ +  { +    name: ["hurd", ...arches.i386.map((a) => `hurd-${a}`)], +    arch: "i386", +    disk: join(os.homedir(), "vms/hurd-i386.qcow2"), +    sshForwardPort: 3222, +  }, +  { +    name: [...arches.x86_64.map((a) => `hurd-${a}`)], +    arch: "x86_64", +    disk: join(os.homedir(), "vms/hurd-x86_64.qcow2"), +    sshForwardPort: 3223, +  }, +]; + +function normalizeVmSetup(generalSetup: GeneralVmSetup): VmSetup { +  const { arch, disk, sshForwardPort, kvm } = generalSetup; +  return { +    arch: normalizeArch(arch), +    disk, +    sshForwardPort, +    kvm: kvm ?? Deno.build.os === "linux", +  }; +} + +function resolveVmSetup( +  name: string, +  vms: GeneralVmSetup[], +): VmSetup | undefined { +  const setup = vms.find((vm) => vm.name?.includes(name)); +  return setup == null ? undefined : normalizeVmSetup(setup); +} + +const qemuBinPrefix = "qemu-system" as const; + +const qemuBinSuffix = { +  x86_64: "x86_64", +  i386: "x86_64", +} as const; + +function getQemuBin(arch: Arch): string { +  return `${qemuBinPrefix}-${qemuBinSuffix[arch]}`; +} + +function getLinuxHostArgs(kvm: boolean): string[] { +  return kvm ? ["-enable-kvm"] : []; +} + +function getMachineArgs(arch: Arch): string[] { +  const is64 = arch === "x86_64"; +  const machineArgs = is64 ? ["-machine", "q35"] : []; +  const memory = is64 ? 8 : 4; +  return [...machineArgs, "-m", `${memory}G`]; +} + +function getNetworkArgs(sshForwardPort: number): string[] { +  return ["-net", "nic", "-net", `user,hostfwd=tcp::${sshForwardPort}-:22`]; +} + +function getDisplayArgs(): string[] { +  return ["-vga", "vmware"]; +} + +function getDiskArgs(disk: string): string[] { +  return ["-drive", `cache=writeback,file=${disk}`]; +} + +function createQemuArgs(setup: VmSetup): string[] { +  const { arch, disk, sshForwardPort } = setup; +  return [ +    getQemuBin(arch), +    ...getLinuxHostArgs(setup.kvm), +    ...getMachineArgs(arch), +    ...getDisplayArgs(), +    ...getNetworkArgs(sshForwardPort), +    ...getDiskArgs(disk), +  ]; +} + +if (import.meta.main) { +  await yargs(Deno.args) +    .scriptName("manage-vm") +    .command({ +      command: "gen <name>", +      describe: "generate cli command to run the vm", +      builder: (builder) => { +        return builder +          .positional("name", { +            describe: "name of the vm to run", +            type: "string", +          }) +          .demandOption("name") +          .strict(); +      }, +      handler: (argv) => { +        const vm = resolveVmSetup(argv.name, MY_VMS); +        if (vm == null) { +          console.error(`No vm called ${argv.name} is found.`); +          Deno.exit(-1); +        } +        const cli = createQemuArgs(vm); +        console.log(`${cli.join(" ")}`); +      }, +    }) +    .demandCommand(1, "One command must be specified.") +    .help() +    .strict() +    .parse(); +}  | 
