diff --git a/.gitignore b/.gitignore index 2fda424..6ce1a45 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,9 @@ boilerplates/**/.env # Misc doppler-token.yaml + +# talhelper generated machine configs — contain secrets, never commit +talos/talhelper/clusterconfig/ +# talenv.yaml plaintext — only commit the SOPS-encrypted version +talos/talhelper/talenv.yaml +!talos/talhelper/talenv.sops.yaml \ No newline at end of file diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..e91d378 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,3 @@ +creation_rules: + - path_regex: talos/talhelper/talenv.yaml + age: age1xkyuv8r8ce6lu3d64jfspz4e50k6pxlaxwmuplzrtnpfnnrnycaq6mfsrn diff --git a/docs/city-hall-setup.md b/docs/city-hall-setup.md new file mode 100644 index 0000000..590c35c --- /dev/null +++ b/docs/city-hall-setup.md @@ -0,0 +1,321 @@ +# city-hall Setup Guide + +`city-hall` (`10.1.71.38`) is the Ansible/Terraform control node and admin jump box +for the `mk-labs` homelab. This document is the authoritative list of required software +and configuration. + +## System + +- **OS**: Ubuntu 24.04 +- **User**: `wed` (passwordless sudo) +- **SSH key**: `~/.ssh/ansible` (used by Ansible and Terraform SSH agent) +- **Repo**: `/opt/git/homelab` (cloned from `git.mk-labs.cloud/rblundon/homelab`) + +--- + +## Required Software + +### Core Tools + +| Tool | Purpose | Install | +|---|---|---| +| `git` | Source control | `sudo apt install git -y` | +| `terraform` | Infrastructure provisioning | See below | +| `ansible` | Configuration management | See below | +| `packer` | VM template creation | See below | + +#### Terraform + +```bash +wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg +echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list +sudo apt update && sudo apt install terraform -y +``` + +#### Ansible + +```bash +sudo apt install pipx -y +pipx install ansible +pipx inject ansible netaddr +ansible-galaxy collection install effectivelywild.technitium_dns +``` + +#### Packer + +```bash +sudo apt update && sudo apt install packer -y +``` + +--- + +### Talos Cluster Tools + +Required for provisioning and managing the `fastpass` Talos Kubernetes cluster. + +| Tool | Purpose | Install | +|---|---|---| +| `talhelper` | Talos cluster config generation | See below | +| `talosctl` | Talos cluster CLI | See below | +| `kubectl` | Kubernetes CLI | See below | +| `helm` | Kubernetes package manager | See below | +| `age` | Encryption key generation (used by SOPS) | `sudo apt install age -y` | +| `sops` | Secret encryption for `talsecret.sops.yaml` | See below | + +#### talhelper + +```bash +curl -fsSL https://i.jpillora.com/budimanjojo/talhelper! | bash +``` + +Verify: +```bash +talhelper --version +``` + +#### talosctl + +```bash +curl -sL https://talos.dev/install | sh +``` + +Verify: +```bash +talosctl version --client +``` + +#### kubectl + +```bash +curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +chmod +x kubectl +sudo mv kubectl /usr/local/bin/ +``` + +Verify: +```bash +kubectl version --client +``` + +#### Helm + +```bash +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +``` + +Verify: +```bash +helm version +``` + +#### SOPS + +```bash +SOPS_VERSION=$(curl -s https://api.github.com/repos/getsops/sops/releases/latest | grep tag_name | cut -d '"' -f 4) +curl -LO "https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.amd64" +chmod +x "sops-${SOPS_VERSION}.linux.amd64" +sudo mv "sops-${SOPS_VERSION}.linux.amd64" /usr/local/bin/sops +``` + +Verify: +```bash +sops --version +``` + +--- + +## SSH Agent + +The SSH agent must be running and loaded with the Ansible key before running Terraform +or Ansible. Add to `~/.bashrc`: + +```bash +eval $(ssh-agent) > /dev/null +ssh-add ~/.ssh/ansible 2>/dev/null +``` + +--- + +## SOPS / age Setup (first time only) + +Required before running `talhelper genconfig` for the first time. + +```bash +# Generate an age key pair +age-keygen -o ~/.config/sops/age/keys.txt + +# Note the public key from the output, e.g.: +# Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +# Create a .sops.yaml at the repo root referencing your public key +cat > /opt/git/homelab/.sops.yaml << EOF +creation_rules: + - path_regex: talos/talhelper/talsecret.sops.yaml + age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +EOF +``` + +Generate and encrypt the secrets file (talhelper looks for `talsecret.sops.yaml` by default): + +```bash +cd talos/talhelper +talhelper gensecret > talsecret.sops.yaml +sops -e -i talsecret.sops.yaml +``` + +--- + +## Terraform Notes + +- Always run from the relevant module directory (e.g., `terraform/talos/cluster/`) +- State files live in `states/` within each module — never committed to git +- Sensitive vars passed via CLI, never in committed tfvars files +- The `!` in the Proxmox API token requires single quotes to prevent bash history expansion: + +```bash +terraform apply \ + -var-file="tfvars/fastpass.tfvars" \ + -state="states/fastpass.tfstate" \ + -var='proxmox_api_token=terraform@pve!terraform-token=' \ + -var='unifi_password=' \ + -var='technitium_api_token=' +``` + +--- + +## Ansible Notes + +- Always run from the `ansible/` directory +- Roles path: `ansible/playbooks/roles/` +- SSH user: `wed` +- Vault password file: `~/.vault_pass` (not committed) + +--- + +## Talos / talhelper Notes + +- Config lives at `talos/talhelper/` in the repo +- Secrets file is named `talsecret.sops.yaml` (talhelper default) — SOPS-encrypted +- Network interface on Proxmox VMs is `ens18` (not `eth0`) +- `talosctl` config (`~/.talos/config`) is generated by talhelper — keep a backup +- Cluster endpoint: `https://10.1.71.65:6443` (VIP) +- Talos API endpoint: `https://10.1.71.65:50000` +- Initial `apply-config` requires `--extra-flags="--insecure"` (maintenance mode only) +- `bootstrap` only runs once — bootstraps etcd on `space-mountain` + +--- + +## fastpass Cluster Bootstrap Runbook + +Run from `talos/talhelper/` unless otherwise noted. + +### 1. Provision VMs (Terraform) + +```bash +cd terraform/talos/cluster +terraform apply \ + -var-file="tfvars/fastpass.tfvars" \ + -state="states/fastpass.tfstate" \ + -var='proxmox_api_token=terraform@pve!terraform-token=' \ + -var='unifi_password=' \ + -var='technitium_api_token=' +``` + +### 2. Generate Talos Machine Configs + +```bash +cd talos/talhelper + +# First time only — generate and encrypt secrets +talhelper gensecret > talsecret.sops.yaml +sops -e -i talsecret.sops.yaml + +# Generate machine configs (uses talsecret.sops.yaml automatically) +talhelper genconfig +``` + +### 3. Verify Nodes are in Maintenance Mode + +```bash +talosctl -n 10.1.71.66 get discoveredvolumes \ + --talosconfig clusterconfig/talosconfig --insecure +``` + +Confirm install disk is `/dev/sda`. + +### 4. Apply Machine Configs + +```bash +talhelper gencommand apply --extra-flags="--insecure" | bash +``` + +Nodes will install Talos to disk and reboot automatically. + +### 5. Monitor Install + +```bash +talosctl -n 10.1.71.66 --talosconfig clusterconfig/talosconfig dashboard +``` + +Wait until all nodes show `Running` state. + +### 6. Bootstrap etcd (once only) + +```bash +talhelper gencommand bootstrap | bash +``` + +Only run once — bootstraps etcd on `space-mountain`. + +### 7. Get kubeconfig + +```bash +talosctl --talosconfig clusterconfig/talosconfig \ + --nodes 10.1.71.66 kubeconfig ~/.kube/config +``` + +### 8. Approve Worker CSRs + +```bash +kubectl get csr +kubectl certificate approve $(kubectl get csr --no-headers | awk '{print $1}') +``` + +### 9. Install Cilium (CNI) + +```bash +helm repo add cilium https://helm.cilium.io/ +helm repo update + +helm install cilium cilium/cilium \ + --namespace kube-system \ + --set ipam.mode=kubernetes \ + --set routingMode=tunnel \ + --set tunnelProtocol=vxlan \ + --set kubeProxyReplacement=true \ + --set k8sServiceHost=10.1.71.65 \ + --set k8sServicePort=6443 \ + --set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \ + --set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \ + --set cgroup.autoMount.enabled=false \ + --set cgroup.hostRoot=/sys/fs/cgroup +``` + +Note: The explicit `securityContext.capabilities` flags are required for Talos — Talos +restricts privileged container capabilities by default. The `cgroup.autoMount.enabled=false` +and `cgroup.hostRoot` flags are required because Talos manages cgroups itself. + +### 10. Verify Cluster + +```bash +kubectl get nodes +kubectl get pods -n kube-system +``` + +All nodes should show `Ready`. Cilium pods should be `Running`. + +### 11. Post-Install HA Setup + +See output of `terraform output` for the full HA registration commands. +Register VMs with Proxmox HA and add to node-affinity rules once the +cluster is stable and the ISO is no longer needed for boot. diff --git a/talos/talhelper/talconfig.yaml b/talos/talhelper/talconfig.yaml new file mode 100644 index 0000000..c878c42 --- /dev/null +++ b/talos/talhelper/talconfig.yaml @@ -0,0 +1,188 @@ +# ─── fastpass Talos Cluster Configuration ──────────────────────────────────── +# Managed by talhelper on city-hall (10.1.71.38) +# Generate configs: talhelper genconfig +# Generate secrets: talhelper gensecret > talenv.yaml && sops -e -i talenv.yaml +# Apply configs: talhelper gencommand apply | bash +# Bootstrap: talhelper gencommand bootstrap | bash + +clusterName: fastpass +talosVersion: v1.13.2 +kubernetesVersion: v1.32.3 + +# ─── Cluster Endpoint ──────────────────────────────────────────────────────── +# Points at the Talos native VIP (10.1.71.65) which floats across +# control plane nodes. All kubectl and talosctl traffic goes here. +endpoint: https://10.1.71.65:6443 + +# ─── Domain ────────────────────────────────────────────────────────────────── +domain: cluster.local + +# ─── CNI ───────────────────────────────────────────────────────────────────── +# Cilium is installed post-bootstrap via Helm. +# Talos ships with flannel by default — disable it so Cilium takes over. +cniConfig: + name: none + +# ─── Patches applied to ALL nodes ──────────────────────────────────────────── +patches: + - |- + machine: + time: + servers: + - 10.1.71.21 + sysctls: + net.ipv4.ip_forward: "1" + kernel: + modules: + - name: br_netfilter + - name: ip_tables + kubelet: + extraArgs: + rotate-server-certificates: "true" + +# ─── Nodes ─────────────────────────────────────────────────────────────────── + +nodes: + + # ── Control Plane ────────────────────────────────────────────────────────── + + - hostname: space-mountain + ipAddress: 10.1.71.66 + controlPlane: true + installDisk: /dev/sda + networkInterfaces: + - interface: ens18 + addresses: + - 10.1.71.66/24 + routes: + - network: 0.0.0.0/0 + gateway: 10.1.71.1 + vip: + ip: 10.1.71.65 + patches: + - |- + machine: + network: + nameservers: + - 10.1.71.32 + + - hostname: big-thunder-mountain + ipAddress: 10.1.71.67 + controlPlane: true + installDisk: /dev/sda + networkInterfaces: + - interface: ens18 + addresses: + - 10.1.71.67/24 + routes: + - network: 0.0.0.0/0 + gateway: 10.1.71.1 + patches: + - |- + machine: + network: + nameservers: + - 10.1.71.32 + + - hostname: splash-mountain + ipAddress: 10.1.71.68 + controlPlane: true + installDisk: /dev/sda + networkInterfaces: + - interface: ens18 + addresses: + - 10.1.71.68/24 + routes: + - network: 0.0.0.0/0 + gateway: 10.1.71.1 + patches: + - |- + machine: + network: + nameservers: + - 10.1.71.32 + + # ── Workers ──────────────────────────────────────────────────────────────── + + - hostname: jungle-cruise + ipAddress: 10.1.71.69 + controlPlane: false + installDisk: /dev/sda + networkInterfaces: + - interface: ens18 + addresses: + - 10.1.71.69/24 + routes: + - network: 0.0.0.0/0 + gateway: 10.1.71.1 + patches: + - |- + machine: + network: + nameservers: + - 10.1.71.32 + + - hostname: haunted-mansion + ipAddress: 10.1.71.70 + controlPlane: false + installDisk: /dev/sda + networkInterfaces: + - interface: ens18 + addresses: + - 10.1.71.70/24 + routes: + - network: 0.0.0.0/0 + gateway: 10.1.71.1 + patches: + - |- + machine: + network: + nameservers: + - 10.1.71.32 + + - hostname: peter-pans-flight + ipAddress: 10.1.71.71 + controlPlane: false + installDisk: /dev/sda + networkInterfaces: + - interface: ens18 + addresses: + - 10.1.71.71/24 + routes: + - network: 0.0.0.0/0 + gateway: 10.1.71.1 + patches: + - |- + machine: + network: + nameservers: + - 10.1.71.32 + +# ─── Control Plane Config ──────────────────────────────────────────────────── +controlPlane: + patches: + - |- + machine: + features: + kubernetesTalosAPIAccess: + enabled: true + allowedRoles: + - os:reader + allowedKubernetesNamespaces: + - kube-system + cluster: + etcd: + advertisedSubnets: + - 10.1.71.64/26 + coreDNS: + disabled: false + proxy: + disabled: true + +# ─── Worker Config ─────────────────────────────────────────────────────────── +worker: + patches: + - |- + machine: + kubelet: + extraArgs: {} diff --git a/talos/talhelper/talsecret.sops.yaml b/talos/talhelper/talsecret.sops.yaml new file mode 100644 index 0000000..8984101 --- /dev/null +++ b/talos/talhelper/talsecret.sops.yaml @@ -0,0 +1,43 @@ +cluster: + id: ENC[AES256_GCM,data:NUwEiXFZ1F5LbpgT/Qe4Y/z6ScGqqOKjQ3KhKw1FF7ddZjJnY0+0uY+viQg=,iv:eYLLsZWWxrMXUmjrTYdsuUC3I/5BJsAshZtGVsDNzyc=,tag:PIP/rm12hYfc5Cce/sP9bA==,type:str] + secret: ENC[AES256_GCM,data:BL0WIDIPdaUig4lO7Os+ElO7+rylDJMnYimANiwl+3KOZ8rHGfGf1rLBZBM=,iv:t6xOA58qsBR+HHYWT2n2hqYbjGzZfguzonQGwaZa9KM=,tag:u3udtGNyni0G+3KtGy4mmQ==,type:str] +secrets: + bootstraptoken: ENC[AES256_GCM,data:1sl/nm2B5gaWnveOwTj3S7p7jYf7eDw=,iv:Q2cgSYhDTQ0s8oZzhAnh12SBB59FYSAZGXm1VSaUv8k=,tag:loVoraDZRrI5ukQ0dpN7pQ==,type:str] + secretboxencryptionsecret: ENC[AES256_GCM,data:a+lRKO59bWuJ5wkdUMy5AFt2S4SQWxLRBqldWoyg6EplchsRnhNqlqLYmgI=,iv:Q8Eyjrj+FJoo6FMMPVkD8fSEh4kF57klogN+fWmwgXc=,tag:RkWZzg9GyVtpVaaYglpt5w==,type:str] +trustdinfo: + token: ENC[AES256_GCM,data:FJsnC/RBpOfUDd4Tcc6MN/mDLlWqZY0=,iv:I07sFQGdOWsMuNODbmHzrLfKvIxurFE1r4MvFIWTsXk=,tag:ZDnCqZ9VWDRWl/iZurHuBg==,type:str] +certs: + etcd: + crt: ENC[AES256_GCM,data:dtVvQZRWHh3E6QVUjW6irtr8U0UtUkNAbtzC1njha+uaJlocX9ectmyQNcNnnPBGME2n3U7sKxZ0OiJ3Gr1M7o4fsKLsh+m5OvZ8C6gTSl6N0dyUyZYl1yfOn9cYhBMSuCEjp2sIWaXYFAIKfqOSW0Bt6E4rL1T3F389efKbx7uBszOdu5QTeIGAs2ZEKi0iyXQUU05Ps094bSKvYU8mDk9PWMIZF2/owTSih36wPhvaIa1wamX2ceIQm6L5A3VHYm9ZeiYVsHZiUF4BD6KV/K3h50UvCumhC5iF3fypHrGm8I+xEAHaz0b63cnCxz56VjifapzR+fiSUHfz+Zg+vPc7qMyZiN/+uZKD76WI8IMRB1ERmXukOkDkYnknwGP8mZHCAIPqlATn2yJHfLEG7gX0D5hOLvHmiR7SNzb9auqh851CD5m1DicNHLhwxxGPHX1YagGSRpgHZorE7GAHGSCqVtzzVJSrJ3rkQWSe9hRNs3s59/dvkqi4vfBhtCgF72MwWF9doL2Q3H6+AZKaW98MfwqcpbtvmRpEF18JghBV0TNZTzJfMGKB3tC/bezxwH/7Z0a5wZMEpXD4AB/4hmH4NYC4syYSTRmboLb29t1NXIls6fTGqldg9Zaa+0Yc9rxqVVdXOW0eqng6MfRspcfu0LLOFbvP0LwEWrV3923bw0qA5VF8p2ExK1amI5eiQWvj42HBDoYUY4LHFAxAliV58dzxNx3XsNUpkum+eKIsOVECJKRXJsaT123224xhcFmhU0TU27KnsP4GC/W500KjqAJYjuvuj0KDZi9VFtC+gE8EjU9jZAKlllpat/r/AvrK0+hrToCIpdWUZOd+3WJtGqHd0Ba5G/SLFtq+lN+Rjfyp5EVnmr7oq6OW4jJAxhYHqQp5P/MORM2NIu2gQKijRjp8z9c3mvDIgZMs18hvV3vaUdCIfjpuDZVqh+OURwKnSD+5EEqU5RXeBAmAlGldDyXb9MO5yooxqoL9LNR/c8s3/MP7bvGDmbzMW+laP3D1IA==,iv:580nIQ27/mNovQZ0QDN2ofozqppBrjhYB/1QLzfHvoQ=,tag:q4X91Pkuvi8iECNo/uePhg==,type:str] + key: ENC[AES256_GCM,data:7AKvQZDbHtDTFkF3vmSbb70RX5WpMDEfVKL/1wb6dkieMc7G700ZhpVyXZbUoazD1262WHWL9Wgc1aAtFNMVeWmFzTKhhq53Yh4Ma9D68ERS6Gu9/vQsNne6Cy0/GRN+wmi1X6BuSNnm2CWjBuwuEt8DZdJ2cSlf/jPjOuwAJFV7UhMUZLvOLBPUZkTw5R0Y+4HY884ND8VZenlr9oohkpwdTnsGTyRI6myFQpd57f82ABwPA1IAmdCcbYglrBYWWI6eX4GlJF0zZu/c/xsyTQhGKDoqDOKNFt+U6Z3NZdcldivn00RE1SWRUKPo21FabUXucUySu++i0SOjJq7GcddJpkmO4LTMaLY7fwje7Gsu7xxu960JLDf+zzf0JL8tHVBxIeheu54F4q0J2Dt49w==,iv:koKw5/e8aZYXnEJBuM1U86JeqplnpI8f2r3xB40tI4Y=,tag:x1jJbs54OmIPx8hFT71ong==,type:str] + k8s: + crt: ENC[AES256_GCM,data:Bwbx03E6BkJ+Y6RfNfQjiL2VY2eVhYw9TXdbA/Ai8F1P+I7Y6xJ/OSG0H1BdpdV/7gp5GZGLBTEkjkkCwFLYNwrkXVJfGPRZdWZwR9Wre97w9xRt4iG07zFFVigV2Tsd84fa8jizu13hofT5maqhvTjEbZ2AXHFb3YKreDk4ysuBAS8ogej2iU/UEtshMIfXbd8idoTgqfDA8msCtHjqz6N9xIFpNBi6cIk+2mnB0XcRWWQYrhRf2IhlbyvHJbZsEvvzpp6SM2U3gB3U2c/TQVxO7/r5F/o7SRBdYScVZJUxZmTfvY8f+P9WTqKwyBs+Bv0TDs6Ehd183oowIxTolK4Wk0CTE8W0ckEJxPOkQZmHgfVm33X4Ac2E9xhZmIJ6rdrT138hWpUJKc12s22Efv0uD5ZroIvnrDcHGbVYOMaqClt4YNDGRD2Lb+uVbC1Y4ZA+9WhkeWxSnN5NcsX/V8q1PtEH3HBl3SgLwiOp7wFwCHOaTngSIcvHVM90e9wsy7lunPwcd3vCWfFddxsUFVOU+SowUp8iQ2cafOGzmMrxQmPgW4csLZgC+KAcbFRMOvmASE3FxxJKwFo0bU8TvnDHBnKZjU3ozQql5YVN+TSi6IEdeZs4h7g99ZU6V0UxGFGxTuLM1oLcT6yvWxzYu/oz7NQB9H314NeAQXE3ItZ1KGzijxZlYHIHEEQaamMlC8+iWqCRd1w4jaiQBkeD0li5MWhXhQ/QEHiCVL3Us3RKQDq/k0bDK6zrUyr1SWljFuUbeqfDZY2/hVNCQs4yKXA8gBZNA8xU1iVmsOJugtRbUPkvXsU/fMyGvg/suYsUxUSKIqY43bjslkSzX/yBKpVYu865PR1Q9d1zP3ajUc2M+fqULWoGtKn5uy9x/Eb9AX+MkHT7cAvI6BUQy0OQght3mU6hUKCuB1YmGe8IqExVkb1iwrh+S5U2awZ3+njyMl0dqjVgCGZTq96j2s9O3V6TkiS+Fx9mQYL47ZewIgAC8YyAOZQ+TRCx5aPhk/tgX8+J54O9BN8kEzjFAzhoeI4DoOUCgQgQRImiyg==,iv:AKZ/06LT+u/iLizMiBHOfnPuhcP+AG2FntrZi9ZocBM=,tag:bLvnvZwcjkhqhPYZalr0ig==,type:str] + key: ENC[AES256_GCM,data:OurjgZwQodXLjaaul9GqW5NxToitxDGSpqkx4nqkIYbnA5ggHV4pROkCpGD8YYR4hqvakAiIodsCnjy0GkQTK9L8QfQLxMeNqyLUtVlxY4bkOLwYGWCLcZvOFs/ahutSWTuhCNxfHnpoYNyMpvQaa3vuBN2DNyagpkeMXZqCwEUK+AVVE8/vLz6b2YP/e0DdybiRZPyNh69MrBu6UUjna2H0XS9kTKEVMbETjv67OQaGpcEElQGhRFQ+Aw+IvKx4FQdySJhF1MqX3NLzaJ0/rK2cKhqzzYWH1aWWO28vktQULUVjpHuw3pDbZ1vi+rN6iUI/dUla4BhNCQBXPkibEeu8z75NCBNvUVpZoXn99qGXGRQz2oBwm5uQaCoHm9LS/HbEg8VXK+3m/L02UqZgJw==,iv:vYqyODGdLeHfFZfG7eqHVM8WZtw/NhFQorcrErZwSFA=,tag:BM0BrxGpYGrbkPlT3BkN4w==,type:str] + k8saggregator: + crt: ENC[AES256_GCM,data:GF1KTpy2zOdKPJy2iQI+9MoNEX+0wfogK2O2G9LOA3v+izlRKnnkb9Lo0FkyhPVv+269QgnI/bQBOrN26vNixt3Mc3xagYPIOtxPl+LlHcm6M2xLiF21JmK123XQsgUBaJbDFx8x9PhxqnPmrv+OIi0EJxxHgQ5b08uWfsHvUSdOGq4VMwNQvAAcqfFV+E7F7nxPXnMxUMFoRzO0ZKXY6l/KdTJxcdy+BaYFUXctJd8HBkbR193/FhH8dumSvBo8uaSurMOGnhN/pMk1JVyVMFGk5c/rTqLihY+untvEKh/f9twwl0pdQ13NYEgIV95tDhy7jxCEPZX1S4857jGHBi2s45KoVDvxVAdtopFOnP9nfnya9oUb5jXaz5tooVD1l+ElhOhMa4oj7xK7uc3tOfzHhHh/NR19m5ymB9MAQr6JYeLAfaPKXusSSGV13VvuH0cusEwP4VwbmtUW9Ldo1aj1aNNi3kNtxifLGx4w01fF4XKTgdzI5BeOM6Dymj6KvFSo8di87QiUvWeIrQ6T0fSnZQHyE2G3g97EvdSECDtfGDIIEOcBuvRgZ0Y6LMRD4T+udPF74hm1hmZbBKWSOwvXGjjEupI2vmA1yo3JUxLJgPQRJjsDNHdEbIguiNgfTCnFcsjtxCUU1V6921LBJsHLawMuUD9RgEBS9FkKCQbPk9msHFB/ZXxBuDMZEzNP17XoBuxXKii0/hnczRF/sd+7ZElwl4H+LifETMThc+Ff6/bjS0DCEg8OWnvFUjl6IlHnx4J9roJngxZtj/bFtBlIKODGJOZRstmUIsr5qXxLaI875eObuK3xy/AN57v77jieN7RTOTCWgexul4EuW74E074yC+AlXGVVe0XZAGQ/Gu9I8KN+rNLQACY9gEJiU0iDv8SVKboFr2nhYss0IBxLuIj8HwudodgORBigT9FXPk5erRghccfsIxK8fZ+k,iv:66ePYW0wK2iUwr575brOOBlFAoKTi0N9FaJofHxyCYQ=,tag:ofoX8fwmQzj8KIZt46kj+g==,type:str] + key: ENC[AES256_GCM,data:ZKfGMfFCccWqdwQ5Xq8xENlvOSBZ9W5gIU6Uk2q73mFv+lH0pQnGS0i1l3QLC5eiQWcTouq5H5h7bJQ3+PNhCmxScNrp9JGrNYCNnAUCA8DpGW/MeXZc1nWPSBqG/ZayOIFsZraIh6HzZCSwe7edyg/wmCnY4kprahtd/a3IJQHUSCnD1iW2TSYo3kmHAGaXBwvLICVNruUeoYZ2WWunhp/ZeN7VWXMmRw5l07dmmd5lcDDL66Fb/zjJrJNlaGoJUPduVqp+VLs32XCuIGAeXPH8VwlDFRztGsDVUrPhd2p2poz1wz4k+omugbh3ibk2G7TXdD3THI2NptQyyu0gdBnv8wzIksyDhWbT1ByYDPhOS333AAZZZqEeSHxZQy2UovF1UHDmJ5mx7GMky6vzrQ==,iv:oYEK//Ivg8X1N1XjOHOXjagutsxOtM90jSvcfcqmY7o=,tag:MFYIeCRkmhuoLvhLCyD/yw==,type:str] + k8sserviceaccount: + key: ENC[AES256_GCM,data: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,iv:hq+iFN+FkFdiCSVwH3LvJl3hEjnuQN8PLEdQrOxpb6M=,tag:Y/W/S9+Akxpz/neRxAcuxA==,type:str] + os: + crt: ENC[AES256_GCM,data:zVjiSE0bFWTqzGF/KMlK+f57lMwhMpEOGbtoB/7fv8ZTF9HuX2v8CSDItOIYYm5g/P83IHeX/K62lWSgLYl1xT0WCEv4mDUHCcp4fAanXMvh62XSzFKTTPzbfP9s7UlDF8AiGZV2palBgl9Vi0MezqyAg0PEAvoI9q2csOJQg7+VsiVu0wiVSZL1Zt4+ZGMHDqI3YwdHcaB/M1m0ZTIJVMiPstmTNpF3wjFcFp9+I9c3oQfbGNjOtOgvxAIGCqZuYwpKnTnhSyaxUhysNTBGpILcbeix6KLRlNLGkJ2ri/EdnmfDM+VRahylP2+bmTbFjyjs97iYSKLnoyLqeOI30JAasphkdqcTvyOxH1Odok1M3viezSa3cirsoCEIKHoe0T+kJObrmHVJi7dh2PDx0ZRu8JQ5jTCyr/FJnJpLrxuFX3iUXDrrmDE78QVul9Q1rmOIG7TOHfM6WT74xRdvx80oSbkZq/j8XmbsIA43buxdImrFCq2k/phu+bN3vEyXQZcXi/wXoCGEfrq+DqizRcLlVtjS+PXcwCWiqPBYc8JG3qAmsEZCLqFOJgCverWgCLXbmlr952I6rAjteGVW6+83IXaRxNDJvX9oc/71t/2tCiyrgtftOLPErSTk3UWZAQqyapephvKtq3lHkaimiASQZ0YMrCXUPhnHx8tplsaJZKUphdsxkvBYsoPqVdCVrB7fdwyiewD6d8yI2nkq1SRiC3lDap8CAh5SzGetIkhah4XomKZoT+eYSfO76/RMSogGT0qLRkKHMGLuBxFPVrnyAKtkXfBmcaipKCcmwC4EB+5Nlrli8cSAYGihtXyNxkmJvV5EL1w6Mu/bitTiVIEWr1Hb6s2rBSFPRNz5M0aeSOP3,iv:NYgVz9OsF9XHin7E1+UWgJu3NXgcKj+5bRKS3OJQVtY=,tag:CZbSQcffK4yA5rq+IIC/rA==,type:str] + key: ENC[AES256_GCM,data:FLLchp6Gq0lK3sAxCFNxg0i6seWD50ZlfN2zu417ZX9nZfSpZU3xOfFbRjYAv0zN4kUaoS/K1eVySdKVnoqXXVrIiBfW0gH2n/wjG0u8jIN6KA6z4rOHzgnEn/7YYtKPVxYg85JIJpjDJB8gK00TYpKuycbaeBLiEUrQb/G3mpVOmpFzacme6PON/aZmWXdG1UtL1QHPdONMCJDh0hcXugXWSj/YyRYSov8WokEXrW7R2fRV,iv:f91XwZj1otLwVRBpAI3+Ga5Vrv/Nx9FiRDO9sTRzbVA=,tag:WrC4NVKig72BcmXReESB+A==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1xkyuv8r8ce6lu3d64jfspz4e50k6pxlaxwmuplzrtnpfnnrnycaq6mfsrn + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNRWsyNklnTnR3UytYTGxY + bDRML3o5R0tmRlNNSnVuamRXdlZ0NjgwYVdJCnhkR3IrcmFpUTJqZExwUjM0YTRn + amQyODFVd0pKSERUY0k4WmEycCtjOWsKLS0tIHFwS29OTG5DQ0s1RHBZdTlnNkhB + SVNuWGxjQ05GVW9xNFhpRmlXZzI5TEEKPxoO4MgFpMUPTVUKadW+q50AThnPDj/M + A4kUwn6VPBJwx7Bq+4EF1CQiNe/T5W364mHKOqT7xEsvAef6nDkPkg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-17T20:06:30Z" + mac: ENC[AES256_GCM,data:4r1o7rIuAFv9Miwd+x7nOhYpooFKa8e/PPOeL3KgXP18Uwe6y3extqnrczwMSkHvLMuo7QadTzbGtbfdYp7pMrQFiE/pBwG5kKnsKiFaejtD/In1XBO8TtRKJ0rOKX7BeIOZxyvs5ktkULj/lKooeoR/vOS8GDiPP7WsYv5QMl8=,iv:agkdqnXyIARQtR5iwPB9GyIaKgDI2XZ4SrCO+7BSKqM=,tag:y7VI7ZcGuTEzOCU1PnqSMg==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.4 diff --git a/terraform/talos/cluster/.terraform.lock.hcl b/terraform/talos/cluster/.terraform.lock.hcl new file mode 100644 index 0000000..79b5f74 --- /dev/null +++ b/terraform/talos/cluster/.terraform.lock.hcl @@ -0,0 +1,60 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/bpg/proxmox" { + version = "0.106.0" + constraints = ">= 0.74.0" + hashes = [ + "h1:CnUVTBB9ZlJRH9xhyxPIAY++4gSRqMeLMZokzgN1V2A=", + "zh:1a8d717dada526bf0aab51683352390e194bf5c05c8c69464e00befd113121d6", + "zh:31db845b45df5839b442104e31aa41f832eb1f363f5278df290f1a906b731150", + "zh:36af78f4395ac64e5a997fad6cfb18f369e5cae7e4895086ef7d39c65e3d6324", + "zh:526e87482db9a0f8c7a22b430214886a3df4fd08c6949b7235d9a088850fd736", + "zh:6c99f833606527b86fed0ef74bf8723e118b98a8067ff9f3745e90b095a4bc2b", + "zh:7aa85292c4891e85a817821dd31d736e977eabe3a04d29913d1cb5dfca2020ef", + "zh:879dc2b315093c2818c8ff289fcb20d6925dab57e7d758b5e646c6b35860ac37", + "zh:955eb04b8df5438a717af9766f02b48e919c0a33c97590905ee04f3106adb299", + "zh:981eb203d9cfbc735bd792a7654319a5a9b8d68a485d0399b4f018557550f0ce", + "zh:a0e3b74af3976aa4f9e68a7c8bde620abda2d4f0d29859032f9d6d2df3e4c200", + "zh:b767b70e56dc47238ef9f541c29a16e97e7f19b63b303d551593b620d0c9f609", + "zh:edf48f3699cf8597d07d7ab8923e5353da4337dfa9462c07c289643309bf7494", + "zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597", + "zh:faf3b240bce18a4cf91359b0bdb017f9b2a8f4c38202c4c959c3149f69bf321e", + ] +} + +provider "registry.terraform.io/darkhonor/technitium" { + version = "1.1.0" + constraints = ">= 0.3.0" + hashes = [ + "h1:+PIjpgfSqFxA3893Jvd8Xs4xiuoiGr/4Lu4QXOZWW2g=", + "zh:0855bb0e2a5bd9ec68a6a7cf117980189b2f2030232ef7ae6815c97118eb0908", + "zh:1cbca86ce5f77e8aa32f863f2f40046a12b32f4d94860f7f0a03d24a6091ffd4", + "zh:268dc7849a9fee58efcfbe662fc4a0f915bb5a4e1f55746228926a02bcbde408", + "zh:6e878f8bdf8b468166774766879094d39626bd03995b0942ce156448c22988f9", + "zh:7006c9b909905f5fe8cee43610d4676ec555666ff26862b8821122e428327cd3", + "zh:97d3c059d8a44d7525232f1471b49f638ea071a85955d82630d1c7682a58dab2", + ] +} + +provider "registry.terraform.io/paultyng/unifi" { + version = "0.41.0" + constraints = "0.41.0" + hashes = [ + "h1:vE4C9E7NtYUC7otQrAvNNkDFBHqEMxkUlcqJhLWchLY=", + "zh:03ddd3aee05a08e1446f75a7b3f52810181d3307728cba08ce8fb67f109a9c00", + "zh:11b14b79ad02b0a55fd6116b10c0eb6fab432dd7d1f3527af0e2055adf292451", + "zh:18c0eb19889927f115a1e05d64f59b4e8d530ccdf1a8b574940a86be20973564", + "zh:2df9ca0c21830d2757758e574b19d0d4e54965ce80dbbfb3f124db1dac3d7e8f", + "zh:36274af3b7e8b08ba69c04a226c63e0dd2ec386c583288ebd7bc2a30e349ee8f", + "zh:413eb222ef30889bab33ccbfc46c9fb64307555da34eac4625d51e696ac72e1d", + "zh:4839814ff9f405a13397ffadd6f1052c770b88802280a4d8cde066f9a19718c7", + "zh:9547b7831852cc5b9c0fd13ab447d48539eae94582c8725ad255af36e31fb5d9", + "zh:a855c89b12326eb1c89bbf292a2bb1de3651794e3409d5012076ada89aabdc8a", + "zh:aef12a33b90fd77a9bf4e9d397966ccbfa4a037a648a1725074aff2db2d90fb0", + "zh:b3c72a6a02e29b4d21aa0d0831a272ca7cb82c3f8c2c3c7f09fcc2d2dcd78752", + "zh:c8354eaaab5f526e8e530b098544c7583a0f0b5b27d67500c7b3e9da56a3a7e5", + "zh:dc29f1e70f20ce86d3c6a66c7a817616f993a1cf9d941604dfd5222a06992c4c", + "zh:e772779333419f34d2c6da333c7f7d235a5a34f21ea47636b548e132aed74f3b", + ] +} diff --git a/terraform/talos/cluster/main.tf b/terraform/talos/cluster/main.tf new file mode 100644 index 0000000..aeb505a --- /dev/null +++ b/terraform/talos/cluster/main.tf @@ -0,0 +1,151 @@ +# ─── fastpass Talos Cluster ─────────────────────────────────────────────────── +# +# Provisions all infrastructure for the fastpass Kubernetes cluster: +# - Talos ISO download to Proxmox +# - 3 control plane VMs (space-mountain, big-thunder-mountain, splash-mountain) +# - 3 worker VMs (jungle-cruise, haunted-mansion, peter-pans-flight) +# - Unifi DHCP reservations (MAC → IP) for all 6 nodes +# - Technitium DNS A records for all 6 nodes + cluster VIP +# +# After apply: +# Run talhelper from city-hall to generate and apply machine configs. +# See talos/talhelper/ for cluster configuration. +# +# IP Allocation (10.1.71.64/26 — Container Orchestration): +# .65 — Kubernetes API VIP (Talos native VIP, not a VM) +# .66 — space-mountain (control plane, main-street-usa) +# .67 — big-thunder-mountain (control plane, tomorrowland) +# .68 — splash-mountain (control plane, fantasyland) +# .69 — jungle-cruise (worker, main-street-usa) +# .70 — haunted-mansion (worker, tomorrowland) +# .71 — peter-pans-flight (worker, fantasyland) +# .80-.99 — Cilium LB pool (managed by Cilium, not Terraform) + +locals { + # Node definitions — single source of truth for the entire cluster. + # VM ID convention: {third_octet}{fourth_octet:03d} + nodes = { + "space-mountain" = { + role = "controlplane" + target_node = "main-street-usa" + ip_address = "10.1.71.66" + vm_id = 71066 + cpu_cores = 4 + memory_mb = 8192 + disk_size = 50 + } + "big-thunder-mountain" = { + role = "controlplane" + target_node = "tomorrowland" + ip_address = "10.1.71.67" + vm_id = 71067 + cpu_cores = 4 + memory_mb = 8192 + disk_size = 50 + } + "splash-mountain" = { + role = "controlplane" + target_node = "fantasyland" + ip_address = "10.1.71.68" + vm_id = 71068 + cpu_cores = 4 + memory_mb = 8192 + disk_size = 50 + } + "jungle-cruise" = { + role = "worker" + target_node = "main-street-usa" + ip_address = "10.1.71.69" + vm_id = 71069 + cpu_cores = 8 + memory_mb = 16384 + disk_size = 100 + } + "haunted-mansion" = { + role = "worker" + target_node = "tomorrowland" + ip_address = "10.1.71.70" + vm_id = 71070 + cpu_cores = 8 + memory_mb = 16384 + disk_size = 100 + } + "peter-pans-flight" = { + role = "worker" + target_node = "fantasyland" + ip_address = "10.1.71.71" + vm_id = 71071 + cpu_cores = 8 + memory_mb = 16384 + disk_size = 100 + } + } +} + +# ─── Talos ISO ─────────────────────────────────────────────────────────────── +# ISO is pre-uploaded to the shared 'templates' datastore (Ceph-backed, +# visible to all nodes). File ID format: :iso/ + +locals { + talos_iso_file_id = "${var.iso_datastore}:iso/metal-amd64.iso" +} + +# ─── Cluster Nodes ─────────────────────────────────────────────────────────── +# Iterate over the node map and call the node module for each. + +module "nodes" { + source = "../modules/node" + for_each = local.nodes + + hostname = each.key + vm_id = each.value.vm_id + node_role = each.value.role + target_node = each.value.target_node + iso_file_id = local.talos_iso_file_id + cpu_cores = each.value.cpu_cores + memory_mb = each.value.memory_mb + disk_size = each.value.disk_size + datastore = var.datastore + bridge = var.bridge +} + +# ─── Unifi DHCP Reservations ───────────────────────────────────────────────── +# Create a static MAC→IP reservation for each node. +# Reservations are created before VMs start so the IP is guaranteed +# on first DHCP broadcast. + +resource "unifi_user" "nodes" { + for_each = local.nodes + + name = each.key + mac = lower(module.nodes[each.key].mac_address) + fixed_ip = each.value.ip_address + + note = "Managed by mk-labs Terraform — fastpass cluster ${each.value.role}" + + depends_on = [module.nodes] +} + +# ─── DNS A Records ─────────────────────────────────────────────────────────── +# Create A records for all 6 nodes in the internal zone. +# The cluster VIP (.65) is managed by Talos natively — no VM, but still +# needs a DNS entry for kubectl and talhelper to resolve the endpoint. + +resource "technitium_record" "nodes" { + for_each = local.nodes + + zone = var.dns_zone + name = "${each.key}.${var.dns_zone}" + type = "A" + ttl = 300 + value = each.value.ip_address +} + +resource "technitium_record" "vip" { + zone = var.dns_zone + name = "${var.cluster_name}-api.${var.dns_zone}" + type = "A" + ttl = 300 + value = "10.1.71.65" +} + diff --git a/terraform/talos/cluster/outputs.tf b/terraform/talos/cluster/outputs.tf new file mode 100644 index 0000000..135bb70 --- /dev/null +++ b/terraform/talos/cluster/outputs.tf @@ -0,0 +1,43 @@ +# ─── Post-Apply Reminders ──────────────────────────────────────────────────── + +output "ha_rule_instructions" { + description = "Manual steps required after the Talos cluster is fully installed and the ISO is no longer needed." + value = <<-EOT + + ── Post-Cluster-Install: HA Setup ────────────────────────────────────────── + + Once Talos is fully installed and running (ISO no longer needed for boot), + complete the following steps: + + 1. Register each VM with the Proxmox HA manager: + ha-manager add vm:71066 --state started + ha-manager add vm:71067 --state started + ha-manager add vm:71068 --state started + ha-manager add vm:71069 --state started + ha-manager add vm:71070 --state started + ha-manager add vm:71071 --state started + + 2. Add VMs to the existing node-affinity rules (check current resources + first with pvesh get /cluster/ha/rules/): + + prefer_main-street-usa (space-mountain:71066, jungle-cruise:71069): + pvesh set /cluster/ha/rules/prefer_main-street-usa \ + --type node-affinity \ + --resources ",vm:71066,vm:71069" + + prefer_tomorrowland (big-thunder-mountain:71067, haunted-mansion:71070): + pvesh set /cluster/ha/rules/prefer_tomorrowland \ + --type node-affinity \ + --resources ",vm:71067,vm:71070" + + prefer_fantasyland (splash-mountain:71068, peter-pans-flight:71071): + pvesh set /cluster/ha/rules/prefer_fantasyland \ + --type node-affinity \ + --resources ",vm:71068,vm:71071" + + 3. Eject the Talos ISO from each VM in the Proxmox UI or via: + qm set --ide2 none + + ──────────────────────────────────────────────────────────────────────────── + EOT +} diff --git a/terraform/talos/cluster/providers.tf b/terraform/talos/cluster/providers.tf new file mode 100644 index 0000000..dc9e6e6 --- /dev/null +++ b/terraform/talos/cluster/providers.tf @@ -0,0 +1,48 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + proxmox = { + source = "bpg/proxmox" + version = ">= 0.74.0" + } + + unifi = { + source = "paultyng/unifi" + version = "0.41.0" + } + + technitium = { + source = "darkhonor/technitium" + version = ">= 0.3.0" + } + } +} + +# ─── Proxmox Provider ──────────────────────────────────────────────────────── + +provider "proxmox" { + endpoint = var.proxmox_api_url + api_token = var.proxmox_api_token + insecure = true + + ssh { + agent = true + } +} + +# ─── Unifi Provider ────────────────────────────────────────────────────────── + +provider "unifi" { + username = var.unifi_username + password = var.unifi_password + api_url = var.unifi_api_url + allow_insecure = true +} + +# ─── Technitium DNS Provider ───────────────────────────────────────────────── + +provider "technitium" { + server_url = var.technitium_url + api_token = var.technitium_api_token +} diff --git a/terraform/talos/cluster/tfvars/fastpass.tfvars b/terraform/talos/cluster/tfvars/fastpass.tfvars new file mode 100644 index 0000000..d4ef6d3 --- /dev/null +++ b/terraform/talos/cluster/tfvars/fastpass.tfvars @@ -0,0 +1,32 @@ +# ─── fastpass Cluster — Variable Values ────────────────────────────────────── +# Copy to fastpass.tfvars and populate. +# Sensitive values (passwords, tokens) are passed via CLI or environment: +# +# terraform apply \ +# -var-file="tfvars/fastpass.tfvars" \ +# -state="states/fastpass.tfstate" \ +# -var="proxmox_api_token=terraform@pve!terraform-token=" \ +# -var="unifi_password=" \ +# -var="technitium_password=" + +# ─── Proxmox ───────────────────────────────────────────────────────────────── +proxmox_api_url = "https://fantasyland.local.mk-labs.cloud:8006" + +# ─── Unifi ─────────────────────────────────────────────────────────────────── +unifi_api_url = "https://10.1.71.1:443" +unifi_username = "terraform" + +# ─── Technitium DNS ─────────────────────────────────────────────────────────── +technitium_url = "http://monorail.local.mk-labs.cloud:5380" + +# ─── Cluster ───────────────────────────────────────────────────────────────── +cluster_name = "fastpass" +talos_version = "v1.13.2" +dns_zone = "local.mk-labs.cloud" + +# ─── ISO ───────────────────────────────────────────────────────────────────── +iso_datastore = "templates" + +# ─── VM Defaults ───────────────────────────────────────────────────────────── +datastore = "liberty-tree" +bridge = "vmbr0" diff --git a/terraform/talos/cluster/tfvars/fastpass.tfvars.example b/terraform/talos/cluster/tfvars/fastpass.tfvars.example new file mode 100644 index 0000000..9f38c36 --- /dev/null +++ b/terraform/talos/cluster/tfvars/fastpass.tfvars.example @@ -0,0 +1,40 @@ +# ─── fastpass Cluster — Variable Values ────────────────────────────────────── +# Copy to fastpass.tfvars and populate. +# Sensitive values (passwords, tokens) are passed via CLI or environment: +# +# terraform apply \ +# -var-file="tfvars/fastpass.tfvars" \ +# -state="states/fastpass.tfstate" \ +# -var="proxmox_api_token=terraform@pve!terraform-token=" \ +# -var="unifi_password=" \ +# -var="technitium_password=" + +# ─── Proxmox ───────────────────────────────────────────────────────────────── +proxmox_api_url = "https://fantasyland.local.mk-labs.cloud:8006" +proxmox_api_token = "terraform@pve!terraform-token=REPLACE_ME" + +# ─── Unifi ─────────────────────────────────────────────────────────────────── +unifi_api_url = "https://10.1.71.1" +unifi_username = "terraform" +unifi_password = "REPLACE_ME" +# Retrieve with: curl -sk -X GET https://10.1.71.1/api/network | jq '.data[] | select(.name=="Server Trusted") | ._id' +unifi_network_id = "REPLACE_ME" + +# ─── Technitium DNS ─────────────────────────────────────────────────────────── +technitium_url = "http://10.1.71.32:5380" +technitium_username = "admin" +technitium_password = "REPLACE_ME" + +# ─── Cluster ───────────────────────────────────────────────────────────────── +cluster_name = "fastpass" +talos_version = "v1.9.5" +dns_zone = "local.mk-labs.cloud" + +# ─── ISO ───────────────────────────────────────────────────────────────────── +iso_node = "fantasyland" +iso_datastore = "local" + +# ─── VM Defaults ───────────────────────────────────────────────────────────── +datastore = "liberty-tree" +bridge = "vmbr0" +vlan_id = 71 diff --git a/terraform/talos/cluster/variables.tf b/terraform/talos/cluster/variables.tf new file mode 100644 index 0000000..ac68545 --- /dev/null +++ b/terraform/talos/cluster/variables.tf @@ -0,0 +1,79 @@ +# ─── Proxmox Authentication ────────────────────────────────────────────────── + +variable "proxmox_api_url" { + type = string + description = "Proxmox API endpoint URL (e.g., https://fantasyland.local.mk-labs.cloud:8006)." +} + +variable "proxmox_api_token" { + type = string + description = "Proxmox API token. Format: user@realm!token-name=token-secret." + sensitive = true +} + +# ─── Unifi Authentication ──────────────────────────────────────────────────── + +variable "unifi_api_url" { + type = string + description = "Unifi controller API URL (e.g., https://10.1.71.1:443)." +} + +variable "unifi_username" { + type = string + description = "Unifi controller username." +} + +variable "unifi_password" { + type = string + description = "Unifi controller password." + sensitive = true +} + +# ─── Technitium DNS Authentication ─────────────────────────────────────────── + +variable "technitium_url" { + type = string + description = "Technitium DNS server URL (e.g., http://10.1.71.32:5380)." +} + +variable "technitium_api_token" { + type = string + description = "Technitium API token. Create under Administration → API Tokens." + sensitive = true +} + +# ─── Cluster Configuration ─────────────────────────────────────────────────── + +variable "cluster_name" { + type = string + description = "Talos cluster name." + default = "fastpass" +} + +variable "dns_zone" { + type = string + description = "Internal DNS zone for A record creation." + default = "local.mk-labs.cloud" +} + +# ─── ISO Storage ───────────────────────────────────────────────────────────── + +variable "iso_datastore" { + type = string + description = "Proxmox datastore for ISO storage. Must be visible to all nodes — use a shared/Ceph-backed datastore, not node-local storage." + default = "templates" +} + +# ─── Shared VM Defaults ────────────────────────────────────────────────────── + +variable "datastore" { + type = string + description = "Proxmox storage pool for VM disks." + default = "liberty-tree" +} + +variable "bridge" { + type = string + description = "Proxmox network bridge." + default = "vmbr0" +} diff --git a/terraform/talos/modules/node/main.tf b/terraform/talos/modules/node/main.tf new file mode 100644 index 0000000..6aaefe1 --- /dev/null +++ b/terraform/talos/modules/node/main.tf @@ -0,0 +1,94 @@ +# ─── Talos Node Resource ───────────────────────────────────────────────────── +# Creates a Proxmox VM booting from the Talos ISO. +# No cloud-init, no template clone — Talos is configured via the Talos API +# after boot using talhelper-generated machine configs from city-hall. +# +# Boot sequence: +# 1. Terraform creates VM (stopped) + DHCP reservation +# 2. Unifi assigns reserved IP when VM starts +# 3. talhelper applies machine config via Talos API (port 50000) +# 4. Cluster bootstraps; ISO is no longer needed after first boot +# (Talos installs itself to disk on first config apply) + +resource "proxmox_virtual_environment_vm" "node" { + name = var.hostname + vm_id = var.vm_id + node_name = var.target_node + + description = "Talos ${var.node_role} node — fastpass cluster. Managed by mk-labs Terraform." + tags = ["managed", "talos", "fastpass", var.node_role] + bios = "ovmf" + + # ─── EFI Disk ───────────────────────────────────────────────────────────── + # Required for OVMF/UEFI boot. Stores EFI variables. + efi_disk { + datastore_id = var.datastore + file_format = "raw" + type = "4m" + } + + # ─── Boot: Talos ISO ────────────────────────────────────────────────────── + # Talos boots from ISO, applies machine config via API, then installs + # itself to the disk. After first boot the ISO is not needed. + cdrom { + file_id = var.iso_file_id + interface = "ide2" + } + + boot_order = ["scsi0", "ide2"] + + # ─── Disk ───────────────────────────────────────────────────────────────── + # Single disk — Talos manages its own partition layout. + # No separate data disk; persistent app storage comes from Pure Storage CSI. + disk { + datastore_id = var.datastore + size = var.disk_size + interface = "scsi0" + file_format = "raw" + ssd = true + discard = "on" + } + + # ─── CPU ────────────────────────────────────────────────────────────────── + cpu { + cores = var.cpu_cores + type = "x86-64-v2-AES" + } + + # ─── Memory ─────────────────────────────────────────────────────────────── + memory { + dedicated = var.memory_mb + } + + # ─── Network ────────────────────────────────────────────────────────────── + # Single NIC on VLAN 71 (Server Trusted). + # MAC is deterministic via Terraform so Unifi DHCP reservation matches. + network_device { + bridge = var.bridge + model = "virtio" + } + + # ─── QEMU Guest Agent ───────────────────────────────────────────────────── + # Requires the qemu-guest-agent Talos system extension (set in talhelper). + agent { + enabled = true + } + + # ─── Start Behavior ─────────────────────────────────────────────────────── + # VM starts immediately — Talos needs to boot to accept machine configs. + # Unifi DHCP reservation is created before this resource so the IP is + # already reserved when the NIC first broadcasts a DHCP request. + started = true + + # ─── Lifecycle ──────────────────────────────────────────────────────────── + lifecycle { + ignore_changes = [ + description, + tags, + # cdrom can be ejected manually after first boot without Terraform drift + cdrom, + # node_name changes after HA migrates VMs to their intended hosts + node_name, + ] + } +} diff --git a/terraform/talos/modules/node/outputs.tf b/terraform/talos/modules/node/outputs.tf new file mode 100644 index 0000000..5145016 --- /dev/null +++ b/terraform/talos/modules/node/outputs.tf @@ -0,0 +1,18 @@ +# ─── Node Outputs ──────────────────────────────────────────────────────────── +# Consumed by the cluster root module to wire up DHCP reservations and +# DNS records without hardcoding MAC addresses. + +output "vm_id" { + description = "Proxmox VM ID." + value = proxmox_virtual_environment_vm.node.vm_id +} + +output "mac_address" { + description = "MAC address of the primary network interface. Used to create the Unifi DHCP reservation." + value = proxmox_virtual_environment_vm.node.network_device[0].mac_address +} + +output "hostname" { + description = "Node hostname. Passed through for convenience when building DNS records." + value = proxmox_virtual_environment_vm.node.name +} diff --git a/terraform/talos/modules/node/providers.tf b/terraform/talos/modules/node/providers.tf new file mode 100644 index 0000000..bc365f6 --- /dev/null +++ b/terraform/talos/modules/node/providers.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = ">= 0.74.0" + } + } +} \ No newline at end of file diff --git a/terraform/talos/modules/node/variables.tf b/terraform/talos/modules/node/variables.tf new file mode 100644 index 0000000..c3e6f4d --- /dev/null +++ b/terraform/talos/modules/node/variables.tf @@ -0,0 +1,71 @@ +# ─── Provider Authentication ───────────────────────────────────────────────── +# Passed through from the cluster root module — not declared here since +# provider config lives in the root module's providers.tf. + +# ─── Node Identity ─────────────────────────────────────────────────────────── + +variable "hostname" { + type = string + description = "Node hostname (e.g., space-mountain). Used as the Proxmox VM name." +} + +variable "vm_id" { + type = number + description = "Proxmox VM ID. Convention: {third_octet}{fourth_octet:03d} (e.g., 10.1.71.66 → 71066)." +} + +variable "node_role" { + type = string + description = "Role of the node in the cluster. Used for tagging." + + validation { + condition = contains(["controlplane", "worker"], var.node_role) + error_message = "node_role must be 'controlplane' or 'worker'." + } +} + +# ─── Proxmox Placement ─────────────────────────────────────────────────────── + +variable "target_node" { + type = string + description = "Proxmox node to place this VM on (e.g., main-street-usa, tomorrowland, fantasyland)." +} + +variable "datastore" { + type = string + description = "Proxmox storage pool for the VM disk." + default = "liberty-tree" +} + +# ─── Boot Image ────────────────────────────────────────────────────────────── + +variable "iso_file_id" { + type = string + description = "Proxmox file ID for the Talos ISO (e.g., local:iso/talos-v1.9.5-metal-amd64.iso). Output from proxmox_virtual_environment_download_file." +} + +# ─── Hardware ──────────────────────────────────────────────────────────────── + +variable "cpu_cores" { + type = number + description = "Number of vCPU cores." +} + +variable "memory_mb" { + type = number + description = "RAM in megabytes." +} + +variable "disk_size" { + type = number + description = "OS disk size in gigabytes. Talos manages its own partition layout." +} + +# ─── Network ───────────────────────────────────────────────────────────────── + +variable "bridge" { + type = string + description = "Proxmox network bridge." + default = "vmbr0" +} +