feat(opentofu): define bare-metal host configuration

This commit is contained in:
Tibo De Peuter 2026-07-27 20:25:39 +02:00
parent 06500a8f01
commit 89cec4e0f3
Signed by: tdpeuter
SSH key fingerprint: SHA256:u/h/LVoqKF1Iz02uOyxe6hcjmoZASCGV2HM0TG9ZMoU
5 changed files with 169 additions and 72 deletions

View file

@ -42,7 +42,7 @@ The current OpenTofu configuration assumes that your system has a 2TB disk attac
It is recommended to use the persistent hardware ID (e.g., `wwn-0x...`, `nvme-eui...`, or `ata-...`) instead of `/dev/sdb` because the latter can change between reboots.
2. **Action Required:** Provide this ID to the system or update the `opentofu/nodes/pve-new/main.tf` configuration with this ID.
2. **Action Required:** Provide this ID to the system or update the `opentofu/nodes/<hostname>/main.tf` configuration with this ID.
### Apply the OpenTofu Host State

View file

@ -0,0 +1,123 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.61.0"
}
}
}
# Bare Metal Host Configurations (SSH Provisioning)
# We use a null_resource to run imperative commands on the Debian host
# that are not currently supported by the bpg/proxmox provider.
resource "null_resource" "bare_metal_setup" {
triggers = {
node = var.node_name
data_disk_id = var.data_disk_id
pool_name = var.zfs_pool_name
}
connection {
type = "ssh"
user = "root"
# Assuming running locally on the node during bootstrap, or via SSH if run from a laptop.
# We default to local host if run from Control Center, but for flexibility we use the endpoint.
host = var.node_name
agent = true
}
provisioner "remote-exec" {
inline = [
# Set laptop lid switch to ignore (prevents sleeping when closed)
"sed -i 's/^#\\?HandleLidSwitch=.*/HandleLidSwitch=ignore/' /etc/systemd/logind.conf",
"systemctl restart systemd-logind",
# Format the ZFS pool if it doesn't already exist
"zpool list ${var.zfs_pool_name} || zpool create -f ${var.zfs_pool_name} /dev/disk/by-id/${var.data_disk_id}",
# Register the ZFS pool in Proxmox if it's not already registered
"pvesm status -storage ${var.zfs_pool_name} || pvesm add zfspool ${var.zfs_pool_name} --pool ${var.zfs_pool_name} --content images,rootdir"
]
}
}
# Resource Pools
# Creates logical groups for organizing and securing VMs.
resource "proxmox_virtual_environment_pool" "core" {
pool_id = "core"
comment = "Critical, public-facing services (reverse proxy, DNS)"
}
resource "proxmox_virtual_environment_pool" "production" {
pool_id = "production"
comment = "Stable services with backup strategies"
}
resource "proxmox_virtual_environment_pool" "dev" {
pool_id = "dev"
comment = "Persistent but non-production services, run 24/7"
}
resource "proxmox_virtual_environment_pool" "staging" {
pool_id = "staging"
comment = "Ephemeral test VMs managed by Pull-Based PR webhooks"
}
# API Tokens for Control Center
# Creates a restricted user for the Control Center VM to manage the cluster.
resource "proxmox_virtual_environment_role" "control_center_role" {
role_id = "ControlCenter"
privileges = [
"VM.Allocate",
"VM.Audit",
"VM.Clone",
"VM.Config.CDROM",
"VM.Config.CPU",
"VM.Config.Disk",
"VM.Config.HWType",
"VM.Config.Memory",
"VM.Config.Network",
"VM.Config.Options",
"VM.Monitor",
"VM.PowerMgmt",
"Datastore.AllocateSpace",
"Datastore.Audit",
"SDN.Use"
]
}
resource "proxmox_virtual_environment_user" "control_center_user" {
user_id = "control-center@pve"
comment = "Managed by OpenTofu - Used by Control Center for GitOps"
}
# Grant full VM access to the control center
resource "proxmox_virtual_environment_acl" "control_center_vms" {
user_id = proxmox_virtual_environment_user.control_center_user.user_id
role_id = proxmox_virtual_environment_role.control_center_role.role_id
path = "/vms"
}
# Grant datastore access
resource "proxmox_virtual_environment_acl" "control_center_storage" {
user_id = proxmox_virtual_environment_user.control_center_user.user_id
role_id = proxmox_virtual_environment_role.control_center_role.role_id
path = "/storage/${var.zfs_pool_name}"
}
resource "proxmox_virtual_environment_user_token" "control_center_token" {
user_id = proxmox_virtual_environment_user.control_center_user.user_id
token_id = "gitops"
privsep = false
comment = "Stored only on the Control Center VM"
}
output "control_center_api_token" {
value = proxmox_virtual_environment_user_token.control_center_token.value
sensitive = true
description = "The secret API token for control-center@pve. This will be injected into the Control Center SOPS config."
}

View file

@ -0,0 +1,15 @@
variable "node_name" {
type = string
description = "The name of the Proxmox node (e.g. pve)"
}
variable "data_disk_id" {
type = string
description = "The persistent block device ID for the data disk (e.g. wwn-0x500...)"
}
variable "zfs_pool_name" {
type = string
default = "data"
description = "The name of the ZFS pool to create on the data disk"
}

View file

@ -0,0 +1,30 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.61.0"
}
}
}
provider "proxmox" {
# Endpoint and credentials will be picked up from environment variables
# or passed via the bootstrap script.
# PROXMOX_VE_ENDPOINT
# PROXMOX_VE_USERNAME
# PROXMOX_VE_PASSWORD
# PROXMOX_VE_INSECURE=true
}
module "proxmox_node" {
source = "../../modules/proxmox-node"
node_name = "mikoshi"
data_disk_id = "nvme-KXG80ZNV2T04_NVMe_KIOXIA_2048GB_241C11Y5EHAK"
zfs_pool_name = "data"
}
output "control_center_api_token" {
value = module.proxmox_node.control_center_api_token
sensitive = true
description = "The secret API token for control-center@pve."
}

View file

@ -1,71 +0,0 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.61.0"
}
}
}
# This bootstrap state must be run manually ONCE with the root@pam credentials
# to establish the restricted terraform@pve user for the rest of the CI pipeline.
provider "proxmox" {
# Configuration can be passed via environment variables:
# PROXMOX_VE_ENDPOINT=https://your-proxmox-ip:8006/
# PROXMOX_VE_USERNAME=root@pam
# PROXMOX_VE_PASSWORD=your-root-password
# PROXMOX_VE_INSECURE=true # If using self-signed certs
}
resource "proxmox_virtual_environment_role" "terraform_prov" {
role_id = "TerraformProv"
privileges = [
"VM.Allocate",
"VM.Audit",
"VM.Clone",
"VM.Config.CDROM",
"VM.Config.CPU",
"VM.Config.Disk",
"VM.Config.HWType",
"VM.Config.Memory",
"VM.Config.Network",
"VM.Config.Options",
"VM.Monitor",
"VM.PowerMgmt",
"Datastore.AllocateSpace",
"Datastore.Audit",
"SDN.Use"
]
}
resource "proxmox_virtual_environment_user" "terraform_user" {
user_id = "terraform@pve"
comment = "Managed by Terraform (proxmox-bootstrap) for GitOps CI/CD"
}
resource "proxmox_virtual_environment_acl" "terraform_vms" {
user_id = proxmox_virtual_environment_user.terraform_user.user_id
role_id = proxmox_virtual_environment_role.terraform_prov.role_id
path = "/vms"
}
resource "proxmox_virtual_environment_acl" "terraform_storage" {
user_id = proxmox_virtual_environment_user.terraform_user.user_id
role_id = proxmox_virtual_environment_role.terraform_prov.role_id
# Update this path to match your actual local-zfs or TrueNAS mounted storage
path = "/storage/local-zfs"
}
resource "proxmox_virtual_environment_user_token" "terraform_token" {
user_id = proxmox_virtual_environment_user.terraform_user.user_id
token_id = "tf-automation"
privsep = false
comment = "Token for Forgejo CI/CD to provision VMs"
}
output "terraform_api_token" {
value = proxmox_virtual_environment_user_token.terraform_token.value
sensitive = true
description = "The secret API token for terraform@pve. Save this to Forgejo Secrets as PROXMOX_VE_API_TOKEN."
}