A Docker-like CLI and HTTP API for managing headless VMs
at main 129 lines 3.7 kB view raw
1import chalk from "chalk"; 2import { Effect } from "effect"; 3import { BridgeSetupError, NetworkError } from "./errors.ts"; 4 5export const setupQemuBridge = (bridgeName: string) => 6 Effect.tryPromise({ 7 try: async () => { 8 const bridgeConfPath = "/etc/qemu/bridge.conf"; 9 const bridgeConfContent = await Deno.readTextFile(bridgeConfPath).catch( 10 () => "", 11 ); 12 if (bridgeConfContent.includes(`allow ${bridgeName}`)) { 13 console.log( 14 chalk.greenBright( 15 `QEMU bridge configuration for ${bridgeName} already exists.`, 16 ), 17 ); 18 return; 19 } 20 21 console.log( 22 chalk.blueBright( 23 `Adding QEMU bridge configuration for ${bridgeName}...`, 24 ), 25 ); 26 27 const cmd = new Deno.Command("sudo", { 28 args: [ 29 "sh", 30 "-c", 31 `mkdir -p /etc/qemu && echo "allow ${bridgeName}" >> ${bridgeConfPath}`, 32 ], 33 stdin: "inherit", 34 stdout: "inherit", 35 stderr: "inherit", 36 }); 37 const status = await cmd.spawn().status; 38 39 if (!status.success) { 40 console.error( 41 chalk.redBright( 42 `Failed to add QEMU bridge configuration for ${bridgeName}.`, 43 ), 44 ); 45 Deno.exit(status.code); 46 } 47 48 console.log( 49 chalk.greenBright( 50 `QEMU bridge configuration for ${bridgeName} added successfully.`, 51 ), 52 ); 53 }, 54 catch: (error) => new BridgeSetupError({ cause: error }), 55 }); 56 57export const createBridgeNetworkIfNeeded = (bridgeName: string) => 58 Effect.tryPromise({ 59 try: async () => { 60 const bridgeExistsCmd = new Deno.Command("ip", { 61 args: ["link", "show", bridgeName], 62 stdout: "null", 63 stderr: "null", 64 }); 65 66 const bridgeExistsStatus = await bridgeExistsCmd.spawn().status; 67 if (bridgeExistsStatus.success) { 68 console.log( 69 chalk.greenBright(`Network bridge ${bridgeName} already exists.`), 70 ); 71 await setupQemuBridge(bridgeName); 72 return; 73 } 74 75 console.log(chalk.blueBright(`Creating network bridge ${bridgeName}...`)); 76 const createBridgeCmd = new Deno.Command("sudo", { 77 args: ["ip", "link", "add", bridgeName, "type", "bridge"], 78 stdin: "inherit", 79 stdout: "inherit", 80 stderr: "inherit", 81 }); 82 83 let status = await createBridgeCmd.spawn().status; 84 if (!status.success) { 85 console.error( 86 chalk.redBright(`Failed to create network bridge ${bridgeName}.`), 87 ); 88 Deno.exit(status.code); 89 } 90 91 const bringUpBridgeCmd = new Deno.Command("sudo", { 92 args: ["ip", "link", "set", "dev", bridgeName, "up"], 93 stdin: "inherit", 94 stdout: "inherit", 95 stderr: "inherit", 96 }); 97 status = await bringUpBridgeCmd.spawn().status; 98 if (!status.success) { 99 console.error( 100 chalk.redBright(`Failed to bring up network bridge ${bridgeName}.`), 101 ); 102 Deno.exit(status.code); 103 } 104 105 console.log( 106 chalk.greenBright(`Network bridge ${bridgeName} created and up.`), 107 ); 108 109 await setupQemuBridge(bridgeName); 110 }, 111 catch: (error) => new NetworkError({ cause: error }), 112 }); 113 114export const generateRandomMacAddress = () => 115 Effect.sync(() => { 116 const hexDigits = "0123456789ABCDEF"; 117 let macAddress = "52:54:00"; 118 119 for (let i = 0; i < 3; i++) { 120 macAddress += ":"; 121 for (let j = 0; j < 2; j++) { 122 macAddress += hexDigits.charAt( 123 Math.floor(Math.random() * hexDigits.length), 124 ); 125 } 126 } 127 128 return macAddress; 129 });