116 lines
4.0 KiB
TypeScript
Executable File
116 lines
4.0 KiB
TypeScript
Executable File
#!/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 <numMasters:number>", "Number of master nodes", { default: 3 })
|
|
.action(({ masters }) => setupCluster(masters))
|
|
.parse(Deno.args);
|