diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index 587138c..dd788bd 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -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//main.tf` configuration with this ID. ### Apply the OpenTofu Host State diff --git a/opentofu/modules/proxmox-node/main.tf b/opentofu/modules/proxmox-node/main.tf new file mode 100644 index 0000000..b495f66 --- /dev/null +++ b/opentofu/modules/proxmox-node/main.tf @@ -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." +} diff --git a/opentofu/modules/proxmox-node/variables.tf b/opentofu/modules/proxmox-node/variables.tf new file mode 100644 index 0000000..3bb24ed --- /dev/null +++ b/opentofu/modules/proxmox-node/variables.tf @@ -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" +} diff --git a/opentofu/nodes/mikoshi/main.tf b/opentofu/nodes/mikoshi/main.tf new file mode 100644 index 0000000..08c10a9 --- /dev/null +++ b/opentofu/nodes/mikoshi/main.tf @@ -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." +} diff --git a/opentofu/proxmox-bootstrap/main.tf b/opentofu/proxmox-bootstrap/main.tf deleted file mode 100644 index 720d183..0000000 --- a/opentofu/proxmox-bootstrap/main.tf +++ /dev/null @@ -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." -}