diff --git a/home-lab/terraform/lxc.tf b/home-lab/terraform/lxc.tf index ac756ee..35e3c7f 100644 --- a/home-lab/terraform/lxc.tf +++ b/home-lab/terraform/lxc.tf @@ -142,9 +142,9 @@ module "immich_lxc" { } module "bitwarden_lxc" { - source = "../../terraform-modules/proxmox/lxc" + source = "../../terraform-modules/proxmox/lxc" - node_name = "proxmox" + node_name = "proxmox" lxc_hostname = "vaultwarden" # vm_id = 114 @@ -157,7 +157,7 @@ module "bitwarden_lxc" { lxc_memory = 1024 # lxc_memory_swap = 512 - lxc_password = var.default_password + lxc_password = var.default_password ssh_public_keys = local.default_ssh_public_key lxc_features_nesting = true diff --git a/home-lab/terraform/versions.tf b/home-lab/terraform/versions.tf index 8163392..81deb62 100644 --- a/home-lab/terraform/versions.tf +++ b/home-lab/terraform/versions.tf @@ -3,7 +3,7 @@ terraform { # https://github.com/bpg/terraform-provider-proxmox proxmox = { source = "bpg/proxmox" - version = "0.86.0" + version = "0.89.1" } # https://github.com/Telmate/terraform-provider-proxmox diff --git a/home-lab/terraform/vm.tf b/home-lab/terraform/vm.tf new file mode 100644 index 0000000..94336e1 --- /dev/null +++ b/home-lab/terraform/vm.tf @@ -0,0 +1,127 @@ +module "opnsense" { + source = "../../terraform-modules/proxmox/vm" + + proxmox_node = "proxmox" + + vm_name = "opnsense" + vm_description = "Managed by Terraform" + + vm_cpu_type = "host" + vm_cores = 2 + vm_sockets = 1 + + vm_memory = 4096 + + vm_scsihw = "virtio-scsi-single" + + vm_display = { + type = "serial0" + } + + vm_disks = [ + { + backup = true + datastore_id = "local" + interface = "ide2" + replicate = true + size = 2 + }, + { + aio = "io_uring" + backup = true + cache = "writeback" + datastore_id = "nvme4tb" + size = 30 + iothread = true + replicate = true + } + ] + + vm_network_devices = [ + { + bridge = "vmbr0" + enable_firewall = false + }, + { + bridge = "vmbr1" + enable_firewall = false + } + ] + + operating_system_type = "l26" + vm_machine_type = "q35" + + vm_tags = ["firewall"] +} + + +module "windows11" { + source = "../../terraform-modules/proxmox/vm" + + proxmox_node = "proxmox" + + vm_name = "Win11" + vm_description = "Managed by Terraform" + + vm_cpu_flags = [] + # vm_cpu_units = 0 + vm_memory = 4096 + + vm_scsihw = "virtio-scsi-single" + vm_enable_qemu_agent = true + vm_bios_type = "ovmf" # UEFI + + vm_display = { + type = "std" + } + + vm_disks = [ + # CD/DVD ide0 virtio-win + { + backup = true + datastore_id = "local" + interface = "ide0" + replicate = true + # The size is around 0.7GB but the tf proxmox provider + # (bpg/proxmox:v0.89.1) requires a minimum of 1GB + # I have to manually update the tfstate + size = 1 + }, + # CD/DVD ide2 Win11.iso + { + backup = true + datastore_id = "local" + interface = "ide2" + replicate = true + size = 7 + }, + # Hard disk scsi0 + { + backup = true + datastore_id = "local-lvm" + interface = "scsi0" + replicate = true + size = 64 + iothread = true + }, + ] + + vm_network_devices = [ + { + bridge = "vmbr0" + enable_firewall = true + }, + ] + + operating_system_type = "win11" + vm_machine_type = "pc-q35-10.0+pve1" + + efi_disk = { + datastore_id = "local-lvm" + file_format = "raw" + pre_enrolled_keys = true + type = "4m" + } + + vm_tags = ["windows"] +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/examples/00-simple-vm/main.tf b/terraform-modules/proxmox/vm/examples/00-simple-vm/main.tf new file mode 100644 index 0000000..485e7b3 --- /dev/null +++ b/terraform-modules/proxmox/vm/examples/00-simple-vm/main.tf @@ -0,0 +1,11 @@ +module "this" { + source = "../../" + + proxmox_node = "proxmox" + + vm_name = "terraform-provider-proxmox-ubuntu-vm" + vm_description = "Managed by Terraform" + vm_tags = ["ubuntu"] + + enable_network = false +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/examples/00-simple-vm/variables.tf b/terraform-modules/proxmox/vm/examples/00-simple-vm/variables.tf new file mode 100644 index 0000000..db3e0e6 --- /dev/null +++ b/terraform-modules/proxmox/vm/examples/00-simple-vm/variables.tf @@ -0,0 +1,23 @@ +variable "proxmox_api_url" { + description = "Proxmox API URL" + type = string + default = "https://pve01.cloud.local:8006/api2/json" +} + +variable "proxmox_api_token_id" { + description = "Proxmox API token ID" + type = string + sensitive = true +} + +variable "proxmox_api_token_secret" { + description = "Proxmox API token secret" + type = string + sensitive = true +} + +variable "proxmox_tls_insecure" { + description = "Skip TLS verification for Proxmox API" + type = bool + default = true +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/examples/00-simple-vm/versions.tf b/terraform-modules/proxmox/vm/examples/00-simple-vm/versions.tf new file mode 100644 index 0000000..b1247b1 --- /dev/null +++ b/terraform-modules/proxmox/vm/examples/00-simple-vm/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + # https://github.com/bpg/terraform-provider-proxmox + proxmox = { + source = "bpg/proxmox" + version = "0.89.1" + } + } +} + +provider "proxmox" { + endpoint = "https://proxmox.internal.local:8006" + insecure = var.proxmox_tls_insecure + + api_token = "${var.proxmox_api_token_id}=${var.proxmox_api_token_secret}" +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/examples/01-clone-vm/main.tf b/terraform-modules/proxmox/vm/examples/01-clone-vm/main.tf new file mode 100644 index 0000000..b3e883c --- /dev/null +++ b/terraform-modules/proxmox/vm/examples/01-clone-vm/main.tf @@ -0,0 +1,17 @@ +module "this" { + source = "../../" + + proxmox_node = "proxmox" + + vm_name = "terraform-ubuntu-clone" + vm_description = "Managed by Terraform" + vm_tags = ["ubuntu"] + + clone_vm = true + clone_vm_target = { + vm_id = 901 + full = true + } + + vm_enable_qemu_agent = true +} diff --git a/terraform-modules/proxmox/vm/examples/01-clone-vm/variables.tf b/terraform-modules/proxmox/vm/examples/01-clone-vm/variables.tf new file mode 100644 index 0000000..f8ae186 --- /dev/null +++ b/terraform-modules/proxmox/vm/examples/01-clone-vm/variables.tf @@ -0,0 +1,24 @@ +# Proxmox provider +variable "proxmox_api_url" { + description = "Proxmox API URL" + type = string + default = "https://pve01.cloud.local:8006/api2/json" +} + +variable "proxmox_api_token_id" { + description = "Proxmox API token ID" + type = string + sensitive = true +} + +variable "proxmox_api_token_secret" { + description = "Proxmox API token secret" + type = string + sensitive = true +} + +variable "proxmox_tls_insecure" { + description = "Skip TLS verification for Proxmox API" + type = bool + default = true +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/examples/01-clone-vm/versions.tf b/terraform-modules/proxmox/vm/examples/01-clone-vm/versions.tf new file mode 100644 index 0000000..b1247b1 --- /dev/null +++ b/terraform-modules/proxmox/vm/examples/01-clone-vm/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + # https://github.com/bpg/terraform-provider-proxmox + proxmox = { + source = "bpg/proxmox" + version = "0.89.1" + } + } +} + +provider "proxmox" { + endpoint = "https://proxmox.internal.local:8006" + insecure = var.proxmox_tls_insecure + + api_token = "${var.proxmox_api_token_id}=${var.proxmox_api_token_secret}" +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/main.tf b/terraform-modules/proxmox/vm/main.tf new file mode 100644 index 0000000..5677119 --- /dev/null +++ b/terraform-modules/proxmox/vm/main.tf @@ -0,0 +1,135 @@ +data "proxmox_virtual_environment_vms" "all_vms" { + node_name = var.proxmox_node +} + +locals { + default_tags = ["managed-by-terraform"] + vga_hardware_with_memory = ["std", "vmware", "qxl", "cirrus", "virtio"] + vga_hardware_without_memory = ["none", "std", "serial0", "serial1", "serial2", "serial3"] + vga_hardware = concat(local.vga_hardware_with_memory, local.vga_hardware_without_memory) + available_storage = ["nvme4tb", "local-lvm"] + + template_vms = var.clone_vm && var.clone_vm_target != null ? [ + for vm in data.proxmox_virtual_environment_vms.all_vms.vms : vm + if vm.name == var.vm_template_name + ] : [] + vm_template_id = length(local.template_vms) > 0 ? local.template_vms[0].vm_id : null +} + +resource "proxmox_virtual_environment_vm" "this" { + name = var.vm_name + description = var.vm_description + tags = distinct(concat(local.default_tags, var.vm_tags)) + + node_name = var.proxmox_node + vm_id = var.proxmox_vm_id + + on_boot = var.vm_start_on_boot + started = var.vm_started + + cpu { + cores = var.vm_cores + sockets = var.vm_sockets + type = var.vm_cpu_type + flags = var.vm_cpu_flags + units = var.vm_cpu_units + } + + memory { + dedicated = var.vm_memory + # floating = var.vm_memory # set equal to dedicated to enable ballooning + } + + # VM Hardware Configuration + dynamic "agent" { # qemu guest agent + for_each = var.vm_enable_qemu_agent != null ? [1] : [] + + content { + enabled = var.vm_enable_qemu_agent + } + } + boot_order = var.vm_boot_order + scsi_hardware = var.vm_scsihw + + dynamic "vga" { + for_each = var.vm_display != null ? [var.vm_display] : [] + + content { + type = vga.value.type + memory = contains(local.vga_hardware_without_memory, vga.value.type) ? null : vga.value.memory + } + } + + # if agent is not enabled, the VM may not be able to shutdown properly, and may need to be forced off + # https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm#qemu-guest-agent + stop_on_destroy = var.vm_enable_qemu_agent != null ? !var.vm_enable_qemu_agent : true + + # Clone or boot from disk (cdrom) + dynamic "cdrom" { + for_each = var.clone_vm || !var.vm_boot_from_disk ? [] : [1] + + content { + #:/ + # proxmox_virtual_environment_download_file + file_id = "local:iso/ubuntu-24.04.3-desktop-amd64.iso" + interface = "ide2" + } + } + + dynamic "clone" { + for_each = var.clone_vm ? [var.clone_vm_target] : [] + + content { + vm_id = var.clone_vm_target != null ? clone.value.vm_id : local.vm_template_id + datastore_id = var.clone_vm_target != null ? clone.value.datastore : null + full = var.clone_vm_target != null ? clone.value.full : true + } + } + + # Disk Configuration + dynamic "disk" { + for_each = var.clone_vm ? [] : (length(var.vm_disks) > 0 ? var.vm_disks : [{}]) + + content { + backup = length(var.vm_disks) > 0 && disk.value.backup != null ? disk.value.backup : false + cache = length(var.vm_disks) > 0 && disk.value.cache != null ? disk.value.cache : "none" + iothread = length(var.vm_disks) > 0 && disk.value.iothread != null ? disk.value.iothread : false + datastore_id = length(var.vm_disks) > 0 && disk.value.datastore_id != null ? disk.value.datastore_id : contains(local.available_storage, "nvme4tb") ? "nvme4tb" : null + interface = length(var.vm_disks) > 0 && disk.value.interface != null ? disk.value.interface : "scsi0" + size = length(var.vm_disks) > 0 && disk.value.size != null ? disk.value.size : "32" + replicate = length(var.vm_disks) > 0 && disk.value.replicate != null ? disk.value.replicate : false + } + } + + # Network Configuration + dynamic "network_device" { + for_each = var.enable_network ? (length(var.vm_network_devices) > 0 ? var.vm_network_devices : [{}]) : [] + + content { + bridge = length(var.vm_network_devices) > 0 ? network_device.value.bridge : var.vm_network_bridge + firewall = length(var.vm_network_devices) > 0 ? network_device.value.enable_firewall : true + } + } + + dynamic "operating_system" { + for_each = var.operating_system_type != null ? [1] : [] + + content { + type = var.operating_system_type + } + } + + machine = var.vm_machine_type + bios = var.vm_bios_type + + dynamic "efi_disk" { + for_each = var.efi_disk != null ? [var.efi_disk] : [] + + content { + datastore_id = efi_disk.value.datastore_id + file_format = efi_disk.value.file_format + pre_enrolled_keys = efi_disk.value.pre_enrolled_keys + type = efi_disk.value.type + } + } +} diff --git a/terraform-modules/proxmox/vm/outputs.tf b/terraform-modules/proxmox/vm/outputs.tf new file mode 100644 index 0000000..d1b79a4 --- /dev/null +++ b/terraform-modules/proxmox/vm/outputs.tf @@ -0,0 +1,53 @@ +# # VM Information Outputs +output "vm_id" { + description = "The ID of the created VM" + value = proxmox_virtual_environment_vm.this.vm_id +} + +output "vm_name" { + description = "The name of the created VM" + value = proxmox_virtual_environment_vm.this.name +} + +output "vm_node" { + description = "The Proxmox node where the VM is running" + value = proxmox_virtual_environment_vm.this.node_name +} + +output "vm_template_name" { + description = "The template name used to create the VM" + value = var.vm_template_name +} + +output "vm_template_id" { + description = "The template ID used to create the VM" + value = var.clone_vm ? (var.clone_vm_target != null ? var.clone_vm_target.vm_id : local.vm_template_id) : null +} + +# # VM Configuration Outputs +output "vm_cores" { + description = "Number of CPU cores assigned to the VM" + value = proxmox_virtual_environment_vm.this.cpu[0].cores +} + +output "vm_sockets" { + description = "Number of CPU sockets assigned to the VM" + value = proxmox_virtual_environment_vm.this.cpu[0].sockets +} + +output "vm_memory" { + description = "Amount of memory (MB) assigned to the VM" + value = proxmox_virtual_environment_vm.this.memory[0].dedicated +} + +# Network Information +output "vm_network" { + description = "Network configuration of the VM" + value = proxmox_virtual_environment_vm.this.network_device +} + +# VM Status and Connection Info +output "vm_tags" { + description = "Tags assigned to the VM" + value = proxmox_virtual_environment_vm.this.tags +} diff --git a/terraform-modules/proxmox/vm/variables.tf b/terraform-modules/proxmox/vm/variables.tf new file mode 100644 index 0000000..73a4e65 --- /dev/null +++ b/terraform-modules/proxmox/vm/variables.tf @@ -0,0 +1,290 @@ +# Proxmox Provider Configuration +variable "proxmox_node" { + description = "Proxmox node name where VM will be created" + type = string + default = "proxmox" +} + +variable "proxmox_vm_id" { + description = "The identifier for the source VM" + type = number + default = null +} + +# VM Template Configuration +variable "clone_vm" { + description = "Whether to clone an existing VM" + type = bool + default = false +} + +variable "vm_template_name" { + description = "Name of the Proxmox template to clone" + type = string + default = null +} + +variable "clone_vm_target" { + description = < 0 + error_message = "VM name cannot be empty." + } +} + +variable "vm_description" { + description = "Description of the usage of the virtual machine" + type = string +} + +variable "vm_cores" { + description = "Number of CPU cores for the VM" + type = number + default = 2 + + validation { + condition = var.vm_cores > 0 && var.vm_cores <= 32 + error_message = "VM cores must be between 1 and 32." + } +} + +variable "vm_sockets" { + description = "Number of CPU sockets for the VM" + type = number + default = 1 + + validation { + condition = var.vm_sockets > 0 && var.vm_sockets <= 4 + error_message = "VM sockets must be between 1 and 4." + } +} + +variable "vm_cpu_type" { + description = "CPU type for the VM" + type = string + default = "x86-64-v2-AES" +} + +variable "vm_cpu_flags" { + description = "List of CPU flags for the VM" + type = list(string) + default = null +} + +variable "vm_cpu_units" { + description = "CPU units for the VM" + type = number + default = null +} + +variable "vm_memory" { + description = "Amount of memory in MB for the VM" + type = number + default = 2048 + + validation { + condition = var.vm_memory >= 512 + error_message = "VM memory must be at least 512 MB." + } +} + +variable "vm_start_on_boot" { + description = "Whether the VM should start on boot" + type = bool + default = false +} + +variable "vm_started" { + description = "Whether the VM should be started" + type = bool + default = false +} + +# VM Hardware Configuration +variable "vm_boot_order" { + description = "Boot order for the VM. List of devices to boot from in the order they appear in the list." + type = list(string) + default = null + # default = ["ide0", "net0"] +} + +variable "vm_boot_from_disk" { + description = "Whether to boot the VM from disk" + type = bool + default = false +} + +variable "vm_scsihw" { + description = "SCSI hardware type" + type = string + default = "virtio-scsi-pci" + + validation { + condition = contains(["virtio-scsi-pci", "lsi", "lsi53c895a", "megasas", "virtio-scsi-single"], var.vm_scsihw) + error_message = "Invalid SCSI hardware type." + } +} + +variable "vm_enable_qemu_agent" { + description = "Enable QEMU guest agent" + type = bool + default = null +} + +variable "vm_display" { + description = "The VGA configuration for the VM" + type = object({ + type = string + memory = optional(number) + }) + + default = { + type = "serial0" + memory = 0 + } + + validation { + condition = var.vm_display == null || contains(local.vga_hardware_without_memory, var.vm_display.type) || (var.vm_display.memory >= 8 && contains(local.vga_hardware_with_memory, var.vm_display.type)) + error_message = "If vm_display is set, memory must be at least 8 MB and type must be one of 'std', 'qxl', 'virtio', 'vmware', or 'none'." + } +} + +variable "operating_system_type" { + description = "The operating system configuration" + type = string + default = null + + validation { + condition = var.operating_system_type == null || contains(["l26", "l24", "l23", "win10", "win11", "other"], var.operating_system_type) + error_message = "If operating_system_type is set, it must be one of 'l26', 'l24', 'l23', 'win10', 'win11', or 'other'." + } +} + +variable "vm_machine_type" { + description = "The machine type for the VM. Defaults of module to `pc`" + type = string + default = null +} + +variable "vm_bios_type" { + description = "The BIOS implementation for the VM" + type = string + default = null +} + +# Network Configuration +variable "enable_network" { + description = "Whether to enable network interface for the VM" + type = bool + default = true +} + +variable "vm_network_model" { + description = "Network model for the VM" + type = string + default = "virtio" +} + +variable "vm_network_bridge" { + description = "Network bridge for the VM" + type = string + default = "vmbr0" +} + +variable "vm_network_devices" { + description = "" + type = list(object({ + bridge = string + enable_firewall = bool + })) + default = [] +} + +# Disk Configuration +variable "vm_disk_type" { + description = "Disk type for the VM" + type = string + default = "scsi" +} + +variable "vm_disk_storage" { + description = "Storage location for the VM disk" + type = string + default = "local-lvm" +} + +variable "vm_disk_size" { + description = "Size of the VM disk (GB)" + type = number + default = 10 +} + +variable "vm_disks" { + description = "List of disk configurations for the VM" + type = list(object({ + aio = optional(string) + backup = optional(bool) + cache = optional(string) + iothread = optional(bool) + datastore_id = optional(string) + interface = optional(string) + size = optional(number) + replicate = optional(bool) + })) + default = [] +} + +# Cloud-Init Configuration +variable "vm_ci_user" { + description = "Cloud-init username" + type = string + default = "ubuntu" +} + +variable "vm_ci_password" { + description = "Cloud-init password" + type = string + sensitive = true + default = "" +} + +# Other Configurations +variable "efi_disk" { + description = "EFI disk configuration for the VM" + type = object({ + datastore_id = string + file_format = string + pre_enrolled_keys = optional(bool) + type = string + }) + default = null +} + +variable "vm_tags" { + description = "Tags for the VM" + type = list(string) + default = ["managed-by-terraform"] +} \ No newline at end of file diff --git a/terraform-modules/proxmox/vm/versions.tf b/terraform-modules/proxmox/vm/versions.tf new file mode 100644 index 0000000..707b98c --- /dev/null +++ b/terraform-modules/proxmox/vm/versions.tf @@ -0,0 +1,11 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + # https://github.com/bpg/terraform-provider-proxmox + proxmox = { + source = "bpg/proxmox" + version = "0.89.1" + } + } +} \ No newline at end of file