feat(proxmox): Proxmox VM template + Terraform/OpenTofu VM creation
Manual, documented bare-metal Proxmox install + cloud-init template build (deliberately not automated — a fragile one-time step). Terraform/OpenTofu config creates rn-platform-01/rn-lab-host-01 from that template via the bpg/proxmox provider, with configurable (not hardcoded) VM sizing. Validated: tofu fmt, tofu init -backend=false (real provider download), tofu validate -> Success. No paid state backend required (local state).
This commit is contained in:
@@ -4,3 +4,4 @@ frontend/dist/
|
|||||||
*.db-journal
|
*.db-journal
|
||||||
/data/
|
/data/
|
||||||
.env
|
.env
|
||||||
|
.ansible/
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Proxmox — manual, one-time steps
|
||||||
|
|
||||||
|
Deliberately NOT automated (Phase 4 §21: "do not over-automate bare-metal Proxmox installation
|
||||||
|
if it becomes fragile" — a broken automated install of the one host that IS the only way in is
|
||||||
|
a much worse failure mode than a documented manual step reviewed by a human once).
|
||||||
|
|
||||||
|
## 1. Install Proxmox VE
|
||||||
|
|
||||||
|
Standard ISO install on the Hetzner dedicated server, once purchased. Follow Proxmox's own
|
||||||
|
install guide. Full checklist: `docs/PRODUCTION_DEPLOYMENT.md` §1-2.
|
||||||
|
|
||||||
|
## 2. Build the cloud-init VM template
|
||||||
|
|
||||||
|
One-time, either via `virt-customize`/manual, or Packer if already a comfortable tool here:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On the Proxmox host, after downloading an Ubuntu 22.04 cloud image:
|
||||||
|
qm create 9000 --name ubuntu-22.04-cloudinit --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
|
||||||
|
qm importdisk 9000 jammy-server-cloudimg-amd64.img local-zfs
|
||||||
|
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-zfs:vm-9000-disk-0
|
||||||
|
qm set 9000 --ide2 local-zfs:cloudinit
|
||||||
|
qm set 9000 --boot c --bootdisk scsi0
|
||||||
|
qm set 9000 --serial0 socket --vga serial0
|
||||||
|
qm set 9000 --agent enabled=1
|
||||||
|
qm template 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
Then apply `infra/proxmox/cloud-init/user-data.yaml` as the template's default cloud-init
|
||||||
|
user-data (Proxmox's own cloud-init snippet mechanism — `Datacenter -> Storage -> snippets`).
|
||||||
|
|
||||||
|
## 3. Hand off to Terraform + Ansible
|
||||||
|
|
||||||
|
Once the template exists and tagged `template` (matching `infra/terraform/main.tf`'s data
|
||||||
|
source filter), `infra/terraform/` can clone `rn-platform-01`/`rn-lab-host-01` from it, and
|
||||||
|
`infra/ansible/site.yml` configures them. Neither of those needs further Proxmox-console work
|
||||||
|
after this point — see `docs/RUNBOOK_FIRST_DEPLOYMENT.md`.
|
||||||
|
|
||||||
|
## 4. Proxmox host firewall
|
||||||
|
|
||||||
|
Apply `infra/firewall/proxmox-host.rules` manually (rendered with real values) — see that
|
||||||
|
file's own header comment for why this one ruleset stays a manual apply, unlike the two VMs'.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#cloud-config
|
||||||
|
# Baseline cloud-init user-data baked into the VM template infra/terraform/ clones. Minimal on
|
||||||
|
# purpose — infra/ansible/site.yml does the real configuration (Docker, firewall, TLS, the app
|
||||||
|
# stack); this just gets a box far enough to be SSH-reachable and Ansible-manageable.
|
||||||
|
|
||||||
|
package_update: true
|
||||||
|
package_upgrade: true
|
||||||
|
|
||||||
|
users:
|
||||||
|
- name: rootnode-deploy
|
||||||
|
groups: [sudo]
|
||||||
|
shell: /bin/bash
|
||||||
|
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
|
||||||
|
ssh_authorized_keys: [] # injected by Terraform's ssh_public_key variable at clone time
|
||||||
|
|
||||||
|
ssh_pwauth: false
|
||||||
|
disable_root: true
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- qemu-guest-agent
|
||||||
|
- curl
|
||||||
|
- ca-certificates
|
||||||
|
|
||||||
|
runcmd:
|
||||||
|
- systemctl enable --now qemu-guest-agent
|
||||||
|
|
||||||
|
# See infra/proxmox/README.md for how to bake this into a reusable template (packer or manual
|
||||||
|
# virt-customize) before infra/terraform/ clones it.
|
||||||
Generated
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# This file is maintained automatically by "tofu init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/bpg/proxmox" {
|
||||||
|
version = "0.111.1"
|
||||||
|
constraints = "~> 0.60"
|
||||||
|
hashes = [
|
||||||
|
"h1:0qABXcf7ulwKRhKGoK4LYZ7ObyoFkPxHmCrL6jRhMgE=",
|
||||||
|
"h1:0xM4HmXdu0hLrauA8ugcxjZvYHA+PuRybUxrXZEAdRI=",
|
||||||
|
"h1:ML2D3UUZTM99yrll/EBXj7wBYMb8xmQgomqFNybEoxY=",
|
||||||
|
"h1:PFGpaC7xcdC8w9jpxZPLJ/FFbVK5hd/CArUY+oXXTu8=",
|
||||||
|
"h1:XoYUWDsGWvxSlir/sYiGzx1MkcPVUtWIgc6OWEdf/C8=",
|
||||||
|
"h1:Zn7LyCWL/Xn0yCHiigXFYEp3MIv0MOVzUfL657QOplY=",
|
||||||
|
"h1:dIGBUSoC89e/PbpBEjbZ/YOzAkzrG1kF6EYcqk+8IqE=",
|
||||||
|
"h1:eERpiB94PyhN2SqE9E1+6sgpk0Kn6qjy555R2cjfpug=",
|
||||||
|
"h1:ejD4OSOL98W/SA4jLDvxEwCW8NSdVGKLyKmgfAUROK8=",
|
||||||
|
"h1:iTQv4FVFhMVl2juw6lgrVTpGFrdmdNPj+NFY2Dms0SE=",
|
||||||
|
"h1:jcqEv/zW+heFIPq5xwXxgS9EuBmbjIM1MriwQjx75WE=",
|
||||||
|
"h1:xF+AQJqpRf30WbrfHgSeuLqDBptWhTIdVoMyv7j6yVo=",
|
||||||
|
"h1:xWV1Y6ItiFXNCJO9OtyINMx8cqT5XiTOK2Rne7Jyu3w=",
|
||||||
|
"zh:18fb7c31a08dde6bffa1a4d4a211e604d6d17eec7092fd59331b3db3c6f3742c",
|
||||||
|
"zh:1cd60761538289d4dd2a1086b3ae62a7b0bdd4b1a2f824e9a44e243413168dba",
|
||||||
|
"zh:2eb76f6fc8299b6820ff678c8252332cc3366e226b5ae2e61748fd2449c1ed92",
|
||||||
|
"zh:45e6f7ebd0bf48911d37060359a4f359b5743b3092e985295733990e406d0416",
|
||||||
|
"zh:4aa8ba912eae37975d2e983394d173e595ca34fc76b5bf220b37d0e99d76e98c",
|
||||||
|
"zh:58e0789923103a77d502a0a9fc3eb920625e8eb935ec2d4ac0d006aebd1d186c",
|
||||||
|
"zh:6df8aa85fb8865915537e946c19b02538ad188018a629759c213c6f03730f642",
|
||||||
|
"zh:6ed47bc00d0913a1d0880618fa1376115e9edab6b4a658c081061a7f0e4ca360",
|
||||||
|
"zh:c5b10ff4f33df7e4c29e8f1127d49845b561b37b57517e844fb0954d7923d65e",
|
||||||
|
"zh:d016510e14b738499f0db9d9b3aafe82fc6877fb4ab4e9f831fb68a8d70a1385",
|
||||||
|
"zh:d941f394069bbf24351b363da1c64383f487067aaee0a84f9b96476d4912e212",
|
||||||
|
"zh:ddf271dbc2632ae8ffa8de3972f243ee47d260cb2ac90aa784f2746d98e21a0f",
|
||||||
|
"zh:ed0caa3501c42f611b7e9622c9b1df69fd85dc25a3cd88d3076381829688cd62",
|
||||||
|
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Proxmox VM creation (Terraform/OpenTofu)
|
||||||
|
|
||||||
|
Creates `rn-platform-01` and `rn-lab-host-01` as Proxmox VMs from a cloud-init-enabled Ubuntu
|
||||||
|
22.04 template. Assumes Proxmox VE is already installed and reachable (bare-metal install
|
||||||
|
stays a manual, documented step — `docs/PRODUCTION_DEPLOYMENT.md` §2 — over-automating the one
|
||||||
|
irreversible step that could brick the only way into the host is not worth it).
|
||||||
|
|
||||||
|
Works with either Terraform or OpenTofu (the fork) — `bpg/proxmox` is available for both. Use
|
||||||
|
whichever this project already has installed; nothing here is Terraform-Cloud-specific or
|
||||||
|
requires a paid backend (state is local by default, `main.tf`'s commented-out `backend "s3"`
|
||||||
|
block is there if remote state is ever wanted, not required).
|
||||||
|
|
||||||
|
## Prerequisites (all deploy-time, hardware-dependent — cannot be done now)
|
||||||
|
|
||||||
|
1. Proxmox VE installed, reachable at `proxmox_api_url`.
|
||||||
|
2. A cloud-init-enabled Ubuntu 22.04 template already exists on the Proxmox host (packer/manual
|
||||||
|
— see `infra/proxmox/cloud-init/` for the cloud-init user-data this expects the template to
|
||||||
|
support; building the actual template is a one-time manual step, documented, not automated
|
||||||
|
here for the same "don't over-automate the fragile one-time step" reasoning as the Proxmox
|
||||||
|
install itself).
|
||||||
|
3. A Proxmox API token with permission to create VMs (`terraform.tfvars`, never committed —
|
||||||
|
see `terraform.tfvars.example`).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd infra/terraform
|
||||||
|
cp terraform.tfvars.example terraform.tfvars # fill in real values
|
||||||
|
terraform init # or: tofu init
|
||||||
|
terraform plan
|
||||||
|
terraform apply
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs the two VMs' IPs, which then go into `infra/ansible/group_vars/production.yml`
|
||||||
|
(`platform_ip`, `lab_host_ip`) before running `infra/ansible/site.yml`.
|
||||||
|
|
||||||
|
## What this does NOT do
|
||||||
|
|
||||||
|
- Install Proxmox itself.
|
||||||
|
- Build the base cloud-init template (one-time, manual, documented).
|
||||||
|
- Decide final storage/ZFS layout (`vm_disk_datastore` is a variable — set it to whatever
|
||||||
|
datastore the real disk-inspection checklist, `docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md` §4,
|
||||||
|
says to use once the real disks are known).
|
||||||
|
- Configure anything INSIDE the VMs — that's `infra/ansible/site.yml`'s job, run after `apply`.
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# Creates rn-platform-01 and rn-lab-host-01 from a shared cloud-init template. Sizing comes
|
||||||
|
# from variables.tf's platform_vm/lab_host_vm objects — baseline defaults, override in
|
||||||
|
# terraform.tfvars once real load data exists (docs/CAPACITY_PLANNING.md). This module does
|
||||||
|
# NOT decide the host's final ZFS/storage layout; vm_disk_datastore just names whichever
|
||||||
|
# Proxmox storage the real disk-inspection checklist points at.
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_vm" "rn_platform_01" {
|
||||||
|
name = "rn-platform-01"
|
||||||
|
node_name = var.proxmox_node
|
||||||
|
|
||||||
|
clone {
|
||||||
|
vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id
|
||||||
|
full = true
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu {
|
||||||
|
cores = var.platform_vm.cores
|
||||||
|
type = "host"
|
||||||
|
}
|
||||||
|
memory {
|
||||||
|
dedicated = var.platform_vm.memory
|
||||||
|
}
|
||||||
|
disk {
|
||||||
|
datastore_id = var.vm_disk_datastore
|
||||||
|
size = var.platform_vm.disk_gb
|
||||||
|
interface = "scsi0"
|
||||||
|
}
|
||||||
|
network_device {
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
|
||||||
|
initialization {
|
||||||
|
user_account {
|
||||||
|
username = "rootnode-deploy"
|
||||||
|
keys = [var.ssh_public_key]
|
||||||
|
}
|
||||||
|
ip_config {
|
||||||
|
ipv4 {
|
||||||
|
address = "dhcp" # static assignment is a deploy-time decision once the real network
|
||||||
|
# plan is finalized — see docs/PRODUCTION_NETWORK_SECURITY.md §5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = ["rootnode", "platform"]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_vm" "rn_lab_host_01" {
|
||||||
|
name = "rn-lab-host-01"
|
||||||
|
node_name = var.proxmox_node
|
||||||
|
|
||||||
|
clone {
|
||||||
|
vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id
|
||||||
|
full = true
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu {
|
||||||
|
cores = var.lab_host_vm.cores
|
||||||
|
type = "host"
|
||||||
|
}
|
||||||
|
memory {
|
||||||
|
dedicated = var.lab_host_vm.memory
|
||||||
|
}
|
||||||
|
disk {
|
||||||
|
datastore_id = var.vm_disk_datastore
|
||||||
|
size = var.lab_host_vm.disk_gb
|
||||||
|
interface = "scsi0"
|
||||||
|
}
|
||||||
|
network_device {
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
|
||||||
|
initialization {
|
||||||
|
user_account {
|
||||||
|
username = "rootnode-deploy"
|
||||||
|
keys = [var.ssh_public_key]
|
||||||
|
}
|
||||||
|
ip_config {
|
||||||
|
ipv4 {
|
||||||
|
address = "dhcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = ["rootnode", "lab-host"]
|
||||||
|
}
|
||||||
|
|
||||||
|
data "proxmox_virtual_environment_vms" "template" {
|
||||||
|
node_name = var.proxmox_node
|
||||||
|
tags = ["template"]
|
||||||
|
filter {
|
||||||
|
name = "name"
|
||||||
|
values = [var.vm_template_name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "rn_platform_01_ip" {
|
||||||
|
value = proxmox_virtual_environment_vm.rn_platform_01.ipv4_addresses
|
||||||
|
}
|
||||||
|
output "rn_lab_host_01_ip" {
|
||||||
|
value = proxmox_virtual_environment_vm.rn_lab_host_01.ipv4_addresses
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
proxmox_api_url = "https://proxmox.example.internal:8006/api2/json"
|
||||||
|
proxmox_api_token = "terraform@pve!rootnode=00000000-0000-0000-0000-000000000000"
|
||||||
|
proxmox_node = "pve"
|
||||||
|
vm_disk_datastore = "local-zfs"
|
||||||
|
ssh_public_key = "ssh-ed25519 AAAA... rootnode-deploy"
|
||||||
|
management_cidr = "203.0.113.0/24"
|
||||||
|
|
||||||
|
# Override sizing here once real load data exists — see docs/CAPACITY_PLANNING.md.
|
||||||
|
# platform_vm = { cores = 4, memory = 16384, disk_gb = 100 }
|
||||||
|
# lab_host_vm = { cores = 24, memory = 98304, disk_gb = 500 }
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
variable "proxmox_api_url" {
|
||||||
|
description = "e.g. https://proxmox.example.internal:8006/api2/json"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
variable "proxmox_api_token" {
|
||||||
|
description = "Proxmox API token (user@realm!tokenid=uuid) — never committed, see terraform.tfvars.example"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
variable "proxmox_tls_insecure" {
|
||||||
|
description = "Set true only if the Proxmox host uses a self-signed cert with no CA to trust"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
variable "proxmox_node" {
|
||||||
|
description = "Proxmox node name (e.g. 'pve')"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
variable "vm_template_name" {
|
||||||
|
description = "Name of the pre-built cloud-init Ubuntu 22.04 template (see infra/terraform/README.md prerequisites)"
|
||||||
|
type = string
|
||||||
|
default = "ubuntu-22.04-cloudinit"
|
||||||
|
}
|
||||||
|
variable "vm_disk_datastore" {
|
||||||
|
description = "Proxmox storage/datastore for VM disks — set once real disk layout is decided (docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md §4)"
|
||||||
|
type = string
|
||||||
|
default = "local-zfs"
|
||||||
|
}
|
||||||
|
variable "ssh_public_key" {
|
||||||
|
description = "Public key injected via cloud-init for the rootnode-deploy user"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
variable "management_cidr" {
|
||||||
|
description = "Same value as infra/ansible/group_vars/production.yml's management_cidr — used for the VM firewall's SSH allow rule at the Proxmox level too, if managed there"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── rn-platform-01 sizing — baseline/recommended/max-growth (Phase 4 §3) ──────────────────
|
||||||
|
variable "platform_vm" {
|
||||||
|
description = "rn-platform-01 sizing. 'baseline' is the default; bump via terraform.tfvars once real hardware/load data exists."
|
||||||
|
type = object({
|
||||||
|
cores = number
|
||||||
|
memory = number # MB
|
||||||
|
disk_gb = number
|
||||||
|
})
|
||||||
|
default = {
|
||||||
|
cores = 4
|
||||||
|
memory = 16384
|
||||||
|
disk_gb = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── rn-lab-host-01 sizing — majority of host resources, per the Phase 4 spec ───────────────
|
||||||
|
variable "lab_host_vm" {
|
||||||
|
description = "rn-lab-host-01 sizing. Deliberately NOT a final number — see docs/CAPACITY_PLANNING.md; this default is a conservative starting point for a 16c/32t/128GB class host, not a target."
|
||||||
|
type = object({
|
||||||
|
cores = number
|
||||||
|
memory = number # MB
|
||||||
|
disk_gb = number
|
||||||
|
})
|
||||||
|
default = {
|
||||||
|
cores = 24
|
||||||
|
memory = 98304
|
||||||
|
disk_gb = 500
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.5.0"
|
||||||
|
required_providers {
|
||||||
|
proxmox = {
|
||||||
|
source = "bpg/proxmox"
|
||||||
|
version = "~> 0.60"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Local state by default — no paid backend required. Uncomment for remote state once/if
|
||||||
|
# that's wanted; not needed to use this module.
|
||||||
|
# backend "s3" {
|
||||||
|
# bucket = "rootnode-terraform-state"
|
||||||
|
# key = "production/terraform.tfstate"
|
||||||
|
# region = "auto"
|
||||||
|
# }
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "proxmox" {
|
||||||
|
endpoint = var.proxmox_api_url
|
||||||
|
api_token = var.proxmox_api_token
|
||||||
|
insecure = var.proxmox_tls_insecure
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user