Compare commits

...

30 Commits

Author SHA1 Message Date
Lukas Greve
41302119a7 increase vCPU to 2 and make variable ssh_key optional 2025-10-06 12:08:45 +02:00
Lukas Greve
d0dc1f02f6 phyllome os support 2025-10-06 12:07:48 +02:00
Lukas Greve
1f1f2ce12f q35 chipset by default 2025-10-06 12:07:19 +02:00
Lukas Greve
17a810d9c0 make domain name definition a function of the vm name 2025-09-18 20:35:33 +02:00
Lukas Greve
902420a3ea Make enabling cloud_init optionnal
For cloud images that needs it, cloud_init can be enabled in the main.tf file. This change will allow deployments that does not require cloud_init to be supported
2025-09-18 20:24:38 +02:00
Lukas Greve
75b2ae6b40 add support for ubuntu uefi vm 2025-09-18 16:36:48 +02:00
Lukas Greve
3ae8b81859 simplify name of vm 2025-09-18 16:36:33 +02:00
Lukas Greve
3da3aa5cc4 first implementation of UEFI support 2025-09-18 16:36:09 +02:00
Lukas Greve
bfb5f780c8 make the pool name a function of the vm name 2025-09-14 12:15:02 +02:00
Lukas Greve
79be0a2461 network names as a function of vm the name 2025-09-14 12:07:07 +02:00
Lukas Greve
2e6d1c1b8a remove output file as it is not working now 2025-09-14 11:33:13 +02:00
Lukas Greve
5f2e5dc16b add support for fedora 42 2025-09-14 11:33:00 +02:00
Lukas Greve
64f4701507 rename file 2025-09-14 11:32:48 +02:00
Lukas Greve
28b6775c90 add provider file in shared module folder, otherwise deployment fails to identify correct provider to fetch 2025-09-13 16:20:56 +02:00
Lukas Greve
8d451e12d2 remove unnecessary file 2025-09-10 20:54:28 +02:00
Lukas Greve
c68ca02018 add another output, but does not seem to return anything for the moment 2025-09-10 20:54:10 +02:00
Lukas Greve
727903412f ssh public key now defined as variable. User can define it under the main deployment file 2025-09-10 20:53:54 +02:00
Lukas Greve
af31f8c4b2 ignore aider-related files 2025-09-10 20:52:24 +02:00
Lukas Greve
3498b877c8 feat: add ssh_key variable support to cloud-init module 2025-09-10 20:40:25 +02:00
Lukas Greve
798cc871c8 remove some unused variables 2025-09-07 20:10:23 +02:00
Lukas Greve
86be10c776 add some outputs, but not avails 2025-09-07 20:10:09 +02:00
Lukas Greve
b1b7a6e311 host-passthrough cpu mode and scsi for the storage 2025-09-07 20:10:00 +02:00
Lukas Greve
778711fdfb just allow ssh-key authentication 2025-09-07 20:09:26 +02:00
Lukas Greve
8078d9c041 cloud_init.yaml insecure example 2025-09-04 11:34:09 +02:00
Lukas Greve
4409d31ae9 unnecessary files 2025-09-04 11:33:55 +02:00
Lukas Greve
2433e08f90 added some explanations 2025-09-04 11:32:42 +02:00
Lukas Greve
07ed85724b first example of a deployment, a simple ubuntu-based BIOS virtual machine with the i440fx virtual chipset 2025-09-04 11:32:20 +02:00
Lukas Greve
7e470c0bc4 cloud_init.yaml, intented to be shared 2025-09-04 11:31:28 +02:00
Lukas Greve
ac81cb65ce shared modules to be used across deployments 2025-09-04 11:30:46 +02:00
Lukas Greve
96e8bd7588 first simple example 2025-09-04 11:30:25 +02:00
18 changed files with 443 additions and 21 deletions

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# Terraform
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfstate.lock.info
terraform.tfvars
terraform.tfvars.example
# Terraform plan and output files
*.tfplan
*.tfout
# Aider files
*.aider*

View File

@@ -4,6 +4,26 @@ This repository contains Terraform recipes to deploy various modern virtual mach
By modern, it is meant virtual machines that leverage the use of modern desktop-oriented technologies, like UEFI firmware and recent virtual motherboard chipset (i.e. Phyllome OS itself), by staying as close as possible as domain definitions maintained [here](https://git.phyllo.me/roots/xml-definition-for-domains).
## Organization
The folder *multiple* contains two subfolders, one with shared modules and the other with the various target deployment environments.
The idea is to reuse modules across multiple virtual machines and operating systems.
```
./multiple:
environments shared_modules
./multiple/environments:
cloud_init.yaml ubuntu-cloud-server-2404-bios
./multiple/environments/ubuntu-cloud-server-2404-bios:
ubuntu-cloud-server-2404-bios.tf
./multiple/shared_modules:
cloud-init.tf domain.tf network.tf outputs.tf pool.tf provider.tf variables.tf volume.tf
```
## Requirements
- [QEMU](https://www.qemu.org/)
@@ -17,7 +37,7 @@ Your Linux x86_64-based machine has at least 4 GB of available memory and 2 CPUs
## How to use it
- Clone this repository
- Go to folder *ubuntu-cloud-server-2404-bios*
- Go to folder *example*
- Execute the following commands, which will download and install the required Terraform provider if not already present
```
@@ -221,6 +241,8 @@ libvirt_pool.ubuntu2: Destruction complete after 0s
Destroy complete! Resources: 4 destroyed.
```
## Explanations
Let's take a look inside the *ubuntu-cloud-server-2404-bios* folder, which contains two files, *ubuntu-cloud-server-2404-bios.tf* and *cloud_init.cfg*

18
example/cloud_init.yaml Normal file
View File

@@ -0,0 +1,18 @@
#cloud-config
# vim: syntax=yaml
# examples:
# https://cloudinit.readthedocs.io/en/latest/topics/examples.html
---
ssh_pwauth: true
disable_root: false
chpasswd:
list: |
root:password
expire: false
users:
- name: ubuntu
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin
home: /home/ubuntu
shell: /bin/bash
lock_passwd: false

View File

@@ -23,23 +23,18 @@ resource "libvirt_pool" "ubuntu-bios" {
resource "libvirt_volume" "ubuntu-bios" {
name = "ubuntu-bios-${count.index}"
pool = libvirt_pool.ubuntu-bios.name
source = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
source = "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
format = "qcow2"
count = 2
}
data "template_file" "user_data" {
template = file("${path.module}/cloud_init.cfg")
count = 1
}
resource "libvirt_cloudinit_disk" "commoninit" {
name = "commoninit.iso"
user_data = data.template_file.user_data.rendered
pool = libvirt_pool.ubuntu-bios.name
user_data = templatefile("${path.module}/cloud_init.yaml", {})
}
resource "libvirt_domain" "domain" {
count = 2
count = 1
name = "ubuntu-cloud-server-2404-${count.index}"
memory = "4092"
vcpu = 2
@@ -72,4 +67,5 @@ resource "libvirt_domain" "domain" {
network_name = "default"
}
}
}

View File

@@ -0,0 +1,22 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "f42-bios"
image_location = "https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2"
ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMSuVlvOsMqx9qOrKKB1295FjCf2QhHfR1qola9brGkUcFL9dAztG2qdQnpiuPQ4OJpkedrO3C/ixEw1MLTL8l12SvYy/Q9QFguwylp35Nbw1p8h7jrX1FcNLRYltxkMgVhCs1InT5m0lf56bu1h7JfsMs7Ovsy3lU5OdK4h2MysTSKOLctsE4jDJ+XbJYQzj4rbfB/U7/9ple366cGl6xlaHxVfI4BUFWUOiVU4HWvZjrOM5fqPt+AUFRx1l2D7hLUZgOdVQwgO8GFn0sCyCIw0NCXbDn/H05pvWtTUPnyhj5TiseF8qW1byrrT5G8saxwvx8nbIK2tpPfKFdIiL7aj9bYQdltn1knJtvk3hpTPy4QvAbaoGfnfrPAsyU1A/CTw9SD/idvDT2wt1hVsm8EsnpovF7WT5z22fcgoFLDo+QCQrp7t1Wx0/Djay2nThi3FO3N051y5fQWoKOvTsm+rRhrzpDoc+Wtrtss3ua54qnQxHRx3YC0M5Xl9DINkwrcunbZBhozsDG2DzX9qcyzJsSfm9Zt5yM2lpcq+dGPRO1wedw4ogoOpobRr9Cja9W/lJvxmjgIiHz2HbSFPtk/VGjL6M7aQor/GDNN3ugSsfUoTTmNaS9+lWeg+tQWcFUPhYQtQB4/gHQ2u7+mQ0H3hVybsIKIh5XBpAdHQ7pww=="
enable_cloudinit = true
}

View File

@@ -0,0 +1,27 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "phyllome-42-uefi"
image_location = "/var/lib/libvirt/images/virtual-desktop-hypervisor.img"
enable_cloudinit = false
# ---- OPTIONAL UEFI SETTINGS ----------------------------------------------
uefi_firmware = "/usr/share/edk2/x64/OVMF_CODE.4m.fd"
uefi_nvram_template = "/usr/share/edk2/x64/OVMF_VARS.4m.fd"
uefi_nvram_file_suffix = "-uefi"
# ----------------------------------------------------------------
}

View File

@@ -0,0 +1,22 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "u24-bios"
image_location = "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMSuVlvOsMqx9qOrKKB1295FjCf2QhHfR1qola9brGkUcFL9dAztG2qdQnpiuPQ4OJpkedrO3C/ixEw1MLTL8l12SvYy/Q9QFguwylp35Nbw1p8h7jrX1FcNLRYltxkMgVhCs1InT5m0lf56bu1h7JfsMs7Ovsy3lU5OdK4h2MysTSKOLctsE4jDJ+XbJYQzj4rbfB/U7/9ple366cGl6xlaHxVfI4BUFWUOiVU4HWvZjrOM5fqPt+AUFRx1l2D7hLUZgOdVQwgO8GFn0sCyCIw0NCXbDn/H05pvWtTUPnyhj5TiseF8qW1byrrT5G8saxwvx8nbIK2tpPfKFdIiL7aj9bYQdltn1knJtvk3hpTPy4QvAbaoGfnfrPAsyU1A/CTw9SD/idvDT2wt1hVsm8EsnpovF7WT5z22fcgoFLDo+QCQrp7t1Wx0/Djay2nThi3FO3N051y5fQWoKOvTsm+rRhrzpDoc+Wtrtss3ua54qnQxHRx3YC0M5Xl9DINkwrcunbZBhozsDG2DzX9qcyzJsSfm9Zt5yM2lpcq+dGPRO1wedw4ogoOpobRr9Cja9W/lJvxmjgIiHz2HbSFPtk/VGjL6M7aQor/GDNN3ugSsfUoTTmNaS9+lWeg+tQWcFUPhYQtQB4/gHQ2u7+mQ0H3hVybsIKIh5XBpAdHQ7pww=="
enable_cloudinit = true
}

View File

@@ -0,0 +1,28 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
module "shared_modules" {
source = "../../shared_modules"
vm_name = "u24-uefi"
image_location = "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
ssh_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMSuVlvOsMqx9qOrKKB1295FjCf2QhHfR1qola9brGkUcFL9dAztG2qdQnpiuPQ4OJpkedrO3C/ixEw1MLTL8l12SvYy/Q9QFguwylp35Nbw1p8h7jrX1FcNLRYltxkMgVhCs1InT5m0lf56bu1h7JfsMs7Ovsy3lU5OdK4h2MysTSKOLctsE4jDJ+XbJYQzj4rbfB/U7/9ple366cGl6xlaHxVfI4BUFWUOiVU4HWvZjrOM5fqPt+AUFRx1l2D7hLUZgOdVQwgO8GFn0sCyCIw0NCXbDn/H05pvWtTUPnyhj5TiseF8qW1byrrT5G8saxwvx8nbIK2tpPfKFdIiL7aj9bYQdltn1knJtvk3hpTPy4QvAbaoGfnfrPAsyU1A/CTw9SD/idvDT2wt1hVsm8EsnpovF7WT5z22fcgoFLDo+QCQrp7t1Wx0/Djay2nThi3FO3N051y5fQWoKOvTsm+rRhrzpDoc+Wtrtss3ua54qnQxHRx3YC0M5Xl9DINkwrcunbZBhozsDG2DzX9qcyzJsSfm9Zt5yM2lpcq+dGPRO1wedw4ogoOpobRr9Cja9W/lJvxmjgIiHz2HbSFPtk/VGjL6M7aQor/GDNN3ugSsfUoTTmNaS9+lWeg+tQWcFUPhYQtQB4/gHQ2u7+mQ0H3hVybsIKIh5XBpAdHQ7pww=="
enable_cloudinit = true
# ---- OPTIONAL UEFI SETTINGS ----------------------------------------------
uefi_firmware = "/usr/share/edk2/x64/OVMF_CODE.4m.fd"
uefi_nvram_template = "/usr/share/edk2/x64/OVMF_VARS.4m.fd"
uefi_nvram_file_suffix = "-uefi"
# ----------------------------------------------------------------
}

View File

@@ -0,0 +1,11 @@
# Only create the cloudinit disk if enabled
resource "libvirt_cloudinit_disk" "commoninit" {
count = var.enable_cloudinit ? var.instance_count : 0
name = "${var.cloudinit_filename}-${count.index}"
user_data = templatefile("${path.module}/cloud_init.yaml", {
ssh_key = var.ssh_key
})
pool = "${var.vm_name}-pool"
depends_on = [libvirt_pool.tf_tmp_storage]
}

View File

@@ -0,0 +1,10 @@
#cloud-config
disable_root: true
users:
- name: groot
sudo: ALL=(ALL) NOPASSWD:ALL
groups: wheel,sudo,adm
shell: /bin/bash
ssh_authorized_keys:
- ${ssh_key}
ssh_pwauth: false

View File

@@ -0,0 +1,67 @@
resource "libvirt_domain" "domain" {
count = var.instance_count
name = "${var.vm_name}-${count.index}"
memory = var.memory
vcpu = var.vcpu
machine = "q35"
# The chipset q35, which does not support the IDE bus, does not work with the terraform-provider-libvirt cloud-init implementation,
# which creates an ISO attached to an IDE bus by default. Workaround is implemented
# https://github.com/dmacvicar/terraform-provider-libvirt/issues/1137#issuecomment-2592329846
# A cleaner solution might be the following :
# https://github.com/dmacvicar/terraform-provider-libvirt/pull/895#issuecomment-1911167872
xml {
xslt = file("${path.module}/q35-workaround.xslt")
}
# Only include cloudinit if enabled
cloudinit = var.enable_cloudinit ? libvirt_cloudinit_disk.commoninit[count.index].id : null
# ---- optional UEFI support ------------------------------------
# Firmware only add the string when a path is supplied
firmware = can(var.uefi_firmware) && length(var.uefi_firmware) > 0 ? var.uefi_firmware : null
# NVRAM block dynamic block that is evaluated once per VM
dynamic "nvram" {
# create the block once if a firmware path *and* a template were given
for_each = (can(var.uefi_firmware) && length(var.uefi_firmware) > 0
&& can(var.uefi_nvram_template) && length(var.uefi_nvram_template) > 0
) ? [1] : []
content {
# The NVRAM filename is perVM, but we can honour an optional suffix
file = "/var/lib/libvirt/qemu/nvram/${var.vm_name}-${count.index}${var.uefi_nvram_file_suffix}_VARS.fd"
template = var.uefi_nvram_template
}
}
# ----------------------------------------------------------------
cpu {
mode = "host-passthrough"
}
disk {
volume_id = element(libvirt_volume.vm_disk.*.id, count.index)
scsi = "true"
}
console {
type = "pty"
target_port = "0"
target_type = "virtio"
}
video {
type = "virtio"
}
tpm {
backend_type = "emulator"
backend_version = "2.0"
}
network_interface {
network_name = "${var.vm_name}-network"
}
}

View File

@@ -0,0 +1,11 @@
resource "libvirt_network" "tf_libvirt_network" {
name = "${var.vm_name}-network"
mode = var.network_mode
domain = local.computed_network_domain
addresses = var.network_addresses
dns {
enabled = var.dns_enabled
local_only = var.dns_local_only
}
}

View File

@@ -0,0 +1,7 @@
resource "libvirt_pool" "tf_tmp_storage" {
name = "${var.vm_name}-pool"
type = "dir"
target {
path = var.pool_path
}
}

View File

@@ -0,0 +1,9 @@
terraform {
required_version = ">= 0.13"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.8.3"
}
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/domain/devices/disk[@device='cdrom']/target/@bus">
<xsl:attribute name="bus">
<xsl:value-of select="'sata'"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/domain/devices/disk[@device='cdrom']/alias" />
</xsl:stylesheet>

View File

@@ -0,0 +1,141 @@
variable "libvirt_uri" {
description = "URI for libvirt connection"
type = string
default = "qemu:///system"
}
variable "pool_name" {
description = "Name of the storage pool"
type = string
default = "tf_tmp_pool"
}
variable "pool_path" {
description = "Path for the storage pool"
type = string
default = "/tmp/tf_tmp_storage"
}
variable "instance_count" {
description = "Number of instances to create"
type = number
default = 1
}
variable "vm_name" {
description = "Name prefix for VMs"
type = string
}
variable "image_location" {
description = "Location of the OS image"
type = string
default = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
}
# To avoid refetching the cloud ISO each time, it could could be set to a local directory, like : "/var/lib/libvirt/images/noble-server-cloudimg-amd64.img"
variable "cloudinit_filename" {
description = "Name of the cloud-init ISO file"
type = string
default = "commoninit.iso"
}
variable "enable_cloudinit" {
description = "Enable cloud-init support"
type = bool
default = false
}
variable "ssh_key" {
description = "SSH authorized keys for cloud-init"
type = string
default = ""
}
variable "user_data" {
description = "User data for cloud-init"
type = string
default = ""
}
variable "memory" {
description = "Memory allocation in MB"
type = number
default = 2048
validation {
condition = var.memory >= 512
error_message = "Memory must be at least 512MB."
}
}
variable "vcpu" {
description = "Number of virtual CPUs"
type = number
default = 2
}
variable "network_mode" {
description = "Network mode (nat, none, route, open, bridge)"
type = string
default = "nat"
}
variable "network_domain" {
description = "Domain name for the network (derived from vm_name)"
type = string
# Default dynamically based on vm_name
default = ""
}
variable "network_addresses" {
description = "List of network addresses"
type = list(string)
default = ["10.17.3.0/24", "2001:db8:ca2:2::1/64"]
}
variable "dns_enabled" {
description = "Enable DNS for the network"
type = bool
default = true
}
variable "dns_local_only" {
description = "DNS requests only resolved by virtual network's DNS server"
type = bool
default = false
}
variable "uefi_firmware" {
description = <<EOT
Path to the UEFI firmware binary (OVMF_CODE.fd, QEMU_CODE.fd, …).
Leave empty (or omit on the module call) to create a plain BIOS VM.
EOT
type = string
default = "" # “BIOS only” when empty
}
variable "uefi_nvram_template" {
description = <<EOT
Path to an NVRAM template that backs the UEFI NVRAM.
If you specify a template, the VM will get a writable NVRAM block.
Leave empty for a plain BIOS VM or if you dont need UEFI NVRAM.
EOT
type = string
default = "" # no NVRAM when empty
}
variable "uefi_nvram_file_suffix" {
description = <<EOT
Optional filesuffix fragment that is appended to the
generated NVRAM file name. Useful when you want to put the
files under a dedicated directory (`/var/lib/libvirt/qemu/uefi/nvram/…`).
Empty string means “no suffix” (default behaviour).
EOT
type = string
default = ""
}
# Computed variable for network domain (derived from vm_name)
locals {
computed_network_domain = var.network_domain != "" ? var.network_domain : "${var.vm_name}.local"
}

View File

@@ -0,0 +1,9 @@
resource "libvirt_volume" "vm_disk" {
count = var.instance_count
name = "${var.vm_name}-${count.index}"
pool = "${var.vm_name}-pool"
source = var.image_location
format = "qcow2"
depends_on = [libvirt_pool.tf_tmp_storage]
}

View File

@@ -1,10 +0,0 @@
ssh_pwauth: true
chpasswd:
list: |
root:phyllome
expire: False
disable_root: false
runcmd:
- sed -i '/PermitRootLogin/s/.*/PermitRootLogin yes/' /etc/ssh/sshd_config
- systemctl restart sshd