first commit: full infra representation in terraform configs (with exceptions)

exceptions: redpill and zion
This commit is contained in:
2026-09-28 00:34:42 -07:00
commit d38496be54
14 changed files with 970 additions and 0 deletions
+15
View File
@@ -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
+78
View File
@@ -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
+72
View File
@@ -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]
}
}
+147
View File
@@ -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"
}
+8
View File
@@ -0,0 +1,8 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.112.0"
}
}
}
+113
View File
@@ -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
}
+9
View File
@@ -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.")
}
+155
View File
@@ -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
}
+8
View File
@@ -0,0 +1,8 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.112.0"
}
}
}
+6
View File
@@ -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"
}
}
+304
View File
@@ -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"]
]
})}"
}
}
+26
View File
@@ -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"
}
}
}
+19
View File
@@ -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
# }
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">=1.10, < 2.0"
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.112.0"
}
}
}