commit d38496be540883d264c08710375796d98d727fa9 Author: sohcato Date: Mon Sep 28 00:34:42 2026 -0700 first commit: full infra representation in terraform configs (with exceptions) exceptions: redpill and zion diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cff6963 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Local working directories used by Terraform +.terraform/ +.terarform.* + +# Lock files containing provider hashes +.terraform.lock.hcl + +# Secret variable files containg API tokens or passwords +# Ideally we'll be using environment variables, though +*.tfvars +*.tfvars.json +*secret*.tf + +*.swp +*.tfplan diff --git a/README.md b/README.md new file mode 100644 index 0000000..74f0e9c --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# 1337NET Terraform State + +This repository contains the Terraform configs for 1337NET. Terraform configurations are declarative, meaning that you define the *whats* of your result, not the *hows*. Terraform for Proxmox works by making API calls to Proxmox which authenticates with a Proxmox token. However, some functionality is not API exposed; for these situations, we can allow SSH authentication to the Proxmox node we want to make changes to, and Terraform will run the necessary shell commands to achieve its desired state. I'm probably cutting a lot of details, but that's the gist of it. + +Ideally, all infrastructure moving forward should be defined in this repository and deployed using `terraform` on **Reeves**. Some infrastructure was provisioned before the transition to Terraform, and it's currently a work in progress to migrate this infrastructure into Terraform configs. + +This repository uses the bgp/proxmox provider. Find the documentation [here](https://registry.terraform.io/providers/bpg/proxmox/latest/docs). The 3 resources we currently use are [proxmox_virtual_environment_vm](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm), [proxmox_download_file](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/download_file), and [proxmox_virtual_environment_file](https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_file). + +To run `terraform` commands, you must define `proxmox_api_token` and `proxmox_endpoint` in the node sub-directory. You can do this in a file called `secrets.tfvars`. For example: + +``` +# ./proxmox_node_name/secrets.tfvars +proxmox_api_token=terraform@pve!terraform=... +proxmox_endpoint=192.168.30.10:8006 # fishburne's IP + Proxmox port +``` + +## Importing an existing Proxmox resource to Terraform + +If a VM already exists in Proxmox that was created through the Web UI and you want to preserve the VM's state while also importing it into Terraform so that it's managed by Terraform moving forward, take the following steps: + +1. Define a resource import + +Create `imports.tf` and replace `vm_name`, `node_name` and `vm_id`. +``` +# imports.tf + +import { + to = proxmox_virtual_environment_vm.vm_name + id = "node_name/vm_id" +} +``` + +2. Generate a plan that writes the config for the newly defined Terraform resource +``` +terraform plan -var-file secrets.tfvars -generate-config-out=generated.tf +``` + +3. Copy over the relevant settings in `generated.tf` to `main.tf` + +This one is a bit handwavy, but basically, you want to copy over the settings that need explicit definement. This is probably stuff like `vm_id`, `vm_name`, `proxmox_node_name`, `datastore_type`, RAM and CPU settings, etc. The other settings likely have defaults that match the VM. + +``` +# main.tf + +module "vm_name" { + source "../../modules/proxmox-vm" + + vm_id = vm_id + proxmox_node_name = node_name + vm_name = "vm_name" + cores = 1 + memory = 2048 +} +``` + +4. Change `imports.tf` to transfer the new resource to our Proxmox template module +``` +# imports.tf + +import { + to = module.vm_name.proxmox_virtual_environment_vm.this + id = "node_name/vm_id" +} + +``` + +5. DELETE `generated.tf` to remove a duplicate reference to resources. + +6. Generate an import plan +``` +terraform plan -var-file secrets.tfvars -out import.tfplan +``` + +6. Review the generated plan and make sure nothing will be destroyed + + + + diff --git a/modules/proxmox-ct/main.tf b/modules/proxmox-ct/main.tf new file mode 100644 index 0000000..e5c81a3 --- /dev/null +++ b/modules/proxmox-ct/main.tf @@ -0,0 +1,72 @@ +resource "proxmox_virtual_environment_container" "this" { + description = var.description + vm_id = var.ct_id + node_name = var.proxmox_node_name + + unprivileged = var.is_unprivileged + + disk { + datastore_id = var.disk_datastore_id + size = var.disk_size + } + + console { + enabled = var.is_console_enabled + tty_count = var.console_tty_count + } + + cpu { + architecture = var.cpu_architecture + cores = var.cpu_cores + } + + features { + fuse = var.is_fuse + keyctl = var.is_keyctl + mknod = var.is_mknod + nesting = var.is_nesting + } + + initialization { + hostname = var.ct_hostname + + dns { + domain = var.domain + servers = var.servers + } + + ip_config { + ipv4 { + address = var.ip_address + gateway = var.ip_gateway + } + } + } + + memory { + dedicated = var.dedicated_memory + swap = var.swap_memory + } + + network_interface { + bridge = var.network_interface_bridge + enabled = var.is_network_interface + mac_address = var.network_interface_mac_address + name = var.network_interface_name + rate_limit = var.network_interface_rate_limit + vlan_id = var.vlan_id + } + + operating_system { + template_file_id = var.operating_system_template_file_id + type = var.operating_system + } + + lifecycle { + prevent_destroy = true + ignore_changes = [operating_system] + } +} + + + diff --git a/modules/proxmox-ct/variables.tf b/modules/proxmox-ct/variables.tf new file mode 100644 index 0000000..87c7293 --- /dev/null +++ b/modules/proxmox-ct/variables.tf @@ -0,0 +1,147 @@ +variable "description" { + type = string + nullable = false +} + +variable "proxmox_node_name" { + type = string + nullable = false +} + +variable "is_unprivileged" { + type = bool + default = true +} + +variable "ct_id" { + type = number + nullable = false +} + +variable "disk_datastore_id" { + type = string + default = "containers" +} + +variable "disk_size" { + type = number + default = 64 +} + +variable "is_console_enabled" { + type = bool + default = true +} + +variable "console_tty_count" { + type = number + default = 2 +} + +variable "console_type" { + type = string + default = "tty" +} + +variable "cpu_architecture" { + type = string + default = "amd64" +} + +variable "cpu_cores" { + type = number + default = 1 +} + + +variable "is_fuse" { + type = bool + default = false +} + +variable "is_keyctl" { + type = bool + default = false +} + +variable "is_mknod" { + type = bool + default = false +} + +variable "is_nesting" { + type = bool + default = true +} + +variable "ct_hostname" { + type = string + nullable = false +} + +variable "domain" { + type = string + default = "" +} + +variable "servers" { + type = list(string) + default = ["192.168.30.82"] +} + +variable "ip_address" { + type = string +} + +variable "ip_gateway" { + type = string +} + +variable "dedicated_memory" { + type = number + default = 1024 +} + +variable "swap_memory" { + type = number + default = 1024 +} + +variable "network_interface_bridge" { + type = string + default = "vmbr30" +} + +variable "is_network_interface" { + type = bool + default = true +} + +variable "network_interface_mac_address" { + type = string +} + +variable "network_interface_name" { + type = string + default = "eth0" +} + +variable "network_interface_rate_limit" { + type = number + default = 0 +} + +variable "vlan_id" { + type = number + default = 0 +} + +variable "operating_system" { + type = string + default = "debian" +} + +variable "operating_system_template_file_id" { + type = string + default = "iso:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst" +} diff --git a/modules/proxmox-ct/versions.tf b/modules/proxmox-ct/versions.tf new file mode 100644 index 0000000..9726cb4 --- /dev/null +++ b/modules/proxmox-ct/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "0.112.0" + } + } +} \ No newline at end of file diff --git a/modules/proxmox-vm/main.tf b/modules/proxmox-vm/main.tf new file mode 100644 index 0000000..57eba53 --- /dev/null +++ b/modules/proxmox-vm/main.tf @@ -0,0 +1,113 @@ +resource "proxmox_virtual_environment_vm" "this" { + name = var.vm_name + node_name = var.proxmox_node_name + vm_id = var.vm_id + bios = var.bios + reboot_after_update = var.reboot_after_update + + lifecycle { + # Never destroy any existing VMs + prevent_destroy = true + + # Ignore the following changes: + ignore_changes = [ + initialization, + ] + + } + + machine = var.machine + + # Stop rather than shutdown or else it'll hang + # stop_on_destroy = true + + scsi_hardware = var.scsi_hardware + + cpu { + cores = var.cores + type = var.cpu_type + # hotplugged = var.hotplugged + # units = var.cpu_units + flags = var.cpu_flags + } + + memory { + dedicated = var.ram_size_mb + } + + disk { + datastore_id = var.datastore_type + backup = var.disk_backup + import_from = var.disk_import_from # proxmox_virtual_environment_download_file.debian_cloud.id + size = var.disk_size + interface = var.disk_interface + iothread = var.iothread + discard = var.disk_discard_setting + ssd = var.disk_ssd + } + + # Required to be set if bios=ovmf + efi_disk { + datastore_id = var.efi_disk_datastore_type != null ? var.efi_disk_datastore_type : (var.bios == "ovmf" ? "containers" : null) + file_format = var.efi_disk_file_format != null ? var.efi_disk_file_format : (var.bios == "ovmf" ? "raw" : null) + pre_enrolled_keys = var.efi_disk_pre_enrolled_keys != null ? var.efi_disk_pre_enrolled_keys : (var.bios == "ovmf" ? true : null) + + type = var.efi_disk_type != null ? var.efi_disk_type : (var.bios == "ovmf" ? "4m" : null) + } + + network_device { + model = "virtio" + bridge = var.network_bridge + disconnected = false + } + + operating_system { + type = var.operating_system + } + + # Always enable the QEMU agent so that we can observe the IP address in outputs.tf after the VM gets provisioned + agent { + enabled = var.is_qemu + wait_for_ip { + disabled = true + } + } + + serial_device { + device = "socket" + } + + initialization { + datastore_id = "local" + + ip_config { + ipv4 { + address = "dhcp" + } + } + + user_account { + username = var.vm_name + password = random_password.vm_initial_password.result + } + + vendor_data_file_id = var.vendor_data_file_id + } +} + +# Random password generated per VM for initial login -- please change afterwards +# in case the resource is saved in Terraform's state +resource "random_password" "vm_initial_password" { + length = 16 + override_special = "!#$%&*()-_=+[]{}<>:?" + min_lower = 3 + min_numeric = 3 + min_special = 3 + min_upper = 3 +} + +output "vm_password" { + value = random_password.vm_initial_password.result + sensitive = true +} + diff --git a/modules/proxmox-vm/outputs.tf b/modules/proxmox-vm/outputs.tf new file mode 100644 index 0000000..f40e087 --- /dev/null +++ b/modules/proxmox-vm/outputs.tf @@ -0,0 +1,9 @@ +output "vm_id" { + description = "The Proxmox VM ID assigned to the instance." + value = proxmox_virtual_environment_vm.this.vm_id +} + +output "vm_ip_address" { + description = "The DHCP IP address assigned to the VM." + value = try(element(proxmox_virtual_environment_vm.this.ipv4_addresses[0], 0), "DHCP IP address has not been assigned yet.") +} \ No newline at end of file diff --git a/modules/proxmox-vm/variables.tf b/modules/proxmox-vm/variables.tf new file mode 100644 index 0000000..3ea0e94 --- /dev/null +++ b/modules/proxmox-vm/variables.tf @@ -0,0 +1,155 @@ +variable "vm_name" { + type = string +} + +variable "vm_id" { + type = number +} + +variable "reboot_after_update" { + type = bool + default = false +} + +variable "proxmox_node_name" { + type = string +} + +variable "cores" { + type = number + default = 2 +} + +variable "ram_size_mb" { + type = number + default = 2048 +} + +variable "datastore_type" { + type = string + default = "local-lvm" +} + +variable "disk_import_from" { + type = string + default = null + # default = proxmox_virtual_environment_download_file.debian_cloud.id +} + +# variable "disk_image" { +# type = string +# default = "iso:iso/debian-13.2.0-amd64-netinst.iso" +# } + + +variable "disk_size" { + type = number + default = 24 +} +variable "disk_interface" { + type = string + default = "scsi0" +} + +variable "disk_backup" { + type = bool + default = true +} + +variable "efi_disk_datastore_type" { + type = string + default = null + # "containers" IF bios=ovmf, ELSE null +} + +variable "efi_disk_file_format" { + type = string + default = null + # "raw" IF bios=ovmf, ELSE null +} + +variable "efi_disk_pre_enrolled_keys" { + type = bool + default = null + # true IF bios=ovmf, ELSE null +} + +variable "efi_disk_type" { + type = string + default = null + # "4m" IF bios=ovmf, ELSE null +} + +# THis is the VLAN to set -- this DOENS'T set the VLAN tag, which is what we want +variable "network_bridge" { + type = string + default = "vmbr30" +} + +# This is if you WANT a VLAN tag. +# variable "vlan_id" { +# type = number +# default = 30 +# } + +variable "operating_system" { + type = string + default = "l26" +} + +variable "is_qemu" { + type = bool + default = true +} + +variable "vendor_data_file_id" { + type = string + default = null +} +# variable "proxmox_api_token" { +# description = "The API token for Proxmox authentication" +# type = string +# sensitive = true # Don't show in console outputs +# } + +variable "bios" { + type = string + default = "seabios" +} + +variable "machine" { + type = string + default = "pc" +} + +variable "iothread" { + type = bool + default = false +} + +variable "cpu_type" { + type = string + default = "qemu64" + # default = "x86-64-v2-AES" +} + +variable "cpu_flags" { + type = list(string) + default = [] + nullable = false +} + +variable "scsi_hardware" { + type = string + default = "virtio-scsi-pci" +} + +variable "disk_discard_setting" { + type = string + default = "ignore" +} + +variable "disk_ssd" { + type = bool + default = false +} diff --git a/modules/proxmox-vm/versions.tf b/modules/proxmox-vm/versions.tf new file mode 100644 index 0000000..9726cb4 --- /dev/null +++ b/modules/proxmox-vm/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "0.112.0" + } + } +} \ No newline at end of file diff --git a/stacks/prod/proxmox/backend.tf b/stacks/prod/proxmox/backend.tf new file mode 100644 index 0000000..76c5f22 --- /dev/null +++ b/stacks/prod/proxmox/backend.tf @@ -0,0 +1,6 @@ +# Where Terraform stores its state file (current infrastructure vs. configured config) +terraform { + backend "local" { + path = "/srv/terraform-state/proxmox-prod/terraform.tfstate" + } +} diff --git a/stacks/prod/proxmox/main.tf b/stacks/prod/proxmox/main.tf new file mode 100644 index 0000000..e9c17b5 --- /dev/null +++ b/stacks/prod/proxmox/main.tf @@ -0,0 +1,304 @@ +########## PARKER ########## + +module "splunk" { + # Point to the reusable module folder + source = "../../../modules/proxmox-vm" + + vm_name = "splunk" + proxmox_node_name = "parker" + vm_id = 114 + datastore_type = "containers" + disk_import_from = proxmox_download_file.debian_cloud.id +} + +module "exmachina" { + # Point to the reusable module folder + source = "../../../modules/proxmox-vm" + + vm_name = "exmachina" + proxmox_node_name = "parker" + vm_id = 103 + bios = "ovmf" + + datastore_type = "containers" + disk_import_from = null + disk_size = 32 + disk_interface = "scsi0" + + ram_size_mb = 4096 + vendor_data_file_id = null +} + +module "exmachina2" { + # Point to the reusable module folder + source = "../../../modules/proxmox-vm" + + vm_name = "exmachina2" + proxmox_node_name = "parker" + vm_id = 115 + datastore_type = "containers" + disk_size = 24 + disk_interface = "scsi0" + disk_import_from = proxmox_download_file.debian_cloud.id + vendor_data_file_id = proxmox_virtual_environment_file.autoinstall_config.id +} + +module "webhost" { + source = "../../../modules/proxmox-ct" + + description = "webhost" + ct_id = 105 + proxmox_node_name = "parker" + + is_unprivileged = true + + cpu_cores = 2 + + ct_hostname = "webhost" + ip_address = "192.168.30.75/24" + ip_gateway = "192.168.30.1" + + network_interface_mac_address = "BC:24:11:B0:28:05" + + operating_system_template_file_id = "iso:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst" +} + +module "gitea" { + source = "../../../modules/proxmox-ct" + + description = "gitea" + ct_id = 107 + proxmox_node_name = "parker" + + is_unprivileged = true + + disk_size = 16 + + cpu_cores = 2 + + ct_hostname = "gitea" + ip_address = "192.168.30.74/24" + ip_gateway = "192.168.30.1" + + dedicated_memory = 2048 + + network_interface_mac_address = "BC:24:11:88:8D:DD" +} + +module "hockeypuck" { + source = "../../../modules/proxmox-ct" + + description = "hockeypuck" + ct_id = 108 + proxmox_node_name = "parker" + + is_unprivileged = true + + disk_size = 16 + + cpu_cores = 1 + + ct_hostname = "hockeypuck" + ip_address = "192.168.30.72/24" + ip_gateway = "192.168.30.1" + + dedicated_memory = 512 + + network_interface_mac_address = "BC:24:11:82:60:4E" +} + +########## CHONG ########## +module "keymaker" { + source = "../../../modules/proxmox-vm" + + vm_name = "keymaker" + bios = "ovmf" + proxmox_node_name = "chong" + vm_id = 111 + datastore_type = "containers" + disk_size = 32 + vendor_data_file_id = null + ram_size_mb = 2048 +} + +module "phonebox" { + source = "../../../modules/proxmox-vm" + cpu_flags = ["+aes"] + vm_name = "phonebox" + bios = "ovmf" + cores = 1 + proxmox_node_name = "chong" + vm_id = 112 + datastore_type = "containers" + disk_size = 32 + vendor_data_file_id = null + ram_size_mb = 2048 + scsi_hardware = "virtio-scsi-single" + cpu_type = "x86-64-v2-AES" + iothread = true + disk_ssd = true +} + +module "deus" { + source = "../../../modules/proxmox-vm" + + vm_name = "deus" + bios = "ovmf" + proxmox_node_name = "chong" + vm_id = 102 + datastore_type = "containers" + disk_size = 32 + vendor_data_file_id = null + ram_size_mb = 2048 + cores = 1 + machine = "q35" + iothread = true + cpu_flags = ["+aes"] + cpu_type = "x86-64-v2-AES" + scsi_hardware = "virtio-scsi-single" + disk_discard_setting = "on" + disk_ssd = true +} + +module "deus2" { + source = "../../../modules/proxmox-vm" + + vm_name = "deus2" + bios = "ovmf" + proxmox_node_name = "chong" + vm_id = 113 + datastore_type = "containers" + disk_size = 32 + vendor_data_file_id = null + ram_size_mb = 4096 + scsi_hardware = "virtio-scsi-single" + cpu_flags = ["+aes"] + iothread = true + cpu_type = "x86-64-v2-AES" + disk_ssd = true +} + +module "postgresql" { + source = "../../../modules/proxmox-ct" + + description = "postgresql" + ct_id = 106 + proxmox_node_name = "chong" + + is_unprivileged = true + + disk_size = 64 + + cpu_cores = 1 + + ct_hostname = "postgresql" + ip_address = "192.168.30.62/24" + ip_gateway = "192.168.30.1" + + dedicated_memory = 1024 + + network_interface_mac_address = "BC:24:11:AF:6B:61" +} + +module "ejabberd" { + source = "../../../modules/proxmox-ct" + + description = "ejabberd" + ct_id = 110 + proxmox_node_name = "chong" + + is_unprivileged = true + + disk_size = 8 + + cpu_cores = 1 + + ct_hostname = "ejabberd" + ip_address = "192.168.30.63/24" + ip_gateway = "192.168.30.1" + + dedicated_memory = 1024 + + network_interface_mac_address = "BC:24:11:E2:58:45" +} + +########## FISHBURNE ########## +module "operator" { + source = "../../../modules/proxmox-ct" + + description = "operator" + ct_id = 101 + proxmox_node_name = "fishburne" + + is_unprivileged = true + + disk_datastore_id = "backup-storage" + + disk_size = 16 + + cpu_cores = 2 + + ct_hostname = "operator" + ip_address = "192.168.30.14/24" + ip_gateway = "192.168.30.1" + + dedicated_memory = 1024 + + network_interface_mac_address = "BC:24:11:76:06:C3" +} + +module "bluepill" { + source = "../../../modules/proxmox-ct" + + description = "bluepill" + ct_id = 104 + proxmox_node_name = "fishburne" + + is_unprivileged = true + + disk_datastore_id = "backup-storage" + disk_size = 16 + + cpu_cores = 2 + + ct_hostname = "bluepill" + domain = "1337.berkeley.edu" + servers = ["1.1.1.1"] + ip_address = "192.168.40.11/24" + ip_gateway = "192.168.40.1" + + dedicated_memory = 512 + + network_interface_bridge = "vmbr40" + network_interface_mac_address = "BC:24:11:DF:69:A9" + network_interface_name = "vmbr40" + vlan_id = 40 +} + +# Grabbing the Debian installer from the cloud because it contains cloud-init which is required to auto-install QEMU +resource "proxmox_download_file" "debian_cloud" { + node_name = "parker" + datastore_id = "local" + content_type = "import" + + file_name = "debian-13-generic-amd64.qcow2" + url = "https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2" +} + +resource "proxmox_virtual_environment_file" "autoinstall_config" { + content_type = "snippets" + datastore_id = "local" + node_name = "parker" + + source_raw { + file_name = "autoinstall_config.yaml" + data = "#cloud-config\n${yamlencode({ + ssh_pwauth = true + package_update = true + packages = ["qemu-guest-agent"] + runcmd = [ + ["systemctl", "enable", "--now", "qemu-guest-agent"] + ] + })}" + } +} diff --git a/stacks/prod/proxmox/providers.tf b/stacks/prod/proxmox/providers.tf new file mode 100644 index 0000000..e5f2dc8 --- /dev/null +++ b/stacks/prod/proxmox/providers.tf @@ -0,0 +1,26 @@ +provider "proxmox" { + # Proxmox with Terraform is cluster-aware to an extent -- for our purposes, setting + # the endpoint to any Proxmox node is sufficient + + # Proxmox API endpoint + endpoint = "https://192.168.30.70:8006" + + # API token authentication (through environment variables) + api_token = var.proxmox_api_token + + # Accept self-signed certificates (TODO: change in the future if necessary) + insecure = true + + # Allow SSH connections for certain operations (like uploading ISO files) + # https://registry.terraform.io/providers/bpg/proxmox/latest/docs#ssh-connection + ssh { + agent = false + username = "svc-terraform" + private_key = file("/home/svc-terraform/.ssh/id_rsa") + # port = 22 + node { + name = "parker" + address = "192.168.30.70" + } + } +} diff --git a/stacks/prod/proxmox/variables.tf b/stacks/prod/proxmox/variables.tf new file mode 100644 index 0000000..504a039 --- /dev/null +++ b/stacks/prod/proxmox/variables.tf @@ -0,0 +1,19 @@ +variable "proxmox_endpoint" { + description = "Proxmox HTTPS API endpoint (including the port 8006)." + type = string +} + +variable "proxmox_api_token" { + description = "The API token for Proxmox authentication in the form user@realm!tokenid=token-value" + type = string + sensitive = true # Don't show in console outputs +} + +# variable "username" { +# type = string +# sensitive = true # Don't show in console outputs +# } +# variable "password" { +# type = string +# sensitive = true # Don't show in console outputs +# } diff --git a/stacks/prod/proxmox/versions.tf b/stacks/prod/proxmox/versions.tf new file mode 100644 index 0000000..7108aba --- /dev/null +++ b/stacks/prod/proxmox/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">=1.10, < 2.0" + + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "0.112.0" + } + } +}