adding initial files

This commit is contained in:
nick committed 2026-06-19 14:59:55 -07:00
1 parent 3cf540aeac
commit c25586f80f
8 files changed
+396

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/hashicorp/external" {
version = "2.4.0"
hashes = [
"h1:/+SUsgcSNW/RVmImj/m1Z+hSUU5BUMu8CMbLwyuhMMU=",
"h1:/MpYrEgBd7Gu/lyuyViR4ccaYSpHHIiHZfIxy7AGy2U=",
"h1:0SJyK1GT4ma4uTSUz8Y619fc1xRTzJ9FKFvcpvZlSIo=",
"h1:4WFLgDJ0NXqNlKJ2UFniW8/GMMterkrWBXmi/+yW2do=",
"h1:5vgd2l3wLBjETL3h0npHKNm8efKSE7Xb2uxh+8cysm8=",
"h1:6dSIL7ou4oq3hcuRUx4OLRc31PQ1oAC1d885hnQlOh0=",
"h1:7OLUuwL6BIhAYehzbDX5NVTIoDAIBf2dYZGrpSKe0r0=",
"h1:9PmuE7y6euSGJtnl7W0TIBvZBU1pNojfa615jnJAiIs=",
"h1:ME8EoyLapCOOAzPP8uPTm+OSF7nil1r0wOeJ+Q9KNZ8=",
"h1:PxaDF/WKlkCtfmiqzws5/23jq3QgcoJ5TMeEIimtvDQ=",
"h1:auXuC9e9AsQxrYQj6OyYgqKHgjry0IkJnKHpPShoLHM=",
"h1:eBAcMZAh1wmCyYQMCIPyXljazVRYAHkEu/31RbZD6Sw=",
"h1:jkfHEdIUc6uxIm8zuw0lVZliedeqgzDE9qb1Y608VUI=",
"h1:mC6Rw0cABPfsBZLVXfdOByUUW1hHkfscaQufTzVM+rw=",
"h1:nE1mN6A/5lJVLiNQmVm43m9ME2uHlqwIYUVxlZgblSk=",
"zh:483962b782cee2c970f4bdf6118e4bb665f37a0d488024c660b7a7c9853afc93",
"zh:4a8b42651f6de0ea93854ece3ccdf8e2b21b911145859402a9a6ec6ecf31a23c",
"zh:56836ea1468cb328e98cccc76cccc208fb7336513bc76de76309541b8e5ceef2",
"zh:6d30c2c1aff7e0ddcfffdf815e570f5ed8b77d1ce2d31440c7c50c6f3e7cbe97",
"zh:80a21a23bd9bfafb74e4fc5f2a0e2bc33517c418d5f16dc020b7febba9ef6cd2",
"zh:95d9ce0e6407f199f7e9d3aefc3345a6e67c18f0c8280207417272cbb3f973ad",
"zh:98e46c6504da5f1020489730d23f03a1337e643ed452f271c2a13e6af4687a16",
"zh:a3f59b81da319a87bb3ac4a31e669874a090f082959da29975fa6f11f53b4731",
"zh:a5793f88bb25000d6e6b8b2cd5ca71366a8d4e373e17a247089a80331ccf824e",
"zh:cfc9af183162936b2e9a726c4a68d773b4511ada23b2c764f5add90f080e747d",
"zh:d85e722d771fb7865d9d5b591334d0f2b7598b7e66f534f93923effe6d5134ed",
"zh:de6e5d954ef91bb0ad41f30305ab8c31718ea39308523529f528babd0b27db71",
"zh:fca19dda5e05221e231d400fc39fda4f9e9abfe33e8e833a215eb094fe226ed8",
"zh:fcf67b347f2dc3c609c819ad5f27078fa3d0235311cfe5e33242eb49b3a4a6a8",
"zh:fea69a81ffcd63cb776b0f2c13a99a8a6d64e0b9de111ea1926bc4e713023ece",
]
}
provider "registry.opentofu.org/hashicorp/local" {
version = "2.9.0"
constraints = "~> 2.2"
hashes = [
"h1:1dtKYW/5a1qob3yneL6WzOlnSGfYtJ6a2XeejCk9yb4=",
"h1:5NseXq5wU8O20ersTtV4ocrLYFFtgFr7n0pRLO1W2Rw=",
"h1:5d22ZPPK4iiygPbwRz/PJF5Es/0axVpMlPRpCR0Padw=",
"h1:AnwyolirmIlBMjH6+tV8bKkvT+5axJNYxi2y2IguiX4=",
"h1:PBp+HeseY021Fw3sLznCG27idgwPoff4cBuNmKgPL2w=",
"h1:VDxIhe4GbzdOCdmt7mQaqdwERQW6GSI7Roonts42Gr0=",
"h1:ZO6eWWnf8LjjV1q/JNeL9WLtZ6fwIttOnyN5LjCNSEo=",
"h1:dPIAf8oUAz+vW2E0iZunMvpuPddRZIztRsPSY1u+VnY=",
"h1:fwTDVG9AhFVKQZIb1EXkHv4FqzsZNlLWgkyPGDmZZEE=",
"h1:kDc465XPC7/6XFCjrMC4mTqhA9ef0FHKuJ3ZgfGNfeg=",
"h1:kGbjxrI2P8MHeyVtE1U3Q1TbyF71ExnHxtkrE+Aj6UU=",
"h1:kcoK6Afbsj54u9zaEqpecWAFKytqjBijtguCNwV3d4M=",
"h1:rxomJjDwOo+YZ+WIPc25FqEgsz9orh/2MCyUcZmFjvw=",
"h1:t0CMn/Rkwquw8l2yQ+O4ApzbMZfY2UazbsDnZygzACA=",
"h1:tJwgm2BS4xCGlElCDQEFXQoefY9Y4t0JdSKTtsPBbBo=",
"zh:13ef7ecd1e397ec5b20ea588508dd3e3b8d6c50d809ae76b079abf9dd8d02e4b",
"zh:2190c9325980076489ce02b0f5dd2c0b91fc8711cefa99e714d8619a32827ad1",
"zh:2a0cfc5600730093705071707e4a4e4e953e7d9091859e0f66b46daa1060dd5d",
"zh:2ff53eac1af43ab9a2248a0e53c963d46e19cf04bc4c3f323591cfcebb218252",
"zh:4ebc3dee700f60af9da29970052fd02fa947813162b224716862dc9d7f1f7542",
"zh:5fe6dab84ceeaa8eb3f1567c5f05578333370c472240ca5c5bfc25e92d4d5586",
"zh:66bbec16367bbf440045502c9779b11f4ac5b022c8d8d17afe12d431950838b5",
"zh:7641e5c2e4b529e869cde29ab5b1de2fd1091489eb745b19ac2709bd7f4dfd84",
"zh:855bfba0756d17ce07595ff57d7cf664443d1495127cb88fb063362734b8b22a",
"zh:aaec10f237921d60c581d1b7a66f0a8a8019d9802dc04af11b5b981f6682e01d",
"zh:e460835a38ffa1e74f6929904bfd14ef473d217fd537b7ce834abe5ce5e2ce07",
"zh:ecc4295215db0e4aea3c9329611c31e09a853e1ae207d56742403bd4f5516703",
"zh:ee6d9fae63a612072e00402894e14826af7a3351c235b9c5b423b7629a77ca29",
"zh:f2b5c8db74aa7ebcf7cd423672358437d42401675069ef67b01ff910054e49d5",
"zh:f5aff74d3eb96d4592c7bca5cd3ea89b469e84efbf382944bd0f844a57059c09",
]
}
provider "registry.opentofu.org/telmate/proxmox" {
version = "3.0.2-rc04"
constraints = "3.0.2-rc04"
hashes = [
"h1:+cwNDu0k5u7umqhQXFQsaWj8u10fAhYqd6GkbXIMUmE=",
"h1:/gu6nxurXTH52lyU9gLdKplbopmWVWISxG66jCxeu8k=",
"h1:8gE+vAHg3lG9eYID58yJmZBV0juPGfYciqJbXhH8cNg=",
"h1:8tyqYSoNfrqIh0ICT7abFOQVcyGGXry2qFCJVMoc4TE=",
"h1:Fd96pUhKZCMlN3o65grw2wZddYJ8pKT3AlFnzpkKffI=",
"h1:PYeQ8wBBH+L89THsEVgrRynySaoxNQjxiEvRHGug1KQ=",
"h1:SnzOxMwWxJbJl8EIjah6+h0OSLPAVIFFItwnzKkENX4=",
"h1:ZPr93UUVOw1aCqsODLXsqNQ9Lq/WafX5jgCCLxKBHl4=",
"h1:cJqOL86cwKzfgdfceHWQJT+EGD72EA7Ce01UwDFhYHI=",
"h1:fb+/e4VKjjeF86t8rHkwwOtkzlGFShFvsX+nxPRYjOo=",
"h1:o0bK2WRHzFFinCL7G83hD3/Gtli6bnI81s3IbelrHuM=",
"h1:t4E2D4zMKyLSdibGIWkOCEpVvHyFH8GLpGrG0mqFdwk=",
"h1:u/obsRXL8vD4p4JhrQa81o1zZLoH9LRgaOKUT5C0Ba8=",
"h1:xBZTbr96GHQVwVr2OA2cEBDaPNNua36HJucBUgD3qWg=",
"zh:00586a3e02061734f5ff4c309696ec1bbd6e00a7ed1281c44626f02929417f6e",
"zh:05af12aababd0a67a95309a803123f3c238b16ecdb73dc59c8d9c3d4a0b691c5",
"zh:4b6457bd0138f5c66c8b7043bdbffe3b8abcce2383293d77f653dbd77d14428f",
"zh:7b1b6e76d6b03c7829b8dadeb30de236782be314f5146d9f29ee12bd01961d7a",
"zh:87c21e0a8244b73c62ad3221773ec1c72fb918e0025b0664183a8ad8bebeb5bc",
"zh:954997c550285dfc167977683b97b045dd62923c251fad32c4ca81b564c2005f",
"zh:98781ef0199877580256e9f9a7f0666107f76af23f54f67d6392a66a6f5001b5",
"zh:9fc4dbd1752e2b9125223be2dd25597c0e6d534aa784c4829af3c3e3e58eb973",
"zh:a3f672019aee2d45c202668b95c48113e3c50001b740bd48956738ad1418c6b7",
"zh:a539852a4034132442e6dc6f644444994d83b0dc7a4d30bbe6464de420cfdf2d",
"zh:aade0b50a559a1da683c49ec8527e5e66b12ed42d4b1f10e69b25cbbf6c67805",
"zh:bdb7c2b78cfe039247948fa75fad49f871f8a8bb9b969aec4c45a24edc80ab0f",
"zh:c16209a6748f87a379a5a3132ca4f471a4807bd478664499593e09433ba39b7b",
"zh:cb015fb4a0a50d151c35c6a3505cdf7a337edd0b65eba82a43c8142c523adfb8",
]
}
+22
View File
@@ -0,0 +1,22 @@
# File generated by running OpenTofu, not meant to be manually edited
all:
children:
proxmox:
children:
proxmox_lxc:
hosts:
proxmox_vm:
hosts:
lxc:
children:
proxmox_lxc:
vm:
children:
proxmox_vm:
vars:
ansible_user: "ansible"
ansible_ssh_private_key_file: ".././.ssh_key"
+23
View File
@@ -0,0 +1,23 @@
terraform {
# Local backend: stores state in the parent repo so it stays out of this
# submodule. Switch to the remote backend below once you have an S3 bucket.
backend "local" {
path = "./terraform.tfstate"
}
# Remote backend (S3 or S3-compatible, e.g. MinIO). Comment out the local
# backend above and fill in your values to use this instead.
#
# backend "s3" {
# bucket = "my-tofu-state"
# key = "site/terraform.tfstate"
# region = "us-east-1"
#
# # For MinIO or other S3-compatible stores:
# # endpoint = "https://minio.example.com"
# # force_path_style = true
# # skip_credentials_validation = true
# # skip_metadata_api_check = true
# # skip_region_validation = true
# }
}
+79
View File
@@ -0,0 +1,79 @@
resource "local_sensitive_file" "ssh_private_key" {
filename = "${path.module}/.ssh_key"
content_base64 = data.external.infisical.result["ssh_private_key_b64"]
file_permission = "0600"
}
locals {
# Shared inputs passed to every proxmox_node module call.
node_common = {
search_domain = var.search_domain
dns_nameservers = var.dns_nameservers
ssh_key = data.external.infisical.result["ssh_public_key"]
ansible_user = var.ansible_user
lxc_password = data.external.infisical.result["lxc_password"]
}
}
# Provider aliases must be statically declared, so each hypervisor needs its
# own module block. To add or remove a hypervisor, add/remove the corresponding
# provider alias in providers.tf and a module block here. Everything else
# (node contents, common config) lives in the parent repo's terraform.tfvars.
module "proxmox" {
source = "./opentofu/modules/proxmox_node"
node_name = var.hypervisors["proxmox"].node_name
storage = var.hypervisors["proxmox"].storage
lxc = try(var.nodes["proxmox"].lxc, {})
machines = try(var.nodes["proxmox"].machines, {})
search_domain = local.node_common.search_domain
dns_nameservers = local.node_common.dns_nameservers
ssh_key = local.node_common.ssh_key
ansible_user = local.node_common.ansible_user
lxc_password = local.node_common.lxc_password
providers = {
proxmox = proxmox.proxmox
}
}
locals {
hv_hosts = {
proxmox = { lxc = module.proxmox.lxc_hosts, vm = module.proxmox.vm_hosts }
}
all_hosts = flatten([
for hv_name, hv in local.hv_hosts : [
for h in concat(hv.lxc, hv.vm) : [
for tag in h.filtered_tags : {
tag = tag
name = h.name
ip = h.ip
vmid = h.vmid
}
]
]
])
tag_groups = {
for tag in distinct([for h in local.all_hosts : h.tag]) :
tag => [
for h in local.all_hosts : {
name = h.name
ip = h.ip
vmid = h.vmid
} if h.tag == tag
]
}
}
resource "local_file" "ansible_inventory" {
filename = "${path.module}/${var.ansible_inventory_path}"
content = templatefile("${path.module}/opentofu/inventory.tpl", {
hypervisors = local.hv_hosts
tag_groups = local.tag_groups
ansible_user = var.ansible_user
ansible_ssh_private_key_file = local_sensitive_file.ssh_private_key.filename
})
}
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
proxmox = {
source = "telmate/proxmox"
version = "3.0.2-rc04"
}
local = {
source = "hashicorp/local"
version = "~> 2.2"
}
}
}
data "external" "infisical" {
program = ["bash", "-c", "infisical export --template=./secrets.tmpl"]
}
provider "proxmox" {
alias = "proxmox"
pm_api_url = var.hypervisors["proxmox"].api_url
pm_api_token_id = data.external.infisical.result["token_id"]
pm_api_token_secret = data.external.infisical.result["hv2_token_secret"]
pm_tls_insecure = true
}
+13
View File
@@ -0,0 +1,13 @@
{{$secrets := secret "02ba885c-7f22-4290-9b0e-845f65cf7d7c" "prod" "/terraform"}}
{
{{- range $secret := $secrets }}
{{- if eq $secret.Key "HV_USER" }}"token_id": "{{$secret.Value}}",{{ end }}
{{- if eq $secret.Key "PROXMOX_TOKEN_SECRET" }}"hv2_token_secret": "{{$secret.Value}}",{{ end }}
{{- if eq $secret.Key "LXC_PASSWORD" }}"lxc_password": "{{$secret.Value}}",{{ end }}
{{- if eq $secret.Key "SSH_PRIVATE_KEY" }}"ssh_private_key_b64": "{{$secret.Value}}",{{ end }}
{{- if eq $secret.Key "SSH_PUBLIC_KEY" }}"ssh_public_key": "{{$secret.Value}}",{{ end }}
{{- if eq $secret.Key "IPAM_USERNAME" }}"ipam_username": "{{$secret.Value}}",{{ end }}
{{- if eq $secret.Key "IPAM_PASSWORD" }}"ipam_password": "{{$secret.Value}}",{{ end }}
{{- end }}
"_sentinel": "1"
}
+32
View File
@@ -0,0 +1,32 @@
# ── Site configuration ─────────────────────────────────────────────────────────
hypervisors = {
proxmox = {
api_url = "https://192.168.0.240:8006/api2/json"
node_name = "proxmox"
storage = "local-lvm"
}
}
enable_ipam = false
ipam_endpoint = "https://ipam.example.net/api"
network_subnets = {
lan = "192.168.0.0/24"
}
ansible_user = "ansible"
search_domain = "lan.oswaldtheotter.net"
dns_nameservers = "1.1.1.1"
# ── Node definitions ───────────────────────────────────────────────────────────
nodes = {
proxmox = {
lxc = {
}
machines = {
}
}
}
+93
View File
@@ -0,0 +1,93 @@
variable "hypervisors" {
description = "Per-hypervisor configuration. Keys must match provider aliases in providers.tf (e.g. hv2, hv3)."
type = map(object({
api_url = string
node_name = string
storage = string
}))
}
variable "enable_ipam" {
description = "When true, register all IPs in PHPIPAM. Requires ipam_endpoint and IPAM credentials in Infisical."
type = bool
default = false
}
variable "ipam_endpoint" {
description = "Base URL of the PHPIPAM API, e.g. https://ipam.example.com/api"
type = string
default = ""
}
variable "network_subnets" {
description = "Named map of CIDR ranges managed by PHPIPAM. Only used when enable_ipam = true."
type = map(string)
default = {}
}
variable "ansible_user" {
description = "User created on each managed host for Ansible access."
type = string
}
variable "search_domain" {
description = "DNS search domain set on all managed hosts."
type = string
}
variable "dns_nameservers" {
description = "DNS nameserver(s) set on all managed hosts."
type = string
}
variable "ansible_inventory_path" {
description = "Path where the generated Ansible inventory file is written. Defaults to ../ansible/inventories/generated.yml relative to this module, which points to the parent repo when used as a git submodule."
type = string
default = "./ansible/inventories/generated.yml"
}
# LXC and VM definitions per hypervisor, keyed by the same names used in
# hypervisors and providers.tf. Both lxc and machines default to {} so a node
# can be declared with only one type of workload.
variable "nodes" {
type = map(object({
lxc = optional(map(object({
hostname = string
vmid = number
template = string
unprivileged = bool
onboot = bool
tags = string
memory = number
disk_size = string
nic_name = string
bridge = string
ip = string
gw = string
})), {})
machines = optional(map(object({
hostname = string
vmid = number
ip = string
ip2 = optional(string, null)
vlan = optional(number, 0)
template = string
full_clone = bool
onboot = bool
ciupgrade = optional(bool, false)
tags = string
agent = number
memory = number
disk_size = optional(string, "32")
balloon = number
cpu_type = string
cores = number
sockets = number
vcpus = number
bios = string
machine = string
bridge = string
})), {})
}))
default = {}
}