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

  1. Operator registers a host (MAC address + server type)
  2. Operator triggers "rebuild with Proxmox"
  3. Host PXE boots → dnsmasq responds → iPXE chain-loads the Proxmox installer
  4. Installer fetches a per-host answer file (TOML) from Provisioning
  5. Proxmox installs unattended → post-install webhook fires
  6. Host reboots → first-boot script phones home with IP + hardware ID
  7. Provisioning SSHes into the new host → pvecm add joins the cluster
  8. 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-DHCP
  • proxmox.existing_node — IP of any current cluster member
  • proxmox.join_fingerprint — from pvecm status on an existing node
  • credentials.ssh_public_key — public key injected into new hosts
  • credentials.root_password_hashmkpasswd -m sha-512
  • infrastructure.base_url — URL of the Infrastructure service
  • infrastructure.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 test and go vet on every push to main
  • Builds Docker image and pushes to gitea.thewrightserver.net/josh/provisioning:latest
S
Description
No description provided
Readme 258 KiB
Languages
Go 87.5%
CSS 7.7%
JavaScript 3.5%
Shell 0.7%
Dockerfile 0.4%
Other 0.2%