Go server (API, embedded web UI, CLI) backed by PostgreSQL with AES-GCM sealed BMC secrets; Debian live commissioning agent and live-build recipe; Proxmox lab with pve-vbmc virtual BMCs; Gitea workflows for CI, releases and deployment; project website. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
141 lines
6.4 KiB
Markdown
141 lines
6.4 KiB
Markdown
# Rollcall
|
|
|
|
Rollcall is a small, self-contained commissioning service for bare-metal servers,
|
|
in the spirit of MAAS enlistment and commissioning, without the rest of MAAS.
|
|
|
|
A server that PXE-boots for the first time gets an ephemeral Debian live image.
|
|
The image takes a full hardware inventory (DMI, DIMMs, CPUs, disks, NVMe, NICs,
|
|
InfiniBand HCAs, GPUs, PCI, LLDP neighbours, TPM), creates an administrator
|
|
account on the server's BMC through in-band IPMI, reports everything to the
|
|
Rollcall server and powers the machine off. From then on the server is known:
|
|
it shows up in the web UI and the CLI, its BMC can be driven through the API,
|
|
and it will not boot the commissioning image again unless you ask for it.
|
|
|
|
Everything server-side is **one static Go binary**: HTTP API, embedded web UI,
|
|
CLI, PostgreSQL schema management and the PXE bookkeeping.
|
|
|
|
## How it works
|
|
|
|
```
|
|
unknown server ──PXE──▶ dnsmasq (DHCP+TFTP) ──▶ pxelinux default menu
|
|
│ 8 s timeout
|
|
▼
|
|
Debian live image over HTTP (kernel+initrd via TFTP,
|
|
squashfs via HTTP, runs entirely from RAM)
|
|
│
|
|
rollcall-agent: inventory + BMC user + POST /api/v1/commission
|
|
│
|
|
rollcall server: PostgreSQL ◀─┘──▶ writes pxelinux.cfg/01-<mac>
|
|
(next boot: "commissioned → power off")
|
|
```
|
|
|
|
* **Enlist**: a machine is identified by its DMI system UUID (fallback: serial,
|
|
then boot MAC). First contact creates it in state `new`.
|
|
* **Commission**: the inventory upload moves it to `ready`. The BMC password
|
|
the agent generated is sealed with AES-256-GCM (key in
|
|
`/etc/rollcall/master.key`) before it touches the database. It never leaves
|
|
the server again: power and boot-device operations run `ipmitool` server-side.
|
|
* **Re-commission**: removes the per-MAC PXE override; optionally sets
|
|
bootdev=pxe and power-cycles the machine through its BMC.
|
|
|
|
## Repository layout
|
|
|
|
| Path | What |
|
|
|---|---|
|
|
| `cmd/rollcall/` | Go server + CLI + embedded web UI (`web/`) |
|
|
| `agent/` | `rollcall-agent.py` (runs inside the live image), its systemd unit, `build-image.sh` (live-build recipe) |
|
|
| `deploy/` | `install.sh`, `rollcall.service`, `config.example.json`, `pxelinux-default` |
|
|
| `lab/` | Proxmox test lab: `pve-vbmc` (virtual IPMI BMC for VMs backed by the Proxmox API), cloud-init for the PXE server VM |
|
|
| `site/` | Project website (static) |
|
|
| `.gitea/workflows/` | CI (fmt/vet/build/tests), release on tags, deploy on push to `main` |
|
|
|
|
## Server
|
|
|
|
```sh
|
|
# build
|
|
cd cmd/rollcall && CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o rollcall .
|
|
|
|
# PostgreSQL role + database
|
|
sudo -u postgres psql -c "CREATE ROLE rollcall LOGIN PASSWORD '...';" -c "CREATE DATABASE rollcall OWNER rollcall;"
|
|
|
|
# config (see deploy/config.example.json), tokens from `rollcall keygen`
|
|
sudo install -d -m 0750 /etc/rollcall && sudo install -m 0640 config.json /etc/rollcall/config.json
|
|
|
|
# install binary, systemd unit, pxelinux menu, tftp symlinks
|
|
cd deploy && sudo ./install.sh
|
|
```
|
|
|
|
`config.json` keys: `listen`, `image_dir` (served under `/rollcall/`), `tftp_root`,
|
|
`database_url`, `master_key_file`, `admin_token` (web UI + management API),
|
|
`agent_token` (what the live image presents), `commission_append` (kernel
|
|
command line baked into generated PXE files), `bmc_hints` (out-of-band BMC
|
|
mapping for machines without in-band IPMI, e.g. VMs).
|
|
|
|
### CLI (same binary)
|
|
|
|
```sh
|
|
rollcall list
|
|
rollcall show m-2ef1dab828c8 [-json]
|
|
rollcall events m-2ef1dab828c8
|
|
rollcall power m-2ef1dab828c8 status|on|off|soft|cycle|reset
|
|
rollcall bootdev m-2ef1dab828c8 pxe|disk|cdrom|bios|none
|
|
rollcall recommission m-2ef1dab828c8 [-now]
|
|
rollcall delete m-2ef1dab828c8
|
|
```
|
|
|
|
Reads `ROLLCALL_URL` / `ROLLCALL_TOKEN`, or the config file when readable.
|
|
|
|
### API
|
|
|
|
| Method | Path | Auth | Purpose |
|
|
|---|---|---|---|
|
|
| POST | `/api/v1/commission` | agent token | inventory upload from the live image |
|
|
| GET | `/api/v1/bmc-hint?macs=` | agent token | out-of-band BMC hint for VMs |
|
|
| GET | `/api/v1/machines` | admin | list (summaries) |
|
|
| GET | `/api/v1/machines/{id}` | admin | full record: inventory, BMC (no secret), events |
|
|
| POST | `/api/v1/machines/{id}/power` | admin | `{"action":"on"}` etc., runs ipmitool server-side |
|
|
| POST | `/api/v1/machines/{id}/bootdev` | admin | `{"device":"pxe"}` |
|
|
| POST | `/api/v1/machines/{id}/recommission[?now=1]` | admin | boot the image again on next PXE (`now=1`: via BMC immediately) |
|
|
| DELETE | `/api/v1/machines/{id}` | admin | forget the machine |
|
|
| GET | `/rollcall/*` | none | image files (kernel, initrd, squashfs) |
|
|
| GET | `/` | token in browser | web UI |
|
|
|
|
Admin auth is `Authorization: Bearer <admin_token>`; the agent sends
|
|
`X-Rollcall-Token`.
|
|
|
|
## Commissioning image
|
|
|
|
`agent/build-image.sh` builds a Debian 13 live image with live-build (about
|
|
8 minutes, ~780 MB squashfs). It contains the full Debian kernel with non-free
|
|
firmware, `ipmitool`, `freeipmi`, `rdma-core`, `infiniband-diags`, `mstflint`,
|
|
`nvme-cli`, `smartmontools`, `lldpd`, `lshw` and an SSH server (root, key only)
|
|
for debugging. Output goes to `image_dir`.
|
|
|
|
Kernel parameters understood by the agent: `rollcall.server=URL` (required),
|
|
`rollcall.token=…`, `rollcall.bmcuser=NAME` (default `commissioner`),
|
|
`rollcall.nopoweroff` (stay up after reporting).
|
|
|
|
### BMC account
|
|
|
|
In-band (`/dev/ipmi0`): find the LAN channel, pick the existing `commissioner`
|
|
slot or the first empty user ID ≥ 3, set name/password, ADMINISTRATOR privilege,
|
|
channel access, enable, SOL payload. The password is random (16 chars) and is
|
|
reported once to the server. If no in-band IPMI exists the agent asks the server
|
|
for a hint (`bmc_hints`) and verifies it out-of-band.
|
|
|
|
Vendor quirks (iDRAC/iLO password policies, missing empty slots) are reported in
|
|
`bmc.errors` and visible in the UI.
|
|
|
|
## Lab
|
|
|
|
`lab/` documents a Proxmox SDN test setup: an isolated vnet, a PXE server VM
|
|
running dnsmasq + Rollcall, two diskless VMs, and `pve-vbmc`, a tiny
|
|
IPMI-over-LAN BMC emulator that drives VMs through the Proxmox API so that
|
|
`ipmitool power on` and `chassis bootdev pxe` work against virtual machines.
|
|
|
|
## Status
|
|
|
|
Working end to end in the lab (virtual machines, out-of-band BMC hints). The
|
|
in-band BMC path is implemented but has not yet been exercised on real
|
|
hardware.
|