diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..59b04ce --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,23 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/dmacvicar/libvirt" { + version = "0.9.9" + constraints = "~> 0.9.9" + hashes = [ + "h1:ljmMoTuHWXJGf28PY5E0fX9BDQWNZfs8zX+xY80YBok=", + "zh:130c9df49bf2f69deb2af1ebbd25deb09b4ebda1022fcda980c197ffda800c1d", + "zh:38216229290a3450d53a8a2799175fd8de5c26581913e20c8848b75890343ea3", + "zh:45ad900f9c1866e24643b03ec4b0b716e012bd25d0c92bb4d0d47ca7531abec8", + "zh:6e77ca4b5fad91c30b7123fd1b40024dabf188e33509ed219904f7ff84c8fb1b", + "zh:70a9d5af99a60179c7d38032662f443c3d2ad8bddab11d535c545c5c44273b2d", + "zh:7de6b5272a89710f2490d3e7fdf7c495f7acaddfb127df8ebd86ef9a70508e3a", + "zh:8932f2eb1013b84f01008d59bb8a16b982ac9ac72f7de41a0f88b7fe41c352df", + "zh:8b6044884696c671610dfe752bec5507131f21757e848872d8624cfba7e9f6d6", + "zh:d891ec2c1a0ae55d44b158c7e629316caf02ab8a5c4018523865fdc2ae3299a3", + "zh:dd9bc604e2ac389da3bae463a3da2a1e06915b7952892c274ea0b54f2eb41300", + "zh:e18ca294c7ca2d35aa866d37c64d01f62bda2b2c261d8280dba0ab272517e47f", + "zh:ef96bb292d6df57c4975f9d94c09a2efd7d6aed470a0f23f1c56b69dc1ec0b14", + "zh:f7337de4d59dc06d578ab3393506fedfdc962b01cc24c78a3ff46b1311e5fdb7", + ] +} diff --git a/terraform/cloud-init/user-data.yaml.tftpl b/terraform/cloud-init/user-data.yaml.tftpl new file mode 100644 index 0000000..d2c80b7 --- /dev/null +++ b/terraform/cloud-init/user-data.yaml.tftpl @@ -0,0 +1,13 @@ +#cloud-config +hostname: ${hostname} +manage_etc_hosts: true +disable_root: true +ssh_pwauth: false +users: + - name: arch + groups: [wheel] + sudo: ["ALL=(ALL) NOPASSWD: ALL"] + shell: /bin/bash + lock_passwd: true + ssh_authorized_keys: + - ${ssh_public_key} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..9d167b3 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,129 @@ +locals { + output_dir = abspath("${path.module}/../output") + cloud_images = try(sort(fileset(local.output_dir, "Parch-Linux-x86_64-cloudimg-*.qcow2")), []) + selected_image = var.image_path != "" ? pathexpand(var.image_path) : ( + length(local.cloud_images) > 0 ? "${local.output_dir}/${local.cloud_images[length(local.cloud_images) - 1]}" : "" + ) + ssh_public_key = trimspace(file(pathexpand(var.ssh_public_key_path))) +} + +resource "libvirt_volume" "root" { + name = "${var.vm_name}.qcow2" + pool = var.libvirt_pool + target = { format = { type = "qcow2" } } + + create = { + content = { + url = local.selected_image + } + } + + lifecycle { + precondition { + condition = local.selected_image != "" && fileexists(local.selected_image) + error_message = "No Parch cloudimg QCOW2 found. Run 'make build' first or set -var image_path=/absolute/path/to/cloudimg.qcow2." + } + } +} + +resource "libvirt_cloudinit_disk" "seed" { + name = "${var.vm_name}-cloudinit" + + user_data = templatefile("${path.module}/cloud-init/user-data.yaml.tftpl", { + hostname = var.vm_name + ssh_public_key = jsonencode(local.ssh_public_key) + }) + + meta_data = yamlencode({ + instance-id = var.vm_name + local-hostname = var.vm_name + }) +} + +resource "libvirt_volume" "cloudinit" { + name = "${var.vm_name}-cloudinit.iso" + pool = var.libvirt_pool + target = { format = { type = "iso" } } + + create = { + content = { + url = libvirt_cloudinit_disk.seed.path + } + } +} + +resource "libvirt_domain" "vm" { + name = var.vm_name + type = "kvm" + memory = var.memory_mib + memory_unit = "MiB" + vcpu = var.vcpu + + features = { + acpi = true + apic = {} + } + + cpu = { + mode = "host-passthrough" + } + + os = { + type = "hvm" + type_arch = "x86_64" + type_machine = "q35" + boot_devices = [{ dev = "hd" }] + } + + devices = { + disks = [ + { + driver = { name = "qemu", type = "qcow2" } + source = { volume = { pool = libvirt_volume.root.pool, volume = libvirt_volume.root.name } } + target = { dev = "vda", bus = "virtio" } + }, + { + device = "cdrom" + source = { volume = { pool = libvirt_volume.cloudinit.pool, volume = libvirt_volume.cloudinit.name } } + target = { dev = "sda", bus = "sata" } + } + ] + interfaces = [ + { + type = "network" + model = { type = "virtio" } + source = { network = { network = var.libvirt_network } } + } + ] + serials = [{ + type = "pty" + target = { type = "isa-serial", port = 0 } + }] + consoles = [{ + type = "pty" + target = { type = "serial", port = 0 } + }] + channels = [{ + source = { unix = {} } + target = { virt_io = { name = "org.qemu.guest_agent.0" } } + }] + rngs = [{ + model = "virtio" + backend = { random = "/dev/urandom" } + }] + } + + running = true + + lifecycle { + replace_triggered_by = [libvirt_volume.root, libvirt_volume.cloudinit] + } +} + +output "vm_name" { + value = libvirt_domain.vm.name +} + +output "ssh_command" { + value = "make qemu-ssh (or: ssh -o IdentitiesOnly=yes -i ${trimsuffix(pathexpand(var.ssh_public_key_path), ".pub")} arch@)" +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..5bf1426 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,47 @@ +variable "libvirt_uri" { + description = "Libvirt connection URI. Use qemu:///system for the system daemon." + type = string + default = "qemu:///system" +} + +variable "libvirt_pool" { + description = "Existing libvirt storage pool for VM disks and cloud-init seed." + type = string + default = "default" +} + +variable "libvirt_network" { + description = "Existing libvirt network that provides DHCP." + type = string + default = "default" +} + +variable "vm_name" { + description = "Name for the Parch Linux VM." + type = string + default = "parch-cloud" +} + +variable "image_path" { + description = "Optional path to a cloudimg QCOW2. Empty selects the newest output/Parch-Linux-x86_64-cloudimg-*.qcow2." + type = string + default = "" +} + +variable "ssh_public_key_path" { + description = "Path to the public SSH key injected by cloud-init." + type = string + default = "~/.ssh/id_ed25519.pub" +} + +variable "memory_mib" { + description = "VM memory in MiB." + type = number + default = 2048 +} + +variable "vcpu" { + description = "Number of virtual CPUs." + type = number + default = 2 +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000..ea46140 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + libvirt = { + source = "dmacvicar/libvirt" + version = "~> 0.9.9" + } + } +} + +provider "libvirt" { + uri = var.libvirt_uri +}