Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 runipmitoolserver-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
# 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)
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.
Stateless nodes (Warewulf-style)
For HPC compute nodes Rollcall can boot a root image straight into RAM and dress it with per-node files, instead of installing an OS on disk.
- Images live in
images_dir/<name>/{vmlinuz,initrd.img,filesystem.squashfs}and are built withimages/build-rootfs.sh <name>fromimages/<name>/{packages.list,hook.chroot,includes/}(live-build, same toolchain as the commissioning image).images/compute-debian13is a Debian 13 compute node: slurmd, munge, rdma-core, NFS client, firmware. - Profiles bind an image to overlays, extra kernel parameters, a node name
format (
node%03d) and variables:rollcall profile add compute -image compute-debian13 -overlays compute. - Assigning a commissioned machine to a profile allocates a node name and
IPs from
node_pools, writes a per-MAC PXE entry that boots the image withhostname=,rollcall.server=and a per-node token, and exports static DHCP leases plus host names for dnsmasq (dnsmasq_dir, re-read on HUP viarollcall-dnsmasq.path).rollcall assign <id> compute -bootalso power-cycles the machine through its BMC. - Overlays are directory trees under
overlays_dir/<overlay>/{system,runtime}; files ending in.tmplare Go templates rendered with the node, all nodes of the cluster, the profile andcluster_vars. Thecomputeoverlay generates/etc/hosts,slurm.conf(node lines from the commissioning inventory: sockets, cores, threads, memory, GPUs),gres.conf, a persistent SSH host key per node (sealed in the database), rootauthorized_keysand the munge key. Inside the imagerollcall-overlay.serviceapplies the system overlay before munge/slurmd/ssh start; a timer refreshes the runtime overlay every five minutes, which doubles as the node heartbeat (bootedstate, last seen). - The controller renders the same files:
rollcall render compute etc/slurm/slurm.conf > /etc/slurm/slurm.conf.
States: ready → stateless → booted; rollcall unassign releases name and IPs.
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.