#!/usr/bin/env -S deno run --allow-run --allow-read --allow-write import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; const setupCluster = async (numMasters: number) => { // Step 1: Create Low-Resource Profile (if not exists) const profileExists = await Deno.run({ cmd: ["incus", "profile", "show", "low-resource"], stdout: "null", stderr: "null", }).status().then((status) => status.success); if (!profileExists) { await Deno.run({ cmd: ["incus", "profile", "create", "low-resource"], }).status(); await Deno.run({ cmd: ["incus", "profile", "set", "low-resource", "limits.cpu=1", "limits.memory=512MB"], }).status(); await Deno.run({ cmd: ["incus", "profile", "device", "add", "low-resource", "root", "disk", "pool=default", "path=/"], }).status(); await Deno.run({ cmd: ["incus", "profile", "device", "add", "low-resource", "eth-0", "nic", "network=incusbr0"], }).status(); console.log("✅ Low-resource profile created."); } else { console.log("⏩ Low-resource profile already exists."); } // Step 3: Launch VMs (if not already running) for (let i = 1; i <= numMasters; i++) { const vmName = `k3s-master${i}`; const vmExists = await Deno.run({ cmd: ["incus", "list", vmName, "--format", "csv"], stdout: "piped", }).output().then((output) => new TextDecoder().decode(output).trim() !== ""); if (!vmExists) { await Deno.run({ cmd: ["incus", "launch", "images:alpine/edge/cloud", vmName, "--profile", "low-resource"], }).status(); console.log(`✅ VM ${vmName} launched.`); } else { console.log(`⏩ VM ${vmName} already exists.`); } } // Step 4: Install k3sup (if not installed) const k3supInstalled = await Deno.run({ cmd: ["which", "k3sup"], stdout: "null", stderr: "null", }).status().then((status) => status.success); if (!k3supInstalled) { await Deno.run({ cmd: ["sh", "-c", "curl -sLS https://get.k3sup.dev | sh"], }).status(); console.log("✅ k3sup installed."); } else { console.log("⏩ k3sup already installed."); } // Step 5: Bootstrap First Master Node (if not already bootstrapped) const firstMasterIP = await Deno.run({ cmd: ["incus", "list", "k3s-master1", "--format", "csv", "--columns", "n4"], stdout: "piped", }).output().then((output) => new TextDecoder().decode(output).trim().split(",")[1].split(" ")[0]) const kubeconfigExists = await Deno.stat("./kubeconfig").then(() => true).catch(() => false); if (!kubeconfigExists) { await Deno.run({ cmd: ["k3sup", "install", "--ip", firstMasterIP, "--user", "root", "--cluster"], }).status(); console.log("✅ First master node bootstrapped."); } else { console.log("⏩ First master node already bootstrapped."); } // Step 6: Join Additional Master Nodes (if not already joined) for (let i = 2; i <= numMasters; i++) { const vmName = `k3s-master${i}`; const vmIP = await Deno.run({ cmd: ["incus", "list", vmName, "--format", "csv", "--columns", "n4"], stdout: "piped", }).output().then((output) => new TextDecoder().decode(output).trim().split(",")[1].split(" ")[0]) const joined = await Deno.run({ cmd: ["kubectl", "get", "nodes", vmName], stdout: "null", stderr: "null", }).status().then((status) => status.success); if (!joined) { await Deno.run({ cmd: ["k3sup", "join", "--ip", vmIP, "--server-ip", firstMasterIP, "--user", "root"], }).status(); console.log(`✅ VM ${vmName} joined the cluster.`); } else { console.log(`⏩ VM ${vmName} already joined the cluster.`); } } console.log("🚀 HA k3s cluster setup complete!"); }; await new Command() .name("setup-k3s-cluster") .version("0.1.0") .description("Automate the setup of an HA k3s cluster using incus and k3sup") .option("-m, --masters ", "Number of master nodes", { default: 3 }) .action(({ masters }) => setupCluster(masters)) .parse(Deno.args);