b23ef64ee1
Generate a fresh ed25519 key pair at rebuild time, inject the public key into the Proxmox answer file, use the private key for cluster join over SSH, then remove the key from both the remote host and the database. This eliminates the need to manage static SSH keys in config/secrets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.7 KiB
3.7 KiB
Provisioning
Central control plane for a Proxmox homelab cluster. Handles PXE booting bare metal, unattended Proxmox installation, cluster join, and host lifecycle management.
What it does
- Operator registers a host (MAC address + server type)
- Operator triggers "rebuild with Proxmox"
- Host PXE boots → dnsmasq responds → iPXE chain-loads the Proxmox installer
- Installer fetches a per-host answer file (TOML) from Provisioning
- Proxmox installs unattended → post-install webhook fires
- Host reboots → first-boot script phones home with IP + hardware ID
- Provisioning SSHes into the new host →
pvecm addjoins the cluster - Host registered in Infrastructure → marked ready
Admin dashboard shows real-time progress via SSE.
Deploy
Prerequisites
- Docker + Docker Compose on the target host
- Host must be on the same network as the bare-metal nodes (for PXE/DHCP)
- SSH key pair for root access to new Proxmox nodes
- Registry access to
gitea.thewrightserver.net
Setup
mkdir -p /opt/provisioning/keys
cd /opt/provisioning
# Log in to the container registry
docker login gitea.thewrightserver.net
# Pull the compose file
curl -sO https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/docker-compose.yml
# Pull example configs
curl -s https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/deploy/provisioning.example.yaml -o provisioning.yaml
curl -s https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/deploy/server-types.example.yaml -o server-types.yaml
# Copy your SSH key pair
cp /path/to/id_ed25519 ./keys/
Configure
Edit provisioning.yaml:
server.public_url— LAN-reachable URL (e.g.http://192.168.1.100:8080)pxe.interface— NIC name on the host (e.g.eth0,enp2s0)pxe.subnet— LAN CIDR for proxy-DHCPproxmox.existing_node— IP of any current cluster memberproxmox.join_fingerprint— frompvecm statuson an existing nodecredentials.root_password_hash—mkpasswd -m sha-512infrastructure.base_url— URL of the Infrastructure serviceinfrastructure.server_type_map— maps local type keys to Infrastructure IDs
Edit server-types.yaml with your actual hardware types.
Run
docker compose up -d
Dashboard at http://<host>:8080.
Update
docker compose pull
docker compose up -d
Development
# Run tests
go test ./...
# Run locally (PXE disabled)
cp deploy/provisioning.example.yaml provisioning.yaml
cp deploy/server-types.example.yaml server-types.yaml
# Edit provisioning.yaml: set pxe.enabled=false, infrastructure.base_url=""
go run ./cmd/provisioning -config provisioning.yaml
# Build binary
make build
# Build Docker image locally
make docker
Architecture
cmd/provisioning/ Entry point, wiring, shutdown
internal/
config/ YAML config + hot-reloaded server types
db/ SQLite (WAL, embedded migrations)
model/ Domain types
store/ Hand-written SQL (hosts, operations, locks, images)
statemachine/ Table-driven host state machine
events/ SSE fan-out hub
pxe/ dnsmasq supervisor, iPXE scripts, answer files
orchestrator/ Lifecycle driver (state transitions, cluster join)
infra/ Infrastructure API client
api/ HTTP handlers (JSON API + dashboard)
httpserver/ chi router assembly
web/ Embedded static assets (CSS, JS)
CI/CD
Gitea Actions workflow (.gitea/workflows/build.yml):
- Runs
go testandgo veton every push to main - Builds Docker image and pushes to
gitea.thewrightserver.net/josh/provisioning:latest