Add infra/ with student-vms OpenTofu root module

- infra/pve: host facts, IP plan, one-time setup
- infra/tofu/student-vms: 3 student VMs via tf-mod-pve v1.0.0
- infra/sandbox: template clones for throwaway test VMs
- README/TODO: Windows DC/AD instead of FreeIPA, new network
- .gitignore: only ignore secrets.auto.tfvars instead of all tfvars

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CubelaPetarandClaude Opus 5.5 committed 2026-10-27 13:58:21 +01:00
1 parent 2dfa8de5c6
commit 91ed0b300c
12 files changed
+197 -44

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# PVE host
- Host: `pve` (`10.100.200.50`), Proxmox VE 9.2, 6 cores, 31 GiB RAM
- Storage: `local` (dir: ISOs, snippets), `local-lvm` (VM disks)
- Network: `vmbr0` on `10.100.200.0/24`, gateway and current DNS `10.100.200.4`
- Templates: `903` `temp-debian-13`
## IP plan (10.100.200.0/24)
| Address | Use | Size |
|---|---|---|
| `.4` | Gateway, current DNS | |
| `.50` | PVE host | |
| `.70` | DC/AD `dc01.lab.softbox.net` (planned) | 2 vCPU, 4-8 GB |
| `.80-.100` | Student VMs (`vm-00`, …), VMID `1080-1100`, 5-10 planned | 1 vCPU, 1 GB |
| `.101-.211` | In use by other devices (probably DHCP) | |
## One-time setup (as root, done)
```bash
# 1. Allow snippets on local storage
pvesm set local --content iso,vztmpl,backup,import,snippets
# 2. API user and token for OpenTofu; put the printed value into
# infra/tofu/<role>/secrets.auto.tfvars as pve_api_token = "tofu@pve!tofu=<value>"
pveum user add tofu@pve --comment "OpenTofu"
pveum aclmod / -user tofu@pve -role PVEAdmin
pveum user token add tofu@pve tofu --privsep 0
# 3. sudo for sbxadmin; passwordless rules for the snippet upload of the bpg/proxmox provider
usermod -aG sudo sbxadmin
cat > /etc/sudoers.d/sbxadmin-tofu <<'EOF'
sbxadmin ALL=(root) NOPASSWD: /usr/sbin/pvesm apiinfo
sbxadmin ALL=(root) NOPASSWD: /usr/bin/tee ^/var/lib/vz/snippets/[a-zA-Z0-9_][a-zA-Z0-9_.-]*$
EOF
chmod 440 /etc/sudoers.d/sbxadmin-tofu && visudo -c
# 4. Templates: tf-mod-pve/scripts/create_template.sh, copied to /root/templates/ with
# storage=local-lvm, ssh_keyfile=/root/.ssh/authorized_keys, only temp-debian-13 enabled
cd /root/templates && bash create_template.sh
```
+13
View File
@@ -0,0 +1,13 @@
# sandbox
Throwaway test VMs, not managed by OpenTofu. Clone a template on the PVE host (as root), use VMIDs `9990-9999`:
```bash
qm clone 903 9991 --name test-01 --full
qm set 9991 --ipconfig0 ip=dhcp
qm start 9991
# done testing
qm stop 9991 && qm destroy 9991
```
Login: `ssh admin@<ip>` (IP via `qm guest cmd 9991 network-get-interfaces`).
+36
View File
@@ -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.118.1"
constraints = ">= 0.118.0, ~> 0.118"
hashes = [
"h1:/tyENGiIdJgBVlcOQOyHaBmLyQEoogFnTTMN+GJ6ayE=",
"h1:057hFYAlLv2HcjHR7TVoNnDmm57YWNZDNqhDvv6PS7I=",
"h1:CxLdcddSmTqP00oYkvWdN6d+qQSDTOhDzbM2/FzCPbY=",
"h1:E1uc/cddGUBfnYyLOxnmqpzd6yLL4RKHqZJVxRMn/DA=",
"h1:G34GMrwG/FejGMTxwJYKx4tghadFA+xqtrF40FbyOG8=",
"h1:M1nzf28WMPzm4p9sUJGrAUOcqfDyQwRjRIpFMaHzo+Q=",
"h1:MeUGboRbtAzb07dgljVCzUmU5hJ8BOfHBQOOtFzNziU=",
"h1:OCF6iuMldDOqn52ULPf22IJb0Ni3GFx6HdoiTAPIb3M=",
"h1:f46bI0fO0leCFEkYdEVcRrsjAAtSFEN6yZKu2fZymTI=",
"h1:fhAY0NwBwBBu/qUeTb1lAd3a7bffgouILhhjUNNNXCk=",
"h1:gaCA8Ez8zV/OXIVWIE1B11iQmKH7gILdn7Vm9KSRrbU=",
"h1:r52a2cEQ7ftGS1G6vHFMqWI6Bg6o0F/T5BsShH8O2DQ=",
"h1:wAD5hFQpgEptQYR5UK8Hbh8WoTcMGnRCxehLqN+3f9w=",
"zh:07405857f3dda91d1a065b0349547920f4f9b58ec214b19f7e54a07d776c1f88",
"zh:0bd362db77c5971fe0e92b0faf57ed9b50062d4321b5c7a09f492c57dffecb19",
"zh:2cb31f9d4ad2ae90f72f87e3cb77c2f745acbe4b17822821c385612647ea178d",
"zh:2e63ff741101e173feafb133f96273634831322984939115396763b6f860706b",
"zh:787bdda1b4e0924c334acebeb7491966b8e611aaa8e542ec003cb8cbfd9081f4",
"zh:7f2ece41d81a24988f5a357684b2b323cc5165022434e7f91e5a29f10a4182c7",
"zh:9a1e263e4111105c14ed1dc44a5c4e45f00f0f88cb726d16388771c1cdd9b26d",
"zh:a2c1ae4950667bdc0f023f3d12d175fec3cf1a809c4d990cf72f0a200606f6f8",
"zh:abd342c9153e712a8542b0fd03f6a0b25f954573184dfe8f5b05b08b1e26e254",
"zh:b5cc543d31183a63c782af7fab9a4a203b4eebf782db13af94ae65c9b7d51373",
"zh:befeb50e7346802d3c222541e91a06093933990acdfc89cf93364615fdac91c4",
"zh:d164da80b3f3339549942a3131b0717f14798ed514e8362c76874b68feba89f8",
"zh:dc265c6e4c3e656e014c524b50e0ff02761070cb9db7cb24f7b0ab57c12b9e97",
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
]
}
+15
View File
@@ -0,0 +1,15 @@
# student-vms
One Debian 13 VM per student (`vm-00`, `vm-01`, …, IPs `10.100.200.80-100`), created with [tf-mod-pve](https://github.com/CubelaPetar/tf-mod-pve). The VM list is in `vms.tf`.
Prerequisites: host setup from [`../../pve/README.md`](../../pve/README.md) and your SSH key authorized for `sbxadmin` on the host.
```bash
ssh-add ~/.ssh/id_ed25519 # provider uploads snippets via ssh-agent
cp secrets.auto.tfvars.example secrets.auto.tfvars # fill in the API token
tofu init
tofu plan
tofu apply
```
State is local (`terraform.tfstate`, gitignored). Back it up.
+11
View File
@@ -0,0 +1,11 @@
provider "proxmox" {
endpoint = "https://10.100.200.50:8006/"
api_token = var.pve_api_token
insecure = true # self-signed certificate
# Used to upload the cloud-init snippets, key comes from the ssh-agent
ssh {
agent = true
username = var.pve_ssh_user
}
}
@@ -0,0 +1,3 @@
# Copy to secrets.auto.tfvars (gitignored) and fill in
pve_api_token = "tofu@pve!tofu=<secret>"
pve_ssh_user = "sbxadmin"
+16
View File
@@ -0,0 +1,16 @@
variable "pve_api_token" {
type = string
description = "Proxmox API token, e.g. tofu@pve!tofu=<secret>"
sensitive = true
}
variable "pve_ssh_user" {
type = string
description = "SSH user on the PVE host for the snippet upload"
}
variable "ssh_public_key_file" {
type = string
description = "Public key added to the VM user admin"
default = "~/.ssh/id_ed25519.pub"
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.10"
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.118"
}
}
}
+32
View File
@@ -0,0 +1,32 @@
# One VM per student: hostname => VMID and IP (10.100.200.80-100)
locals {
vms = {
"vm-00" = { vm_id = 1080, ip = "10.100.200.80" }
"vm-01" = { vm_id = 1081, ip = "10.100.200.81" }
"vm-02" = { vm_id = 1082, ip = "10.100.200.82" }
}
}
module "vm" {
source = "git::https://github.com/CubelaPetar/tf-mod-pve.git?ref=v1.0.0"
for_each = local.vms
node_name = "pve"
vm_id = each.value.vm_id
template_vm_id = 903 # temp-debian-13
hostname = each.key
domain = "lab.softbox.net"
tags = ["student"]
ssh_public_key = file(pathexpand(var.ssh_public_key_file))
cpu_cores = 1
memory = 1024
storage = "local-lvm"
disk_size = 16
net_bridge = "vmbr0"
ip_address = "${each.value.ip}/24"
gateway = "10.100.200.4"
# Current resolver until the DC/AD takes over DNS
nameservers = ["10.100.200.4"]
}