aec31b9f8babe62d316dd7f51becdd61d37c2ffd
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.ssh_public_key— public key injected into new hostscredentials.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
Description
Languages
Go
87.5%
CSS
7.7%
JavaScript
3.5%
Shell
0.7%
Dockerfile
0.4%
Other
0.2%