├── .gitignore ├── README.md ├── main.tf ├── modules └── vm │ ├── main.tf │ └── variables.tf ├── outputs.tf └── ubuntu-template-creator.sh /.gitignore: -------------------------------------------------------------------------------- 1 | +*.tfstate 2 | +*.tfstate.backup 3 | +*.tfvars 4 | +.terraform/ 5 | +*.pem -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proxmox VM OpenTofu / Terraform Module 2 | 3 | This module helps you easily create and manage multiple Virtual Machines in Proxmox using OpenTofu or Terraform. It supports multiple disks, network interfaces with VLAN tagging, and IP configurations. 4 | 5 | ## Creating Ubuntu Cloud Image Template 6 | 7 | Copy/clone the script to your Proxmox server, make it executable `chmod +x ubuntu-template-creator.sh`, then run the script from the Proxmox console or SSH `./ubuntu-template-creator.sh`. Choose your storage, Ubuntu flavor, and let the script do the rest for you. Adjust the template ID in your main.tf to use it for deploying VMs. 8 | 9 | ```bash 10 | ============================================ 11 | Proxmox Ubuntu Template Creator Script 12 | ============================================ 13 | 14 | This script will help you create an Ubuntu cloud image template on your Proxmox server. 15 | You will be prompted for some configuration options. 16 | 17 | 18 | ===== Prerequisites Check ===== 19 | ==> Checking for required packages... 20 | ==> libguestfs-tools is already installed. 21 | 22 | ===== Configuration Options ===== 23 | Available storages on this Proxmox server: 24 | - SSD 25 | - local-lvm 26 | 27 | Enter the storage name to use [default: local-zfs]: SSD 28 | ==> Using storage: SSD 29 | Enter VM ID for the template [default: 9000]: 9001 30 | ==> Using VM ID: 9001 31 | Enter a name for the template [default: ubuntu-cloud-template]: ubuntu-template 32 | ==> Using VM name: ubuntu-template 33 | 34 | Available Ubuntu versions: 35 | 1) Plucky (24.10) [default] 36 | 2) Noble (24.04 LTS) 37 | 3) Jammy (22.04 LTS) 38 | 4) Focal (20.04 LTS) 39 | Select Ubuntu version [1-4]: 1 40 | ==> Selected Ubuntu Plucky (24.10) 41 | Enter memory size in MB [default: 2048]: 42 | Enter number of CPU cores [default: 2]: 43 | ==> VM Resources: 2048 MB RAM, 2 cores 44 | 45 | ===== Review Configuration ===== 46 | Please review your settings: 47 | Storage: SSD 48 | VM ID: 9001 49 | VM Name: ubuntu-template 50 | Ubuntu: plucky 51 | Memory: 2048 MB 52 | CPU Cores: 2 53 | 54 | ``` 55 | 56 | ## Getting Started 57 | 58 | ### Prerequisites 59 | 60 | 1. Install [OpenTofu](https://opentofu.org/docs/intro/install/) (version 1.0.0 or later) 61 | 2. Have access to a Proxmox server 62 | 63 | ### Setting Up Your Project 64 | 65 | There are two ways to use this module: 66 | 67 | #### Method 1: Clone the Repository 68 | 69 | ```bash 70 | # Clone the repository 71 | git clone https://github.com/dinodem/terraform-proxmox.git 72 | cd terraform-proxmox 73 | 74 | # Create your configuration file 75 | touch main.tf 76 | 77 | # Open main.tf in your editor and add your configuration 78 | ``` 79 | 80 | ### Authentication with Proxmox 81 | Read more here: https://registry.terraform.io/providers/bpg/proxmox/latest/docs#authentication-methods-comparison 82 | Before using this module, you need to provide your Proxmox credentials. You have three options: 83 | 84 | #### Option 1: Environment Variables (Recommended for Development) 85 | 86 | ```bash 87 | export PROXMOX_VE_ENDPOINT="https://your-proxmox-server:8006" 88 | export PROXMOX_VE_USERNAME="root@pam" 89 | export PROXMOX_VE_PASSWORD="your-password-here" 90 | export PROXMOX_VE_INSECURE="true" # Only use if you have self-signed certificates 91 | ``` 92 | 93 | #### Option 2: OpenTofu Variables 94 | 95 | 1. Define variables in `variables.tf`: 96 | 97 | ```terraform 98 | variable "proxmox_endpoint" { 99 | description = "The Proxmox API endpoint" 100 | type = string 101 | } 102 | 103 | variable "proxmox_username" { 104 | description = "Username for Proxmox authentication" 105 | type = string 106 | } 107 | 108 | variable "proxmox_password" { 109 | description = "Password for Proxmox authentication" 110 | type = string 111 | sensitive = true 112 | } 113 | 114 | variable "proxmox_insecure" { 115 | description = "Skip TLS verification" 116 | type = bool 117 | default = false 118 | } 119 | ``` 120 | 121 | 2. Use the variables in `main.tf`: 122 | 123 | ```terraform 124 | provider "proxmox" { 125 | endpoint = var.proxmox_endpoint 126 | username = var.proxmox_username 127 | password = var.proxmox_password 128 | insecure = var.proxmox_insecure 129 | } 130 | ``` 131 | 132 | 3. Create a `terraform.tfvars` file (add to .gitignore): 133 | 134 | ```terraform 135 | proxmox_endpoint = "https://your-proxmox-server:8006" 136 | proxmox_username = "root@pam" 137 | proxmox_password = "your-password-here" 138 | proxmox_insecure = true 139 | ``` 140 | 141 | #### Option 3: API Tokens (Recommended for Production) 142 | 143 | ```bash 144 | export PROXMOX_VE_ENDPOINT="https://your-proxmox-server:8006" 145 | export PROXMOX_VE_API_TOKEN="terraform@pve!provider=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 146 | ``` 147 | 148 | ## Using the Module 149 | 150 | ### Basic Configuration 151 | 152 | Create a `main.tf` file in your project root: 153 | 154 | ```terraform 155 | terraform { 156 | required_providers { 157 | proxmox = { 158 | source = "bpg/proxmox" 159 | version = "0.71.0" 160 | } 161 | } 162 | } 163 | 164 | provider "proxmox" { 165 | endpoint = "https://your-proxmox-server:8006" 166 | insecure = true 167 | # Authentication will use environment variables 168 | } 169 | 170 | module "proxmox_vms" { 171 | source = "./modules/vm" 172 | 173 | node_name = "pve" # Your Proxmox node name 174 | vm_password = "your-vm-password" # Or use random_password 175 | 176 | vm_configs = { 177 | "web-server" = { 178 | vm_id = 100 179 | memory = 4096 180 | cpu_cores = 2 181 | cpu_type = "host" 182 | 183 | disks = [ 184 | { 185 | interface = "scsi0" 186 | size = 20 # 20GB 187 | file_format = "raw" 188 | datastore_id = "local-lvm" 189 | } 190 | ] 191 | 192 | network_devices = [ 193 | { 194 | bridge = "vmbr0" 195 | model = "virtio" 196 | enabled = true 197 | vlan = null # No VLAN tagging 198 | } 199 | ] 200 | 201 | ip_configs = [ 202 | { 203 | address = "192.168.1.100/24" 204 | gateway = "192.168.1.1" 205 | } 206 | ] 207 | 208 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."] 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | ### Running OpenTofu Commands 215 | 216 | ```bash 217 | # Initialize 218 | tofu init 219 | 220 | # Preview changes 221 | tofu plan 222 | 223 | # Apply changes 224 | tofu apply 225 | 226 | # Remove resources 227 | tofu destroy 228 | ``` 229 | 230 | ## Advanced Features 231 | 232 | ### Multiple Disks 233 | 234 | ```terraform 235 | disks = [ 236 | { 237 | interface = "scsi0" 238 | size = 20 # System disk (20GB) 239 | file_format = "raw" 240 | datastore_id = "SSD" 241 | }, 242 | { 243 | interface = "scsi1" 244 | size = 100 # Data disk (100GB) 245 | file_format = "raw" 246 | datastore_id = "HDD" 247 | } 248 | ] 249 | ``` 250 | 251 | ### Multiple Network Interfaces with VLAN Support 252 | 253 | ```terraform 254 | network_devices = [ 255 | { 256 | bridge = "vmbr0" 257 | model = "virtio" 258 | enabled = true 259 | vlan = 100 # Assign to VLAN 100 260 | }, 261 | { 262 | bridge = "vmbr0" 263 | model = "virtio" 264 | enabled = true 265 | vlan = 200 # Assign to VLAN 200 266 | } 267 | ] 268 | 269 | # Each network device needs its own IP configuration 270 | ip_configs = [ 271 | { 272 | address = "192.168.100.10/24" # IP on VLAN 100 273 | gateway = "192.168.100.1" 274 | }, 275 | { 276 | address = "192.168.200.10/24" # IP on VLAN 200 277 | gateway = "192.168.200.1" 278 | } 279 | ] 280 | ``` 281 | 282 | ### Multiple VMs 283 | 284 | ```terraform 285 | vm_configs = { 286 | "web-server" = { 287 | vm_id = 100 288 | memory = 4096 289 | cpu_cores = 2 290 | # ... other configurations 291 | }, 292 | "database" = { 293 | vm_id = 101 294 | memory = 8192 295 | cpu_cores = 4 296 | # ... other configurations 297 | } 298 | } 299 | ``` 300 | 301 | ## Complete Example 302 | 303 | ```terraform 304 | terraform { 305 | required_providers { 306 | proxmox = { 307 | source = "bpg/proxmox" 308 | version = "0.71.0" 309 | } 310 | } 311 | } 312 | 313 | provider "proxmox" { 314 | endpoint = "https://10.10.0.198:8006" 315 | insecure = true 316 | # Authentication through environment variables 317 | } 318 | 319 | resource "random_password" "vm_password" { 320 | length = 32 321 | special = true 322 | } 323 | 324 | module "proxmox_vms" { 325 | source = "./modules/vm" 326 | vm_configs = { for name, config in local.vm_configs : 327 | name => merge(config, { vm_id = local.vm_ids[name] }) 328 | } 329 | node_name = "pve" 330 | vm_password = random_password.vm_password.result 331 | } 332 | 333 | locals { 334 | base_vm_id = 699 335 | vm_configs = { 336 | "web-server" = { 337 | memory = 4096 338 | cpu_cores = 2 339 | cpu_type = "host" 340 | 341 | disks = [ 342 | { 343 | interface = "scsi0" 344 | size = 20 345 | file_format = "raw" 346 | datastore_id = null # Uses default 347 | } 348 | ] 349 | 350 | network_devices = [ 351 | { 352 | bridge = "vmbr0" 353 | model = "virtio" 354 | enabled = true 355 | vlan = 10 # VLAN 10 for web servers 356 | } 357 | ] 358 | 359 | ip_configs = [ 360 | { 361 | address = "192.168.10.100/24" 362 | gateway = "192.168.10.1" 363 | } 364 | ] 365 | 366 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5..."] 367 | }, 368 | 369 | "database" = { 370 | memory = 8192 371 | cpu_cores = 4 372 | cpu_type = "host" 373 | 374 | disks = [ 375 | { 376 | interface = "scsi0" 377 | size = 40 378 | file_format = "raw" 379 | datastore_id = null 380 | }, 381 | { 382 | interface = "scsi1" 383 | size = 200 384 | file_format = "raw" 385 | datastore_id = "HDD" 386 | } 387 | ] 388 | 389 | network_devices = [ 390 | { 391 | bridge = "vmbr0" 392 | model = "virtio" 393 | enabled = true 394 | vlan = 20 # VLAN 20 for databases 395 | } 396 | ] 397 | 398 | ip_configs = [ 399 | { 400 | address = "192.168.20.100/24" 401 | gateway = "192.168.20.1" 402 | } 403 | ] 404 | 405 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5..."] 406 | } 407 | } 408 | 409 | # Generate VM IDs automatically 410 | vm_ids = { for idx, name in sort(keys(local.vm_configs)) : 411 | name => local.base_vm_id + idx 412 | } 413 | } 414 | ``` 415 | 416 | ## Configuration Reference 417 | 418 | ### VM Configuration Options 419 | 420 | | Setting | Description | Default | 421 | |---------|-------------|---------| 422 | | `vm_id` | Unique ID for the VM | Required | 423 | | `memory` | RAM in MB | Required | 424 | | `cpu_cores` | Number of CPU cores | Required | 425 | | `cpu_type` | CPU type | Required | 426 | | `disks` | List of disk objects | Required | 427 | | `network_devices` | List of network device objects | Required | 428 | | `ip_configs` | List of IP configuration objects | Required | 429 | | `ssh_keys` | List of SSH public keys | Required | 430 | | `dns_servers` | List of DNS servers | `["1.1.1.1", "1.0.0.1"]` | 431 | | `vga_type` | VGA type | `"serial0"` | 432 | | `vga_memory` | VGA memory in MB | `16` | 433 | | `template_vm_id` | Template VM ID to clone from | `9000` | 434 | 435 | ### Disk Configuration 436 | 437 | | Setting | Description | Default | 438 | |---------|-------------|---------| 439 | | `interface` | Disk interface (e.g., scsi0, scsi1) | Required | 440 | | `size` | Disk size in GB | Required | 441 | | `file_format` | Disk format | `"raw"` | 442 | | `datastore_id` | Storage location | Module default | 443 | 444 | ### Network Device Configuration 445 | 446 | | Setting | Description | Default | 447 | |---------|-------------|---------| 448 | | `bridge` | Network bridge | `"vmbr0"` | 449 | | `model` | Network device model | `"virtio"` | 450 | | `enabled` | Whether network is enabled | `true` | 451 | | `vlan` | VLAN tag (1-4094) | `null` (no VLAN) | 452 | 453 | ### IP Configuration 454 | 455 | | Setting | Description | Default | 456 | |---------|-------------|---------| 457 | | `address` | IP address with subnet (CIDR notation) | Required | 458 | | `gateway` | Gateway IP address | Required | 459 | 460 | ## Retrieving VM Password 461 | 462 | If you used `random_password` to generate a password, you can see it with: 463 | 464 | ```bash 465 | tofu output -raw vm_password 466 | ``` -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | proxmox = { 4 | source = "bpg/proxmox" 5 | version = "0.76.0" 6 | } 7 | } 8 | } 9 | 10 | provider "proxmox" { 11 | endpoint = "https://10.10.0.198:8006" 12 | insecure = true 13 | } 14 | 15 | resource "random_password" "vm_password" { 16 | length = 32 17 | special = true 18 | } 19 | 20 | module "proxmox_vms" { 21 | source = "./modules/vm" 22 | vm_configs = { for name, config in local.vm_configs : 23 | name => merge(config, { vm_id = local.vm_ids[name] }) 24 | } 25 | node_name = "pve" ## Set your node name. 26 | vm_password = random_password.vm_password.result 27 | # vm_username = "username" ## Uncomment to override default username from variables ubuntu 28 | } 29 | 30 | locals { 31 | base_vm_id = 799 32 | vm_configs = { 33 | "test-vpn" = { 34 | memory = 4048 35 | cpu_cores = 2 36 | cpu_type = "x86-64-v2-AES" 37 | 38 | # Multiple disks configuration 39 | disks = [ 40 | { 41 | interface = "scsi0" 42 | size = 45 43 | file_format = "raw" 44 | datastore_id = "SSD" 45 | }, 46 | { 47 | interface = "scsi1" 48 | size = 25 49 | file_format = "raw" 50 | datastore_id = "SSD" # Specific datastore for this disk 51 | } 52 | ] 53 | 54 | # Multiple network devices 55 | network_devices = [ 56 | { 57 | bridge = "vmbr0" 58 | model = "virtio" 59 | enabled = true 60 | vlan_id = 100 # Support for VLAN ID 61 | }, 62 | { 63 | bridge = "vmbr0" 64 | model = "virtio" 65 | enabled = true 66 | } 67 | ] 68 | # Multiple IP configurations 69 | # This is a list of objects, each containing address and gateway 70 | ip_configs = [ 71 | { 72 | address = "10.10.0.193/24" 73 | gateway = "10.10.0.1" 74 | }, 75 | { 76 | address = "10.10.0.194/24" 77 | gateway = "10.10.0.1" 78 | } 79 | ] 80 | 81 | ssh_keys = ["ssh-ed25519 AAAAC3NzaC1l...@example.com"] 82 | dns_servers = ["10.10.0.237"] ## Comment out if you want to use default value from variables 1.1.1.1, 1.0.0.1 83 | # vga_type = "serial0" ## Uncomment to override default value for vga_type 84 | # vga_memory = 16 ## Uncomment to override default value for vga_memory 85 | template_vm_id = 9001 ### Comment out if you want to use default value from variables 86 | } 87 | } 88 | 89 | # Generate VM IDs sequentially 90 | vm_ids = { for idx, name in sort(keys(local.vm_configs)) : 91 | name => local.base_vm_id + idx 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /modules/vm/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | proxmox = { 4 | source = "bpg/proxmox" 5 | version = "0.76.0" 6 | } 7 | } 8 | } 9 | 10 | resource "proxmox_virtual_environment_vm" "vm" { 11 | for_each = var.vm_configs 12 | 13 | name = each.key 14 | node_name = var.node_name 15 | vm_id = each.value.vm_id 16 | 17 | clone { 18 | vm_id = coalesce(each.value.template_vm_id, var.template_vm_id) 19 | } 20 | 21 | memory { 22 | dedicated = each.value.memory 23 | } 24 | 25 | cpu { 26 | cores = each.value.cpu_cores 27 | type = each.value.cpu_type 28 | } 29 | 30 | # Dynamic block for multiple disks 31 | dynamic "disk" { 32 | for_each = each.value.disks 33 | content { 34 | datastore_id = disk.value.datastore_id != null ? disk.value.datastore_id : var.datastore_id 35 | interface = disk.value.interface 36 | size = disk.value.size 37 | file_format = disk.value.file_format 38 | } 39 | } 40 | 41 | vga { 42 | type = coalesce(each.value.vga_type, var.vga_type) 43 | memory = coalesce(each.value.vga_memory, var.vga_memory) 44 | } 45 | 46 | # Dynamic block for multiple network devices 47 | dynamic "network_device" { 48 | for_each = each.value.network_devices 49 | content { 50 | bridge = network_device.value.bridge != null ? network_device.value.bridge : "vmbr0" 51 | model = network_device.value.model != null ? network_device.value.model : "virtio" 52 | enabled = network_device.value.enabled != null ? network_device.value.enabled : true 53 | vlan_id = network_device.value.vlan_id # Use the vlan_id attribute 54 | } 55 | } 56 | 57 | agent { 58 | enabled = true 59 | } 60 | 61 | initialization { 62 | # Dynamic block for multiple IP configurations 63 | dynamic "ip_config" { 64 | for_each = each.value.ip_configs 65 | content { 66 | ipv4 { 67 | address = ip_config.value.address 68 | gateway = ip_config.value.gateway 69 | } 70 | } 71 | } 72 | 73 | dns { 74 | servers = coalesce(each.value.dns_servers, var.dns_servers) 75 | } 76 | 77 | user_account { 78 | username = var.vm_username 79 | password = var.vm_password 80 | keys = each.value.ssh_keys 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /modules/vm/variables.tf: -------------------------------------------------------------------------------- 1 | variable "dns_servers" { 2 | type = list(string) 3 | default = ["1.1.1.1", "1.0.0.1"] # Default DNS servers 4 | description = "DNS servers for the VMs" 5 | } 6 | 7 | variable "vga_type" { 8 | type = string 9 | default = "serial0" 10 | description = "Default VGA type for VMs" 11 | } 12 | 13 | variable "vga_memory" { 14 | type = number 15 | default = 16 16 | description = "Default VGA memory for VMs" 17 | } 18 | 19 | variable "node_name" { 20 | type = string 21 | } 22 | 23 | variable "datastore_id" { 24 | type = string 25 | default = "SSD" 26 | } 27 | 28 | variable "vm_password" { 29 | type = string 30 | } 31 | 32 | variable "vm_username" { 33 | type = string 34 | default = "ubuntu" 35 | } 36 | 37 | variable "template_vm_id" { 38 | type = number 39 | default = 9000 # Default template VM ID 40 | description = "Default template VM ID for cloning" 41 | } 42 | 43 | # Define the disk object 44 | variable "vm_configs" { 45 | type = map(object({ 46 | vm_id = number 47 | memory = number 48 | cpu_cores = number 49 | cpu_type = string 50 | 51 | 52 | disks = list(object({ 53 | datastore_id = optional(string) 54 | interface = string 55 | size = number 56 | file_format = optional(string, "raw") 57 | })) 58 | 59 | network_devices = list(object({ 60 | bridge = optional(string) 61 | model = optional(string) 62 | enabled = optional(bool) 63 | vlan_id = optional(number) 64 | })) 65 | 66 | ip_configs = list(object({ 67 | address = string 68 | gateway = string 69 | })) 70 | 71 | dns_servers = optional(list(string)) 72 | ssh_keys = list(string) 73 | vga_type = optional(string) 74 | vga_memory = optional(number) 75 | template_vm_id = optional(number) 76 | })) 77 | description = "Map of VM configurations" 78 | } -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "vm_ipv4_addresses" { 2 | value = { 3 | for name, vm in module.proxmox_vms : name => vm.vm_ipv4_address 4 | } 5 | } 6 | 7 | output "vm_password" { 8 | value = random_password.vm_password.result 9 | sensitive = true 10 | } 11 | 12 | output "get_sensitive_data" { 13 | value = "Run the command tofu output -json | jq .vm_password to get the random password." 14 | } 15 | -------------------------------------------------------------------------------- /ubuntu-template-creator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Proxmox Ubuntu Cloud Image Template Creator Script 3 | # Run this from the Proxmox console or SSH 4 | # Created by [Dino Demirovic] - https://github.com/dinodem 5 | GREEN='\033[0;32m' 6 | BLUE='\033[0;34m' 7 | YELLOW='\033[1;33m' 8 | RED='\033[0;31m' 9 | NC='\033[0m' 10 | 11 | 12 | print_message() { 13 | echo -e "${BLUE}==>${NC} $1" 14 | } 15 | 16 | print_step() { 17 | echo -e "\n${GREEN}===== $1 =====${NC}" 18 | } 19 | 20 | print_warning() { 21 | echo -e "${YELLOW}Warning:${NC} $1" 22 | } 23 | 24 | print_error() { 25 | echo -e "${RED}Error:${NC} $1" 26 | } 27 | 28 | 29 | check_success() { 30 | if [ $? -ne 0 ]; then 31 | print_error "$1" 32 | exit 1 33 | fi 34 | } 35 | 36 | 37 | clear 38 | echo -e "${GREEN}============================================${NC}" 39 | echo -e "${GREEN} Proxmox Ubuntu Template Creator Script ${NC}" 40 | echo -e "${GREEN}============================================${NC}" 41 | echo -e "\nThis script will help you create an Ubuntu cloud image template on your Proxmox server." 42 | echo -e "You will be prompted for some configuration options.\n" 43 | 44 | 45 | if [ "$EUID" -ne 0 ]; then 46 | print_warning "This script needs to be run with sudo or as root." 47 | echo -e "Please run it again with: ${YELLOW}sudo $0${NC}" 48 | exit 1 49 | fi 50 | 51 | 52 | print_step "Prerequisites Check" 53 | print_message "Checking for required packages..." 54 | 55 | 56 | if ! dpkg -l | grep -q libguestfs-tools; then 57 | print_message "Installing libguestfs-tools..." 58 | apt update -y 59 | apt install libguestfs-tools -y 60 | check_success "Failed to install libguestfs-tools" 61 | else 62 | print_message "libguestfs-tools is already installed." 63 | fi 64 | 65 | 66 | print_step "Configuration Options" 67 | 68 | 69 | echo -e "Available storages on this Proxmox server:" 70 | pvesm status | grep -v "local\s" | awk 'NR>1 {print " - " $1}' 71 | echo "" 72 | 73 | read -p "Enter the storage name to use [default: local-zfs]: " STORAGE 74 | STORAGE=${STORAGE:-local-zfs} 75 | print_message "Using storage: $STORAGE" 76 | 77 | 78 | read -p "Enter VM ID for the template [default: 9000]: " VM_ID 79 | VM_ID=${VM_ID:-9000} 80 | print_message "Using VM ID: $VM_ID" 81 | 82 | 83 | read -p "Enter a name for the template [default: ubuntu-cloud-template]: " VM_NAME 84 | VM_NAME=${VM_NAME:-ubuntu-cloud-template} 85 | print_message "Using VM name: $VM_NAME" 86 | 87 | 88 | echo -e "\nAvailable Ubuntu versions:" 89 | echo " 1) Plucky (24.10) [default]" 90 | echo " 2) Noble (24.04 LTS)" 91 | echo " 3) Jammy (22.04 LTS)" 92 | echo " 4) Focal (20.04 LTS)" 93 | read -p "Select Ubuntu version [1-4]: " VERSION_CHOICE 94 | case $VERSION_CHOICE in 95 | 2) 96 | UBUNTU_VERSION="noble" 97 | print_message "Selected Ubuntu Noble (24.04 LTS)" 98 | UBUNTU_PATH="${UBUNTU_VERSION}/current" 99 | ;; 100 | 3) 101 | UBUNTU_VERSION="jammy" 102 | print_message "Selected Ubuntu Jammy (22.04 LTS)" 103 | UBUNTU_PATH="${UBUNTU_VERSION}/current" 104 | ;; 105 | 4) 106 | UBUNTU_VERSION="focal" 107 | print_message "Selected Ubuntu Focal (20.04 LTS)" 108 | UBUNTU_PATH="${UBUNTU_VERSION}/current" 109 | ;; 110 | *) 111 | UBUNTU_VERSION="plucky" 112 | print_message "Selected Ubuntu Plucky (24.10)" 113 | UBUNTU_PATH="${UBUNTU_VERSION}/current" 114 | ;; 115 | esac 116 | 117 | 118 | read -p "Enter memory size in MB [default: 2048]: " VM_MEMORY 119 | VM_MEMORY=${VM_MEMORY:-2048} 120 | 121 | read -p "Enter number of CPU cores [default: 2]: " VM_CORES 122 | VM_CORES=${VM_CORES:-2} 123 | 124 | print_message "VM Resources: $VM_MEMORY MB RAM, $VM_CORES cores" 125 | 126 | 127 | print_step "Review Configuration" 128 | echo -e "Please review your settings:" 129 | echo -e " Storage: ${YELLOW}$STORAGE${NC}" 130 | echo -e " VM ID: ${YELLOW}$VM_ID${NC}" 131 | echo -e " VM Name: ${YELLOW}$VM_NAME${NC}" 132 | echo -e " Ubuntu: ${YELLOW}$UBUNTU_VERSION${NC}" 133 | echo -e " Memory: ${YELLOW}$VM_MEMORY MB${NC}" 134 | echo -e " CPU Cores: ${YELLOW}$VM_CORES${NC}" 135 | echo "" 136 | 137 | read -p "Do you want to proceed with these settings? (y/n) [default: y]: " CONFIRM 138 | CONFIRM=${CONFIRM:-y} 139 | 140 | if [[ $CONFIRM != [Yy]* ]]; then 141 | print_message "Template creation cancelled." 142 | exit 0 143 | fi 144 | 145 | 146 | print_step "Creating Ubuntu Template" 147 | 148 | IMAGE_FILE="${UBUNTU_VERSION}-server-cloudimg-amd64.img" 149 | IMAGE_URL="https://cloud-images.ubuntu.com/${UBUNTU_PATH}/${IMAGE_FILE}" 150 | 151 | print_message "Downloading Ubuntu cloud image from ${IMAGE_URL}..." 152 | wget "$IMAGE_URL" 153 | check_success "Failed to download the cloud image" 154 | 155 | print_message "Installing qemu-guest-agent in the image..." 156 | virt-customize -a "$IMAGE_FILE" --install qemu-guest-agent 157 | check_success "Failed to install qemu-guest-agent" 158 | 159 | 160 | print_message "Creating VM with ID $VM_ID..." 161 | qm create $VM_ID --name "$VM_NAME" --memory $VM_MEMORY --cores $VM_CORES --net0 virtio,bridge=vmbr0 162 | check_success "Failed to create VM" 163 | 164 | print_message "Importing disk to $STORAGE..." 165 | qm importdisk $VM_ID "$IMAGE_FILE" $STORAGE 166 | check_success "Failed to import disk" 167 | 168 | print_message "Configuring VM settings..." 169 | qm set $VM_ID --scsihw virtio-scsi-pci --scsi0 ${STORAGE}:vm-${VM_ID}-disk-0 170 | check_success "Failed to configure disk" 171 | 172 | qm set $VM_ID --boot c --bootdisk scsi0 173 | check_success "Failed to configure boot options" 174 | 175 | qm set $VM_ID --ide2 ${STORAGE}:cloudinit 176 | check_success "Failed to add cloudinit drive" 177 | 178 | qm set $VM_ID --serial0 socket --vga serial0 179 | check_success "Failed to configure serial port" 180 | 181 | qm set $VM_ID --agent enabled=1 182 | check_success "Failed to enable qemu-guest-agent" 183 | 184 | print_message "Converting VM to template..." 185 | qm template $VM_ID 186 | check_success "Failed to convert VM to template" 187 | 188 | 189 | print_message "Cleaning up downloaded files..." 190 | rm "$IMAGE_FILE" 191 | check_success "Failed to clean up files" 192 | 193 | 194 | print_step "Template Creation Complete" 195 | echo -e "Your Ubuntu ${YELLOW}$UBUNTU_VERSION${NC} template has been created successfully!" 196 | echo -e "Template ID: ${YELLOW}$VM_ID${NC}" 197 | echo -e "Template Name: ${YELLOW}$VM_NAME${NC}" 198 | echo -e "\nYou can now use this template to create new VMs using OpenTofu with this module." 199 | echo -e "Make sure to set ${YELLOW}template_vm_id = $VM_ID${NC} in your tofu / terraform configuration.\n" 200 | 201 | echo -e "${GREEN}Thank you for using the Proxmox Ubuntu Template Creator!${NC}" --------------------------------------------------------------------------------