diff --git a/terraform/proxmox/vm/main.tf b/terraform/proxmox/vm/main.tf new file mode 100644 index 0000000..ff9046f --- /dev/null +++ b/terraform/proxmox/vm/main.tf @@ -0,0 +1,88 @@ +# ─── VM ID Derivation ──────────────────────────────────────────────────────── +# Convention carried from existing Ansible playbooks: +# VM ID = {third_octet}{fourth_octet:03d} +# Example: 10.1.71.35 → 71035 + +locals { + ip_octets = split(".", var.ip_address) + vm_id = tonumber("${local.ip_octets[2]}${format("%03d", tonumber(local.ip_octets[3]))}") +} + +# ─── Template Lookup ───────────────────────────────────────────────────────── +# Find the template VM ID by name on the clone node + +data "proxmox_virtual_environment_vms" "templates" { + node_name = var.clone_node + + filter { + name = "template" + values = ["true"] + } + + filter { + name = "name" + values = [var.template_name] + } +} + +# ─── VM Resource ───────────────────────────────────────────────────────────── + +resource "proxmox_virtual_environment_vm" "vm" { + name = var.hostname + vm_id = local.vm_id + node_name = var.target_node + + description = "Provisioned by mk-labs pipeline on ${timestamp()}" + tags = ["managed", "pipeline"] + + # Clone from template on fantasyland + clone { + vm_id = data.proxmox_virtual_environment_vms.templates.vms[0].vm_id + node_name = var.clone_node + full = true + datastore_id = var.datastore + } + + # Cloud-init configuration + # Uses DHCP — Unifi DHCP reservation is created before VM starts + initialization { + ip_config { + ipv4 { + address = "dhcp" + } + } + + dns { + servers = var.dns_servers + domain = var.search_domain + } + + datastore_id = var.datastore + } + + # Network interface + network_device { + bridge = var.bridge + model = "virtio" + vlan_id = var.vlan_id + } + + # VM stays STOPPED after clone. + # n8n handles: DHCP reservation → HA affinity rule → VM start + # This ensures DHCP is in place before first boot. + started = false + + # QEMU guest agent is installed in template (via Packer). + # Note: Proxmox 9 requires VM.GuestAgent.Audit privilege on the API token. + agent { + enabled = true + } + + # Prevent Terraform from fighting with manual changes + lifecycle { + ignore_changes = [ + description, + tags, + ] + } +} diff --git a/terraform/proxmox/vm/outputs.tf b/terraform/proxmox/vm/outputs.tf new file mode 100644 index 0000000..d6c40e3 --- /dev/null +++ b/terraform/proxmox/vm/outputs.tf @@ -0,0 +1,24 @@ +output "vm_id" { + description = "Proxmox VM ID of the created VM." + value = proxmox_virtual_environment_vm.vm.vm_id +} + +output "mac_address" { + description = "MAC address of the VM's first network interface." + value = proxmox_virtual_environment_vm.vm.network_device[0].mac_address +} + +output "hostname" { + description = "Hostname of the created VM." + value = var.hostname +} + +output "ip_address" { + description = "IP address of the created VM." + value = var.ip_address +} + +output "node_name" { + description = "Proxmox node where the VM is running." + value = proxmox_virtual_environment_vm.vm.node_name +} diff --git a/terraform/proxmox/vm/providers.tf b/terraform/proxmox/vm/providers.tf new file mode 100644 index 0000000..d86e444 --- /dev/null +++ b/terraform/proxmox/vm/providers.tf @@ -0,0 +1,20 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + proxmox = { + source = "bpg/proxmox" + version = ">= 0.74.0" + } + } +} + +provider "proxmox" { + endpoint = var.proxmox_api_url + api_token = var.proxmox_api_token + insecure = true + + ssh { + agent = true + } +} diff --git a/terraform/proxmox/vm/terraform.tfvars.example b/terraform/proxmox/vm/terraform.tfvars.example new file mode 100644 index 0000000..5ea362d --- /dev/null +++ b/terraform/proxmox/vm/terraform.tfvars.example @@ -0,0 +1,13 @@ +# Copy this file to terraform.tfvars and fill in the values. +# terraform.tfvars is in .gitignore — never commit secrets. + +proxmox_api_url = "https://fantasyland.mk-labs.net:8006" +proxmox_api_token = "terraform@pve!terraform-token=your-token-secret-here" + +# These values come from n8n via -var flags at runtime: +# hostname = "test-vm" +# ip_address = "10.1.71.100" +# target_node = "fantasyland" +# template_name = "ubuntu-24.04-small" +# datastore = "liberty-tree" +# vlan_id = 71 diff --git a/terraform/proxmox/vm/variables.tf b/terraform/proxmox/vm/variables.tf new file mode 100644 index 0000000..a289fbe --- /dev/null +++ b/terraform/proxmox/vm/variables.tf @@ -0,0 +1,88 @@ +# ─── Provider Authentication ───────────────────────────────────────────────── + +variable "proxmox_api_url" { + type = string + description = "Proxmox API endpoint URL (e.g., https://fantasyland.mk-labs.net:8006)" +} + +variable "proxmox_api_token" { + type = string + description = "Proxmox API token in format: user@realm!token-name=token-secret" + sensitive = true +} + +# ─── VM Identity ───────────────────────────────────────────────────────────── + +variable "hostname" { + type = string + description = "VM hostname. Used as the Proxmox VM name and cloud-init hostname." +} + +variable "ip_address" { + type = string + description = "Primary IP address for the VM (without CIDR, e.g., 10.1.71.100). Used for VM ID derivation and DHCP reservation." +} + +variable "dns_servers" { + type = list(string) + description = "DNS servers for the VM." + default = ["10.1.71.102", "10.1.71.1"] +} + +variable "search_domain" { + type = string + description = "DNS search domain for the VM." + default = "mk-labs.net" +} + +# ─── Proxmox Placement ────────────────────────────────────────────────────── + +variable "clone_node" { + type = string + description = "Proxmox node where templates live. Cloning always happens here." + default = "fantasyland" +} + +variable "target_node" { + type = string + description = "Proxmox node for final VM placement. VM is migrated here after clone." +} + +variable "datastore" { + type = string + description = "Proxmox storage pool for VM disks." + default = "liberty-tree" +} + +# ─── Template Selection ───────────────────────────────────────────────────── + +variable "template_name" { + type = string + description = "Name of the Proxmox VM template to clone." + + validation { + condition = contains([ + "ubuntu-24.04-small", + "ubuntu-24.04-medium", + "ubuntu-24.04-large", + "ubuntu-24.04-large-plus", + "ubuntu-24.04-xlarge", + "ubuntu-24.04-xlarge-plus", + ], var.template_name) + error_message = "template_name must be a valid Ubuntu 24.04 template." + } +} + +# ─── Network ───────────────────────────────────────────────────────────────── + +variable "vlan_id" { + type = number + description = "VLAN tag for the VM network interface." + default = 71 +} + +variable "bridge" { + type = string + description = "Proxmox network bridge for the VM." + default = "vmbr0" +} diff --git a/terraform/unifi/dhcp/main.tf b/terraform/unifi/dhcp/main.tf new file mode 100644 index 0000000..67e0ede --- /dev/null +++ b/terraform/unifi/dhcp/main.tf @@ -0,0 +1,12 @@ +# ─── DHCP Static Reservation ───────────────────────────────────────────────── +# Creates a fixed IP assignment on the UDM Pro for the newly provisioned VM. +# Triggered by n8n after Proxmox VM creation returns the MAC address. + +resource "unifi_user" "vm" { + name = var.hostname + mac = lower(var.mac_address) + fixed_ip = var.ip_address + network_id = var.network_id + + note = "Managed by mk-labs pipeline" +} diff --git a/terraform/unifi/dhcp/outputs.tf b/terraform/unifi/dhcp/outputs.tf new file mode 100644 index 0000000..419794a --- /dev/null +++ b/terraform/unifi/dhcp/outputs.tf @@ -0,0 +1,4 @@ +output "reservation_id" { + description = "Unifi user/client ID for the DHCP reservation." + value = unifi_user.vm.id +} diff --git a/terraform/unifi/dhcp/providers.tf b/terraform/unifi/dhcp/providers.tf new file mode 100644 index 0000000..68f2f77 --- /dev/null +++ b/terraform/unifi/dhcp/providers.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + unifi = { + source = "paultyng/unifi" + version = ">= 0.41.0" + } + } +} + +provider "unifi" { + username = var.unifi_username + password = var.unifi_password + api_url = var.unifi_api_url + + # Set to true if using a self-signed certificate + allow_insecure = true +} diff --git a/terraform/unifi/dhcp/terraform.tfvars.example b/terraform/unifi/dhcp/terraform.tfvars.example new file mode 100644 index 0000000..8188606 --- /dev/null +++ b/terraform/unifi/dhcp/terraform.tfvars.example @@ -0,0 +1,12 @@ +# Copy this file to terraform.tfvars and fill in the values. +# terraform.tfvars is in .gitignore — never commit secrets. + +unifi_username = "admin" +unifi_password = "your-password-here" +unifi_api_url = "https://10.1.0.1:443" + +# These values come from n8n via -var flags at runtime: +# hostname = "test-vm" +# mac_address = "AA:BB:CC:DD:EE:FF" +# ip_address = "10.1.71.100" +# network_id = "your-server-trusted-network-id" diff --git a/terraform/unifi/dhcp/variables.tf b/terraform/unifi/dhcp/variables.tf new file mode 100644 index 0000000..99405fc --- /dev/null +++ b/terraform/unifi/dhcp/variables.tf @@ -0,0 +1,41 @@ +# ─── Provider Authentication ───────────────────────────────────────────────── + +variable "unifi_username" { + type = string + description = "Unifi controller username." + sensitive = true +} + +variable "unifi_password" { + type = string + description = "Unifi controller password." + sensitive = true +} + +variable "unifi_api_url" { + type = string + description = "Unifi controller API URL (e.g., https://10.1.0.1:443)." +} + +# ─── DHCP Reservation ─────────────────────────────────────────────────────── + +variable "hostname" { + type = string + description = "Hostname for the DHCP reservation. Used as the client name." +} + +variable "mac_address" { + type = string + description = "MAC address of the VM network interface (from Terraform Proxmox output)." +} + +variable "ip_address" { + type = string + description = "Fixed IP address for the DHCP reservation (without CIDR, e.g., 10.1.71.100)." +} + +variable "network_id" { + type = string + description = "Unifi network ID for the DHCP reservation." + default = "" +}