From d941557e88261d483656cf066f99ebce898b0ac3 Mon Sep 17 00:00:00 2001 From: Ryan Blundon Date: Sat, 28 Feb 2026 00:00:56 -0600 Subject: [PATCH] Update terraform to use vm_id from n8n. --- .ansible/.lock | 0 docs/mk-labs-vm-provisioning-prd.docx | Bin 0 -> 19430 bytes docs/n8n-setup.md | 66 +++++++ docs/vm-provisioning-flow.md | 182 ++++++++++++++++++ terraform/proxmox/vm/main.tf | 14 +- terraform/proxmox/vm/outputs.tf | 2 +- terraform/proxmox/vm/terraform.tfvars.example | 16 +- terraform/proxmox/vm/variables.tf | 9 +- 8 files changed, 267 insertions(+), 22 deletions(-) create mode 100644 .ansible/.lock create mode 100644 docs/mk-labs-vm-provisioning-prd.docx create mode 100644 docs/n8n-setup.md create mode 100644 docs/vm-provisioning-flow.md diff --git a/.ansible/.lock b/.ansible/.lock new file mode 100644 index 0000000..e69de29 diff --git a/docs/mk-labs-vm-provisioning-prd.docx b/docs/mk-labs-vm-provisioning-prd.docx new file mode 100644 index 0000000000000000000000000000000000000000..9bcbc33875be12a4b13871012e776459094faae3 GIT binary patch literal 19430 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 literal 0 HcmV?d00001 diff --git a/docs/n8n-setup.md b/docs/n8n-setup.md new file mode 100644 index 0000000..a14c3bb --- /dev/null +++ b/docs/n8n-setup.md @@ -0,0 +1,66 @@ +# n8n Setup & Dependencies + +**Host:** tiki-room (10.1.71.23) +**Install method:** Proxmox LXC via helper script +**Version:** Community Edition + +## Community Node Dependencies + +The VM provisioning pipeline requires the following n8n community node: + +### n8n-nodes-globals + +- **Package:** [n8n-nodes-globals](https://github.com/umanamente/n8n-nodes-globals) +- **Purpose:** Stores secrets (API tokens, credentials) as encrypted global constants in n8n's credential store. Referenced by pipeline workflows to pass secrets to Terraform and NetBox API calls without persisting them on disk. +- **Install:** + ```bash + cd /usr/lib/node_modules/n8n + npm install n8n-nodes-globals + systemctl restart n8n + ``` +- **Version at time of writing:** v1.1.0 + +### Configuration + +After installing the community node, create a **Global Constants** credential in n8n with the following key-value pairs: + +| Key | Description | +|-----|-------------| +| `PROXMOX_API_URL` | Proxmox API endpoint (e.g., `https://fantasyland.local.mk-labs.cloud:8006`) | +| `PROXMOX_API_TOKEN` | Proxmox API token in format `user@realm!token-name=secret` | +| `UNIFI_USERNAME` | UDM Pro admin username | +| `UNIFI_PASSWORD` | UDM Pro admin password | +| `UNIFI_API_URL` | UDM Pro API URL (e.g., `https://10.1.0.1:443`) | +| `NETBOX_URL` | NetBox base URL (e.g., `https://fire-station`) | +| `NETBOX_API_TOKEN` | NetBox REST API token | + +These constants are encrypted at rest in n8n's database and are accessible in any workflow via the Global Constants node. This avoids storing secrets on city-hall's filesystem or in Terraform `.tfvars` files. + +## n8n Credentials + +In addition to global constants, the following n8n credentials are used by pipeline nodes: + +| Credential Type | Name | Used By | Details | +|----------------|------|---------|---------| +| SSH | city-hall SSH | Terraform apply nodes | SSH access to 10.1.71.35 for running Terraform commands | +| Header Auth | NetBox API Token | HTTP Request nodes | `Authorization: Token ` for NetBox API calls | + +## Secrets Management Roadmap + +The current approach (n8n Global Constants) is a Phase 1 solution. Future plans: + +- **Phase 6+:** Evaluate HashiCorp Vault or equivalent for centralized secrets management +- **Note:** n8n Community Edition may not support external secrets store integrations — verify before planning migration + +## Webhook Configuration + +The pipeline is triggered by a NetBox webhook: + +| Setting | Value | +|---------|-------| +| Webhook URL | `http://tiki-room:5678/webhook/vm-provision` | +| HTTP Method | POST | +| Content Type | `application/json` | +| SSL Verification | Disabled (internal VLAN) | + +See NetBox Event Rule configuration in `netbox/initializers/custom_fields.yml` for the trigger conditions. diff --git a/docs/vm-provisioning-flow.md b/docs/vm-provisioning-flow.md new file mode 100644 index 0000000..c139619 --- /dev/null +++ b/docs/vm-provisioning-flow.md @@ -0,0 +1,182 @@ +# ADR: VM Provisioning Pipeline Flow + +**Status:** Accepted +**Date:** February 2026 +**Supersedes:** PRD v1.0 Section 3.2 (adds DHCP step, adjusts tool boundaries) +**Last Updated:** February 28, 2026 — revised flow for DHCP-before-boot, HA affinity via Proxmox API, secrets via n8n globals + +## Context + +The mk-labs homelab needs an automated VM provisioning pipeline triggered by a single operator action. The original PRD defined the flow; this ADR documents the adjusted architecture where n8n owns all orchestration including DHCP reservation (previously Ansible-rendered Terraform templates). + +## Decision + +### System Context + +| Tool | Host | IP Address | Role in Pipeline | +|------|------|-----------|-----------------| +| NetBox | fire-station | 10.1.71.102 | Source of truth — VM records, IP allocation, VLAN data, webhook emitter | +| n8n | tiki-room | 10.1.71.23 | Event orchestration, payload validation, pipeline sequencing | +| Terraform | city-hall | 10.1.71.35 | Proxmox VM lifecycle + Unifi DHCP reservations (repo: /opt/git/homelab) | +| Ansible / Semaphore | imagineering | 10.1.71.22 | OS configuration, DNS record creation, NetBox status updates | +| Proxmox | fantasyland | 10.1.71.13 | Target hypervisor for all new VM deployments | + +### Status State Machine + +``` +PLANNED →(operator)→ STAGED →(pipeline)→ ACTIVE + | + on error + ↓ + FAILED +``` + +- **Planned**: All fields populated, operator not yet ready. No trigger. +- **Staged**: Operator validated and ready. Triggers pipeline. +- **Active**: Set by Ansible on successful OS config + DNS registration. +- **Failed**: Set by n8n or Ansible on any error. Manual investigation. + +### Provisioning Flow (Revised) + +| # | System | Action | Detail | +|---|--------|--------|--------| +| **1** | NetBox | Operator sets VM status to Staged | Triggers webhook to n8n on tiki-room | +| **2** | n8n | Receive & validate webhook payload | Check: hostname, IP, template, proxmox_node, proxmox_datastore all present. Status must be exactly Staged. On failure → set NetBox status to Failed, stop. | +| **3** | n8n → city-hall | SSH to city-hall, run `terraform apply` (proxmox/vm) | Creates VM clone on fantasyland in **stopped** state. Uses `-state=states/{hostname}.tfstate` for per-VM state isolation. Passes secrets from n8n Global Constants via `-var` flags. | +| **4** | n8n | Parse Terraform output for MAC address | Reads `mac_address` from `terraform output -json`. | +| **5** | n8n → NetBox | Write MAC address to VM interface | PATCH to NetBox API to update VM interface record with MAC. | +| **6** | n8n → city-hall | SSH to city-hall, run `terraform apply` (unifi/dhcp) | Creates static DHCP reservation on UDM Pro. Uses `-state=states/{hostname}.tfstate`. MAC and IP passed as variables. | +| **7** | n8n → Proxmox API | Add VM to HA resource + node affinity rule | POST to `/cluster/ha/resources` to register VM as HA resource. PUT to `/cluster/ha/rules/{prefer_node}` to add VM to the target node's affinity rule. | +| **8** | n8n → Proxmox API | Start VM | POST to `/nodes/{node}/qemu/{vmid}/status/start`. VM boots and receives IP via DHCP (reservation already in place). | +| **9** | n8n → imagineering | Call Semaphore API to trigger Ansible | Launches vm-provision playbook with VM details as extra vars. | +| **10** | Ansible | OS configuration & DNS record creation | Applies vm-baseline role: packages, SSH keys, Technitium DNS A record creation. | +| **11** | Ansible → NetBox | Set VM status to Active | Final step of playbook updates NetBox VM status via API. | +| **ERR** | n8n / Ansible | Any step fails | NetBox VM status set to Failed. Pipeline halts. No cleanup — operator investigates manually. | + +### Key Design Decisions + +**VM created in stopped state (Step 3)** +Cloud-init uses `ip=dhcp`. The DHCP reservation must exist on the UDM Pro before the VM's first boot, otherwise the VM gets a random DHCP lease instead of its assigned IP. The VM stays stopped until after the Unifi DHCP reservation is confirmed (Step 6). + +**HA affinity via Proxmox API, not Terraform (Step 7)** +Proxmox 9.x replaced HA groups with HA node affinity rules. The bpg/proxmox Terraform provider does not yet support these (tracked in bpg/terraform-provider-proxmox#2097). n8n calls the Proxmox API directly to add the VM to the appropriate `prefer_{node}` affinity rule. This also handles VM migration from fantasyland (clone source) to the target node. + +**Per-VM Terraform state files (Steps 3, 6)** +Each VM gets its own state file via `-state=states/{hostname}.tfstate` to avoid state conflicts when managing multiple VMs. The `states/` directory is in `.gitignore`. + +**Secrets via n8n Global Constants** +All secrets (Proxmox API token, Unifi credentials, NetBox API token) are stored in n8n's encrypted credential store using the `n8n-nodes-globals` community node. Secrets are passed to Terraform via `-var` flags in SSH commands. No secrets are stored on city-hall's filesystem. See `docs/n8n-setup.md` for details. + +### Tool Responsibility Boundary (Adjusted) + +| Tool | Owns | Does Not Own | +|------|------|-------------| +| **Terraform** | Proxmox VM lifecycle (bpg/proxmox), UDM Pro DHCP reservations (unifi provider) | OS config, DNS records, application deployment, NetBox data, HA placement | +| **Ansible** | OS configuration, DNS A records (Technitium API), NetBox status updates | Infrastructure provisioning, DHCP reservations, event orchestration | +| **n8n** | Event orchestration, API glue, pipeline sequencing, HA affinity rule management, triggering all Terraform and Ansible steps, secrets distribution | Configuration management, infrastructure state | +| **NetBox** | Source of truth for all assets | Execution of any automation — it only holds data | + +### Dependencies + +| Component | Dependency | Purpose | +|-----------|-----------|---------| +| n8n | n8n-nodes-globals (community node) | Encrypted secrets storage for pipeline credentials | +| Terraform (proxmox/vm) | bpg/proxmox provider >= 0.74.0 | Proxmox VM lifecycle management | +| Terraform (unifi/dhcp) | paultyng/unifi provider >= 0.41.0 | UDM Pro DHCP static reservations | +| Proxmox | API token: terraform@pve!terraform-token | Terraform + n8n API access (requires VM.GuestAgent.Audit for Proxmox 9) | +| NetBox | Event Rule + Webhook (VM Staged Trigger) | Pipeline trigger on status change to Staged | +| NetBox | REST API token | n8n reads/writes VM data | + +### Change from PRD v1.0 + +**What changed (original):** DHCP reservation moved from "existing Terraform/UDM Pro integration" (out of scope) to an explicit pipeline step owned by n8n. + +**What changed (this revision):** +1. VM is created in **stopped** state. DHCP reservation is created before VM boots. +2. HA node affinity rule assignment added as an explicit step (Proxmox API, not Terraform). +3. VM start is a separate explicit step after HA rule assignment. +4. Secrets managed via n8n Global Constants instead of filesystem-based `.tfvars` files. +5. Per-VM Terraform state files via `-state` flag. + +**Why:** Cloud-init uses `ip=dhcp`, so the DHCP reservation must exist before first boot. The bpg/proxmox Terraform provider doesn't support Proxmox 9 HA affinity rules, so n8n handles placement via API. Secrets in n8n's credential store keeps city-hall stateless. + +### Git Repository Structure + +``` +homelab/ +├── ansible/ +│ ├── inventory/ +│ │ ├── netbox.yml # NetBox dynamic inventory plugin +│ │ └── static.yml # Legacy static inventory +│ ├── playbooks/ +│ │ ├── vm-provision.yml # Main playbook (Semaphore trigger) +│ │ ├── add_technitium_dns_entry.yml +│ │ ├── add_dhcp_reservation.yml +│ │ ├── update-linux-os.yml +│ │ └── set_hostname.yml +│ ├── roles/ +│ │ ├── vm-baseline/ # NEW — packages, SSH, DNS, NetBox status +│ │ ├── dns-manager/ # Existing Technitium DNS role +│ │ ├── common/ # Base system config +│ │ ├── haproxy/ +│ │ ├── n8n/ +│ │ ├── observer/ +│ │ └── time-sync/ +│ ├── tasks/ # Shared task files +│ ├── group_vars/ +│ ├── host_vars/ +│ ├── templates/ +│ ├── scripts/ +│ ├── ansible.cfg +│ └── requirements.yml +│ +├── terraform/ +│ ├── proxmox/ +│ │ └── vm/ +│ │ ├── main.tf # bpg/proxmox VM resource (creates stopped) +│ │ ├── variables.tf # hostname, ip, template, proxmox_node +│ │ ├── outputs.tf # vm_id, mac_address +│ │ ├── providers.tf +│ │ ├── terraform.tfvars.example +│ │ └── states/ # Per-VM state files (.gitignore) +│ ├── unifi/ +│ │ └── dhcp/ +│ │ ├── main.tf # Unifi user resource (DHCP reservation) +│ │ ├── variables.tf # hostname, mac, ip +│ │ ├── providers.tf +│ │ ├── terraform.tfvars.example +│ │ └── states/ # Per-VM state files (.gitignore) +│ └── dns/ +│ ├── dns.tf # Existing DNS terraform +│ └── provider.tf +│ +├── packer/ +│ ├── ubuntu-24.04/ # Ubuntu template build +│ └── fedora-42/ # Fedora template build +│ +├── n8n/ +│ └── workflows/ +│ └── vm-provisioning.json # Exported n8n workflow +│ +├── netbox/ +│ └── initializers/ +│ ├── custom_fields.yml +│ ├── vlans.yml +│ └── prefixes.yml +│ +├── docs/ +│ ├── decisions/ +│ │ ├── vm-provisioning-flow.md # This document +│ │ └── tool-responsibility-matrix.md +│ └── n8n-setup.md # n8n dependencies and configuration +│ +├── README.md +├── LICENSE +└── .gitignore +``` + +## Consequences + +- **Positive:** Single visual workflow in n8n shows entire pipeline. Ansible simplified to pure configuration management. Terraform usage consistent (always invoked via n8n SSH, never rendered from templates). Secrets never stored on disk outside n8n's encrypted database. Per-VM state files prevent conflicts. +- **Negative:** n8n becomes a critical dependency — if tiki-room is down, no provisioning. Two separate `terraform apply` calls per VM (Proxmox + Unifi) instead of one. HA rule management is imperative API calls rather than declarative Terraform. +- **Mitigated by:** n8n workflow JSON committed to Git. Both Terraform modules can be run manually from city-hall if n8n is unavailable. HA rules can be managed via `ha-manager` CLI on any Proxmox node. diff --git a/terraform/proxmox/vm/main.tf b/terraform/proxmox/vm/main.tf index d7af3eb..426f6d3 100644 --- a/terraform/proxmox/vm/main.tf +++ b/terraform/proxmox/vm/main.tf @@ -1,12 +1,7 @@ -# ─── VM ID Derivation ──────────────────────────────────────────────────────── -# Convention carried from existing Ansible playbooks: +# ─── VM ID Convention ──────────────────────────────────────────────────────── # 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]))}") -} +# Computed by n8n and passed as a variable to keep the logic in one place. # ─── Template Lookup ───────────────────────────────────────────────────────── # Find the template VM ID by name on the clone node @@ -29,8 +24,7 @@ data "proxmox_virtual_environment_vms" "templates" { resource "proxmox_virtual_environment_vm" "vm" { name = var.hostname - bios = "ovmf" - vm_id = local.vm_id + vm_id = var.vm_id node_name = var.target_node description = "Provisioned by mk-labs pipeline on ${timestamp()}" @@ -65,7 +59,7 @@ resource "proxmox_virtual_environment_vm" "vm" { network_device { bridge = var.bridge model = "virtio" -# vlan_id = var.vlan_id + vlan_id = var.vlan_id } # VM stays STOPPED after clone. diff --git a/terraform/proxmox/vm/outputs.tf b/terraform/proxmox/vm/outputs.tf index d6c40e3..7d906f8 100644 --- a/terraform/proxmox/vm/outputs.tf +++ b/terraform/proxmox/vm/outputs.tf @@ -1,6 +1,6 @@ output "vm_id" { description = "Proxmox VM ID of the created VM." - value = proxmox_virtual_environment_vm.vm.vm_id + value = var.vm_id } output "mac_address" { diff --git a/terraform/proxmox/vm/terraform.tfvars.example b/terraform/proxmox/vm/terraform.tfvars.example index 5ea362d..b399603 100644 --- a/terraform/proxmox/vm/terraform.tfvars.example +++ b/terraform/proxmox/vm/terraform.tfvars.example @@ -1,13 +1,11 @@ -# 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: +# All values come from n8n via -var flags at runtime. +# Secrets are stored in n8n Global Constants — no terraform.tfvars file needed. +# +# vm_id = 71200 # hostname = "test-vm" -# ip_address = "10.1.71.100" +# ip_address = "10.1.71.200" # target_node = "fantasyland" # template_name = "ubuntu-24.04-small" # datastore = "liberty-tree" -# vlan_id = 71 +# proxmox_api_url = "https://fantasyland.local.mk-labs.cloud:8006" +# proxmox_api_token = "terraform@pve!terraform-token=your-token-secret-here" diff --git a/terraform/proxmox/vm/variables.tf b/terraform/proxmox/vm/variables.tf index 408f762..930cc2a 100644 --- a/terraform/proxmox/vm/variables.tf +++ b/terraform/proxmox/vm/variables.tf @@ -2,7 +2,7 @@ variable "proxmox_api_url" { type = string - description = "Proxmox API endpoint URL (e.g., https://fantasyland.local.mk-labs.cloud:8006)" + description = "Proxmox API endpoint URL (e.g., https://fantasyland.mk-labs.net:8006)" } variable "proxmox_api_token" { @@ -13,6 +13,11 @@ variable "proxmox_api_token" { # ─── VM Identity ───────────────────────────────────────────────────────────── +variable "vm_id" { + type = number + description = "Proxmox VM ID. Computed by n8n from IP: {third_octet}{fourth_octet:03d} (e.g., 10.1.71.200 → 71200)." +} + variable "hostname" { type = string description = "VM hostname. Used as the Proxmox VM name and cloud-init hostname." @@ -26,7 +31,7 @@ variable "ip_address" { variable "dns_servers" { type = list(string) description = "DNS servers for the VM." - default = ["10.1.71.1"] + default = ["10.1.71.102", "10.1.71.1"] } variable "search_domain" {