import * as pulumi from "@pulumi/pulumi"; import * as proxmox from "@muhlba91/pulumi-proxmoxve"; const config = new pulumi.Config(); // --------------------------------------------------------------------------- // Providers — one per standalone Proxmox machine // --------------------------------------------------------------------------- const pveProvider = new proxmox.Provider("pve", { endpoint: config.requireSecret("pve1Endpoint"), apiToken: config.requireSecret("pve1ApiToken"), insecure: true, }); const pveBckpProvider = new proxmox.Provider("pve-bckp", { endpoint: config.requireSecret("pve2Endpoint"), apiToken: config.requireSecret("pve2ApiToken"), insecure: true, }); // --------------------------------------------------------------------------- // Download Ubuntu Noble cloud image to each node's ISO storage // --------------------------------------------------------------------------- const ubuntuNobleUrl = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"; const ubuntuImagePve = new proxmox.download.File( "ubuntu-noble-pve", { nodeName: "pve", datastoreId: "pve-local-ext1", contentType: "import", fileName: "noble-server-cloudimg-amd64.qcow2", url: ubuntuNobleUrl, overwrite: true, overwriteUnmanaged: true, }, { provider: pveProvider }, ); const ubuntuImagePveBckp = new proxmox.download.File( "ubuntu-noble-pve-bckp", { nodeName: "pve-bckp", datastoreId: "local", contentType: "import", fileName: "noble-server-cloudimg-amd64.qcow2", url: ubuntuNobleUrl, overwrite: true, overwriteUnmanaged: true, }, { provider: pveBckpProvider }, ); // --------------------------------------------------------------------------- // VM templates — one per node, cloned from the downloaded cloud image. // Templates are not started and have no cloud-init config; that is applied // per-clone so each node gets its own hostname (derived from VM name). // --------------------------------------------------------------------------- const templateSettings = { template: true, started: false, stopOnDestroy: true, scsiHardware: "virtio-scsi-pci", cpu: { cores: 2, sockets: 1, type: "host", numa: true, }, memory: { dedicated: 2048, floating: 0, }, networkDevices: [{ bridge: "vmbr0", model: "virtio" }], serialDevices: [{}], vga: { type: "serial0" }, agent: { enabled: true }, }; const pveTemplate = new proxmox.VmLegacy( "k3s-template-pve", { ...templateSettings, nodeName: "pve", name: "k3s-ubuntu-noble-template", disks: [ { interface: "scsi0", datastoreId: "local-lvm", importFrom: pulumi.interpolate`${ubuntuImagePve.datastoreId}:import/${ubuntuImagePve.fileName}`, size: 10, ssd: true, discard: "on", }, ], }, { provider: pveProvider }, ); const pveBckpTemplate = new proxmox.VmLegacy( "k3s-template-pve-bckp", { ...templateSettings, nodeName: "pve-bckp", name: "k3s-ubuntu-noble-template", disks: [ { interface: "scsi0", datastoreId: "local", importFrom: pulumi.interpolate`${ubuntuImagePveBckp.datastoreId}:import/${ubuntuImagePveBckp.fileName}`, size: 10, ssd: true, discard: "on", }, ], }, { provider: pveBckpProvider }, ); // --------------------------------------------------------------------------- // k3s nodes — full clones of their respective templates // --------------------------------------------------------------------------- const k3sVmPassword = config.requireSecret("k3sVmPassword"); const sshPvePublicKey = config.requireSecret("sshPvePublicKey"); interface NodeConfig { name: string; role: "master" | "worker"; nodeName: string; provider: proxmox.Provider; template: proxmox.VmLegacy; diskDatastore: string; } const nodeConfigs: NodeConfig[] = [ { name: "k3s-master-1", role: "master", nodeName: "pve", provider: pveProvider, template: pveTemplate, diskDatastore: "local-lvm", }, { name: "k3s-master-2", role: "master", nodeName: "pve", provider: pveProvider, template: pveTemplate, diskDatastore: "local-lvm", }, { name: "k3s-worker-1", role: "worker", nodeName: "pve", provider: pveProvider, template: pveTemplate, diskDatastore: "local-lvm", }, { name: "k3s-master-3", role: "master", nodeName: "pve-bckp", provider: pveBckpProvider, template: pveBckpTemplate, diskDatastore: "local", }, { name: "k3s-worker-2", role: "worker", nodeName: "pve-bckp", provider: pveBckpProvider, template: pveBckpTemplate, diskDatastore: "local", }, ]; const k3sVms = nodeConfigs.map( (node) => new proxmox.VmLegacy( node.name, { nodeName: node.nodeName, name: node.name, description: "k3s " + node.role + " node — managed by Pulumi", tags: ["k3s", node.role], clone: { vmId: node.template.vmId, full: true, datastoreId: node.diskDatastore, }, cpu: { cores: 2, sockets: 1, type: "host", numa: true, }, memory: { dedicated: 2048, floating: 0, }, disks: [ { interface: "scsi0", datastoreId: node.diskDatastore, size: 10, ssd: true, discard: "on", }, ], initialization: { datastoreId: node.diskDatastore, ipConfigs: [{ ipv4: { address: "dhcp" } }], userAccount: { username: "ubuntu", password: k3sVmPassword, keys: [sshPvePublicKey.apply((k) => k.trim())], }, }, networkDevices: [{ bridge: "vmbr0", model: "virtio" }], scsiHardware: "virtio-scsi-pci", serialDevices: [{}], vga: { type: "serial0" }, agent: { enabled: true }, started: false, stopOnDestroy: true, }, { provider: node.provider, retainOnDelete: true, ignoreChanges: ["clone"], }, ), ); export const clusterInfo = k3sVms.map((vm, index) => ({ nodeName: vm.nodeName, vmId: vm.vmId, name: nodeConfigs[index].name, role: nodeConfigs[index].role, })); // Individual vmId exports — used by k8s-bootstrap to start VMs and run guest exec. // Order matches nodeConfigs: master-1, master-2, worker-1, master-3, worker-2. export const vmIds = { master1: k3sVms[0].vmId, master2: k3sVms[1].vmId, worker1: k3sVms[2].vmId, master3: k3sVms[3].vmId, worker2: k3sVms[4].vmId, };