diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ab759f --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# OpenTofu specific +.tofu/ +*.tofu.lock.hcl + +# IDE and Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# SSH keys (if any are generated locally) +*.pem +*.key +id_rsa* +*.pub + +# Log files +*.log + +# Backup files +*.backup +*.bak + +# Temporary files +*.tmp +*.temp diff --git a/vm-infrastructure/.terraform.lock.hcl b/vm-infrastructure/.terraform.lock.hcl new file mode 100644 index 0000000..cdf4485 --- /dev/null +++ b/vm-infrastructure/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/dmacvicar/libvirt" { + version = "0.7.6" + constraints = "~> 0.7.0" + hashes = [ + "h1:mmbm4vTyC/DCGO4Ed/vbp5AKvy1gmVn/94fzB9VmR08=", + "zh:0bde54f6f658b20b620b875daf106b5b25b1bae4d15408d6c5f06d58360e254d", + "zh:0c97c6930015918b8a34b6d7a2b0c3d17a649c226fcd1874fcba5bbbc0f35972", + "zh:1bdd7aa0011c5f024a09a124836ee9bc8e71b05a6ece810c61824275fd3f695f", + "zh:2b0cc7c794e4caf395d84ffff0b380d17e4b3219a4696264271bfe5059450efe", + "zh:2f8633f7fe07f76c188836ed6f93321ec5fbf5c004bc7699e1741d9b21ed5f37", + "zh:5bf47eed286ce55ed10a5cf657de49a34ab21cc8677c56fef3aab69cdde41a27", + "zh:7dca790fc5fd1d42bc4bc7170be003a7093602026d0f95c8aab84ad551fdf2a4", + "zh:80476b68bc84e3d661d1390025f83879b88f9cdc836de9751af09bd5716089cb", + "zh:82f3e2f3f50176cd6041c8ba36e295cbda1b289ef52ab75b5eceb0f921f64f7b", + "zh:a179b165f3b9bb9a67ebbbf9d73157ded33f02d476b2f58906389dca03b653c9", + "zh:acae54a5d0616f22b3180ddd8e8aad39af664e604394fdacf1f7b337bca2d5b4", + "zh:da4406a2428a9a7e98272c032cb93431c3919253af2fe9934b532d26c0deab09", + "zh:f63dbd8e579ab5268d01ffab4503b8a8e736b70d1a04e4f271559ba8dd133dcd", + "zh:f85c1d9e51a94ecde137435c9d6b0fb7be590437ea8a725334d1577eebbc550c", + ] +} diff --git a/vm-infrastructure/README.md b/vm-infrastructure/README.md new file mode 100644 index 0000000..689fd99 --- /dev/null +++ b/vm-infrastructure/README.md @@ -0,0 +1,50 @@ +# VM Infrastructure with OpenTofu + LibVirt + +This project provisions virtual machines using OpenTofu and LibVirt. + +## Quick Start + +```bash +# Initialize OpenTofu +tofu init + +# Plan deployment +tofu plan + +# Deploy VMs +tofu apply + +# Get VM IPs +tofu output vm_ips + +# Destroy infrastructure when done +tofu destroy +``` + +## Configuration + +- **VMs**: 3 Ubuntu 22.04 VMs with 4GB RAM, 2 vCPUs, 30GB disk each +- **Authentication**: SSH key-based (no password auth) +- **User**: `ubuntu` with sudo access +- **Network**: Default libvirt network with DHCP + +## Files + +- `main.tf` - Main infrastructure configuration +- `variables.tf` - Input variables +- `outputs.tf` - Output values (VM IPs and names) +- `terraform.tfvars` - Variable values +- `cloud-init/` - VM initialization configuration + +## Prerequisites + +1. Install LibVirt and OpenTofu (see main PLAN.md) +2. Ensure your SSH public key is in `cloud-init/user-data.yaml` +3. User must be in `libvirt` group + +## Accessing VMs + +```bash +# SSH to VM (replace with actual IP) +ssh ubuntu@192.168.122.100 +``` diff --git a/vm-infrastructure/cloud-init/meta-data.yaml b/vm-infrastructure/cloud-init/meta-data.yaml new file mode 100644 index 0000000..0de2525 --- /dev/null +++ b/vm-infrastructure/cloud-init/meta-data.yaml @@ -0,0 +1,2 @@ +instance-id: vm-instance +local-hostname: vm-host diff --git a/vm-infrastructure/cloud-init/user-data.yaml b/vm-infrastructure/cloud-init/user-data.yaml new file mode 100644 index 0000000..f2eae1a --- /dev/null +++ b/vm-infrastructure/cloud-init/user-data.yaml @@ -0,0 +1,29 @@ +#cloud-config +users: + - name: ubuntu + groups: sudo + shell: /bin/bash + sudo: ['ALL=(ALL) NOPASSWD:ALL'] + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE0jo1NZHqhXNPmWZ8x0wdFDfDiloSLfZL0NK/DbukhF3WN5hSn/6Vv6Qa6USpq1S3XiaBL7pA+zrTZGr/ZH6iZKDelYufU1a22dWx6OYKVX84d/wIc+3PHAFm1A5g3+787pT+6HHH4g7GKmf1/zYJlfqPr5zyGVBdQkzglDadC7wdfwGE7Q3gjHflhAIyGvcBfAQ/hYQSgWAMdsYhthh1DCMvPQIqA0E2tslgD1AtpDhjE72a+pLAsGxDjiBcuKW4SEAjcxt+Jcu10brrXAbt5bHg9SNk/NNwwMZVy0siAJRRcg6Dv41VqMa2MvIGoDUiDju4kRwjJMcxorwxAa8n jlkenyon@helios.local + +packages: + - curl + - wget + - git + - htop + +package_update: true +package_upgrade: true + +# Set timezone +timezone: UTC + +# Enable SSH +ssh_pwauth: false + +# Run commands on first boot +runcmd: + - systemctl enable ssh + - systemctl start ssh + - echo "VM provisioned successfully" > /tmp/provision-complete diff --git a/vm-infrastructure/main.tf b/vm-infrastructure/main.tf new file mode 100644 index 0000000..ddc67e9 --- /dev/null +++ b/vm-infrastructure/main.tf @@ -0,0 +1,69 @@ +terraform { + required_providers { + libvirt = { + source = "dmacvicar/libvirt" + version = "~> 0.7.0" + } + } +} + +# Configure the LibVirt Provider +provider "libvirt" { + uri = "qemu:///system" +} + +# Download Ubuntu cloud image +resource "libvirt_volume" "base_image" { + name = "ubuntu-22.04-base.qcow2" + pool = var.pool_name + source = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" + format = "qcow2" +} + +# Create cloud-init disk +resource "libvirt_cloudinit_disk" "cloudinit" { + name = "cloudinit.iso" + pool = var.pool_name + user_data = file("${path.module}/cloud-init/user-data.yaml") + meta_data = file("${path.module}/cloud-init/meta-data.yaml") +} + +# Create VM volumes from base image +resource "libvirt_volume" "vm_disk" { + count = var.vm_count + name = "vm-${count.index + 1}-disk.qcow2" + pool = var.pool_name + base_volume_id = libvirt_volume.base_image.id + size = var.disk_size +} + +# Create VMs +resource "libvirt_domain" "vm" { + count = var.vm_count + name = "vm-${count.index + 1}" + memory = var.memory + vcpu = var.vcpu + + cloudinit = libvirt_cloudinit_disk.cloudinit.id + + network_interface { + network_name = "default" + wait_for_lease = true + } + + disk { + volume_id = libvirt_volume.vm_disk[count.index].id + } + + console { + type = "pty" + target_port = "0" + target_type = "serial" + } + + graphics { + type = "spice" + listen_type = "address" + autoport = true + } +} diff --git a/vm-infrastructure/outputs.tf b/vm-infrastructure/outputs.tf new file mode 100644 index 0000000..cb153fe --- /dev/null +++ b/vm-infrastructure/outputs.tf @@ -0,0 +1,11 @@ +output "vm_ips" { + description = "IP addresses of the created VMs" + value = { + for i, vm in libvirt_domain.vm : vm.name => vm.network_interface[0].addresses[0] + } +} + +output "vm_names" { + description = "Names of the created VMs" + value = libvirt_domain.vm[*].name +} diff --git a/vm-infrastructure/variables.tf b/vm-infrastructure/variables.tf new file mode 100644 index 0000000..0695ac3 --- /dev/null +++ b/vm-infrastructure/variables.tf @@ -0,0 +1,35 @@ +variable "pool_name" { + description = "Name of the storage pool" + type = string + default = "vm-pool" +} + +variable "pool_path" { + description = "Path to the storage pool" + type = string + default = "/var/lib/libvirt/images/vm-pool" +} + +variable "vm_count" { + description = "Number of VMs to create" + type = number + default = 3 +} + +variable "memory" { + description = "Memory allocation for each VM (MB)" + type = number + default = 2048 +} + +variable "vcpu" { + description = "Number of vCPUs for each VM" + type = number + default = 2 +} + +variable "disk_size" { + description = "Disk size for each VM (bytes)" + type = number + default = 21474836480 # 20GB +}