Files
nixos-servers/terraform/vms.tf
Torjus Håkestad 6f7aee3444
Some checks failed
Run nix flake check / flake-check (pull_request) Failing after 1m20s
Run nix flake check / flake-check (push) Failing after 1m54s
bootstrap: implement automated VM bootstrap mechanism for Phase 3
Add systemd service that automatically bootstraps freshly deployed VMs
with their host-specific NixOS configuration from the flake repository.

Changes:
- hosts/template2/bootstrap.nix: New systemd oneshot service that:
  - Runs after cloud-init completes (ensures hostname is set)
  - Reads hostname from hostnamectl (set by cloud-init from Terraform)
  - Checks network connectivity via HTTPS (curl)
  - Runs nixos-rebuild boot with flake URL
  - Reboots on success, fails gracefully with clear errors on failure

- hosts/template2/configuration.nix: Configure cloud-init datasource
  - Changed from NoCloud to ConfigDrive (used by Proxmox)
  - Allows cloud-init to receive config from Proxmox

- hosts/template2/default.nix: Import bootstrap.nix module

- terraform/vms.tf: Add cloud-init disk to VMs
  - Configure disks.ide.ide2.cloudinit block
  - Removed invalid cloudinit_cdrom_storage parameter
  - Enables Proxmox to inject cloud-init configuration

- TODO.md: Mark Phase 3 as completed

This eliminates the manual nixos-rebuild step from the deployment workflow.
VMs now automatically pull and apply their configuration on first boot.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 10:38:35 +01:00

123 lines
3.1 KiB
HCL

# VM Definitions
# Define all VMs to deploy in the locals.vms map below
# Omit fields to use defaults from variables.tf
locals {
# Define VMs here
# Each VM can override defaults by specifying values
# Omit "ip" field for DHCP, include it for static IP
vms = {
# Example DHCP VM (uncomment to deploy):
# "example-dhcp-vm" = {
# cpu_cores = 2
# memory = 2048
# disk_size = "20G"
# }
# Example Static IP VM (uncomment to deploy):
# "example-static-vm" = {
# ip = "10.69.13.50/24"
# cpu_cores = 4
# memory = 4096
# disk_size = "50G"
# }
# Example Minimal VM using all defaults (uncomment to deploy):
# "minimal-vm" = {}
# "bootstrap-verify-test" = {}
}
# Compute VM configurations with defaults applied
vm_configs = {
for name, vm in local.vms : name => {
target_node = lookup(vm, "target_node", var.default_target_node)
template_name = lookup(vm, "template_name", var.default_template_name)
cpu_cores = lookup(vm, "cpu_cores", var.default_cpu_cores)
memory = lookup(vm, "memory", var.default_memory)
disk_size = lookup(vm, "disk_size", var.default_disk_size)
storage = lookup(vm, "storage", var.default_storage)
bridge = lookup(vm, "bridge", var.default_bridge)
vlan_tag = lookup(vm, "vlan_tag", var.default_vlan_tag)
ssh_public_key = lookup(vm, "ssh_public_key", var.default_ssh_public_key)
nameservers = lookup(vm, "nameservers", var.default_nameservers)
search_domain = lookup(vm, "search_domain", var.default_search_domain)
# Network configuration - detect DHCP vs static
ip = lookup(vm, "ip", null)
gateway = lookup(vm, "gateway", var.default_gateway)
}
}
}
# Deploy all VMs using for_each
resource "proxmox_vm_qemu" "vm" {
for_each = local.vm_configs
name = each.key
target_node = each.value.target_node
# Clone from template
clone = each.value.template_name
full_clone = true
# Boot configuration
boot = "order=virtio0"
scsihw = "virtio-scsi-single"
# VM settings
cpu {
cores = each.value.cpu_cores
}
memory = each.value.memory
# Network
network {
id = 0
model = "virtio"
bridge = each.value.bridge
tag = each.value.vlan_tag
}
# Disk settings
disks {
virtio {
virtio0 {
disk {
size = each.value.disk_size
storage = each.value.storage
}
}
}
ide {
ide2 {
cloudinit {
storage = each.value.storage
}
}
}
}
# Start on boot
start_at_node_boot = true
# Agent
agent = 1
# Cloud-init configuration
ciuser = "root"
sshkeys = each.value.ssh_public_key
nameserver = each.value.nameservers
searchdomain = each.value.search_domain
# Network configuration - DHCP or static IP
ipconfig0 = each.value.ip != null ? "ip=${each.value.ip},gw=${each.value.gateway}" : "ip=dhcp"
# Skip IPv6 since we don't use it
skip_ipv6 = true
# RNG device for better entropy
rng {
source = "/dev/urandom"
period = 1000
}
}