76b9f64141
- Remove /etc/provisioning/keys mount (ephemeral keys are in-memory now) - Remove /etc/provisioning VOLUME from Dockerfile - Add deploy/install.sh that creates config files before docker compose up, preventing Docker from creating directories in place of missing bind mounts - Update README with install script usage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.8 KiB
5.8 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"
- Provisioning generates an ephemeral SSH key pair for this rebuild
- Host PXE boots → dnsmasq responds → iPXE chain-loads the Proxmox installer
- Installer fetches a per-host answer file (TOML) with the ephemeral public key
- Proxmox installs unattended → post-install webhook fires
- Host reboots → first-boot script phones home with IP + hardware ID
- Provisioning SSHes in using the ephemeral key →
pvecm addjoins the cluster - Ephemeral key removed from both the host and database
- Host registered in Infrastructure → marked ready
Admin dashboard shows real-time progress via SSE.
Host States
registered → pxe_ready → pxe_booted → installing → installed → first_boot → joining → ready
↓
failed
Deploy
Prerequisites
- Docker + Docker Compose on the target host
- Host must be on the same network as the bare-metal nodes (for PXE/DHCP)
- Registry access to
gitea.thewrightserver.net
No static SSH keys required — Provisioning generates ephemeral keys per rebuild automatically.
Setup
# Log in to the container registry
docker login gitea.thewrightserver.net
# Run the install script (creates /opt/provisioning with config templates)
curl -sf https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/deploy/install.sh | bash
Or manually:
mkdir -p /opt/provisioning && cd /opt/provisioning
curl -sfO https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/docker-compose.yml
curl -sf https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/deploy/provisioning.example.yaml -o provisioning.yaml
curl -sf https://gitea.thewrightserver.net/josh/Provisioning/raw/branch/main/deploy/server-types.example.yaml -o server-types.yaml
Configure
Edit provisioning.yaml:
| Key | Description |
|---|---|
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.root_password_hash |
Generate with mkpasswd -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:
server_types:
minisforum-ms-01:
display_name: "Minisforum MS-01"
boot_disk: "/dev/nvme0n1"
management_nic: "enp2s0"
gpu: false
hostname_prefix: "pve-ms"
minisforum-um790:
display_name: "Minisforum UM790 Pro"
boot_disk: "/dev/nvme0n1"
management_nic: "enp1s0"
gpu: true
hostname_prefix: "pve-um"
Run
docker compose up -d
Dashboard at http://<host>:8080.
Update
docker compose pull
docker compose up -d
API
Host Management
| Method | Path | Description |
|---|---|---|
| GET | /api/hosts |
List all hosts |
| GET | /api/hosts/{id} |
Get host details |
| POST | /api/hosts |
Register host (hostname, mac, server_type) |
| DELETE | /api/hosts/{id} |
Remove host |
| POST | /api/hosts/{id}/rebuild |
Start rebuild operation |
Boot Flow (called by PXE-booting hosts)
| Method | Path | Description |
|---|---|---|
| GET | /ipxe/{mac} |
iPXE boot script |
| POST | /api/boot/answer |
Proxmox answer file (TOML) |
| POST | /api/hosts/{id}/installed |
Post-install webhook |
| GET | /api/hosts/{id}/first-boot-script |
First-boot shell script |
| POST | /api/hosts/{id}/phone-home |
First-boot reports IP + hardware ID |
Dashboard
| Method | Path | Description |
|---|---|---|
| GET | / |
Host grid with live state tiles |
| GET | /hosts/{id} |
Host detail + operation history |
| GET | /events |
SSE stream |
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 (fsnotify)
db/ SQLite (WAL mode, embedded migrations)
model/ Domain types (Host, Operation, Image, ServerType)
store/ SQL stores (hosts, operations, locks, images)
statemachine/ Table-driven host state machine
events/ SSE fan-out hub
pxe/ dnsmasq supervisor, iPXE scripts, answer files, first-boot
orchestrator/ Lifecycle driver (ephemeral keys, cluster join, infra registration)
infra/ Infrastructure API client
api/ HTTP handlers (JSON API + HTML 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