Files
homelab-infrastructure-as-code/proxmox-infra/index.ts
T
kasun 3b356aa823
Deploy Proxmox Infra / Pulumi Preview (push) Has been skipped
Deploy Proxmox Infra / Pulumi Deploy (push) Successful in 1m6s
fix: enabled qemu guest agent for template
2026-05-29 12:36:58 +02:00

257 lines
6.6 KiB
TypeScript

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,
};