Initial implementation: host lifecycle + PXE + admin dashboard

Go service for Proxmox homelab cluster provisioning. Handles PXE boot,
Proxmox autoinstall (answer file generation), cluster join via SSH,
and Infrastructure API registration.

- Host state machine (registered → pxe_ready → installing → ready)
- dnsmasq supervisor with MAC-based allowlist
- iPXE script and Proxmox answer file generation
- First-boot phone-home → cluster join → infra registration
- Operation locking with expiry (409 on conflict)
- SSE event hub for real-time dashboard updates
- Admin dashboard (host grid, detail, registration form)
- Config-driven server types with hot-reload
- Docker deployment (multi-stage fat image)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 20:55:14 -04:00
commit bda568b25c
39 changed files with 3067 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
package pxe
import (
"fmt"
"strings"
"provisioning/internal/config"
"provisioning/internal/model"
)
func GenerateAnswerFile(host *model.Host, serverType model.ServerType, cfg *config.Config) string {
var b strings.Builder
b.WriteString("[global]\n")
b.WriteString(`keyboard = "en-us"` + "\n")
b.WriteString(`country = "us"` + "\n")
b.WriteString(fmt.Sprintf("fqdn = \"%s.thewrightserver.net\"\n", host.Hostname))
b.WriteString(`mailto = "admin@thewrightserver.net"` + "\n")
b.WriteString(`timezone = "America/Indiana/Indianapolis"` + "\n")
b.WriteString(fmt.Sprintf("root-password-hashed = \"%s\"\n", cfg.Credentials.RootPasswordHash))
b.WriteString(fmt.Sprintf("root-ssh-keys = [\"%s\"]\n", cfg.Credentials.SSHPublicKey))
b.WriteString("\n")
b.WriteString("[network]\n")
b.WriteString(`source = "from-dhcp"` + "\n")
b.WriteString("\n")
b.WriteString("[disk-setup]\n")
b.WriteString(`filesystem = "zfs"` + "\n")
b.WriteString(`zfs.raid = "raid0"` + "\n")
b.WriteString(fmt.Sprintf("disk-list = [\"%s\"]\n", serverType.BootDisk))
b.WriteString("\n")
b.WriteString("[post-installation-webhook]\n")
b.WriteString(fmt.Sprintf("url = \"%s/api/hosts/%d/installed\"\n", cfg.Server.PublicURL, host.ID))
b.WriteString("\n")
b.WriteString("[first-boot]\n")
b.WriteString(`source = "from-url"` + "\n")
b.WriteString(fmt.Sprintf("url = \"%s/api/hosts/%d/first-boot-script\"\n", cfg.Server.PublicURL, host.ID))
b.WriteString(`ordering = "after-network"` + "\n")
return b.String()
}