CA (llmgw-style) - region creates its CA (ECDSA P-256) and serves API/UI/images over HTTPS on tls_listen with a certificate for every name/address it has (reissued on change or a month before expiry); admin_http policy (allow|loopback|deny) for the admin API over plain HTTP; /ca.pem - rack controllers enroll with one-time join codes (CA fingerprint + secret + expiry + address): rollcall controller join-code, rollcall rack join; mutual TLS from then on, identity from the certificate (CN rack:NAME, serial on record), removal revokes, renewal a month before expiry; the shared rack_secret stays as a legacy fallback - clients: CA from the server config, ROLLCALL_CA, or trusted on first connect in the console after showing the fingerprint (kept in client.json); deployed machines trust it via cloud-init ca_certs (allowed on RHEL-family images), overlays get .CA; rollcall ca show|issue Power drivers - ipmi (cipher/privilege/interface), redfish (system discovery, ResetType from AllowableValues or ActionInfo, boot override with If-Match), idrac, ilo, supermicro, xcc, openbmc, proxmox (API token), manual; executed on the rack controller; Redfish auto-detection after commissioning; operator settings survive recommissioning; rollcall bmc, BMC editing in web UI and console - lab: pve_redfish.py Redfish emulator per Proxmox VM (iDRAC, iLO, generic profiles) IPv6 - IPv6 subnets and IPAM (netip), dual-stack default layout (all subnets of the boot VLAN), per-family default routes, accept-ra off where addresses are planned, dnsmasq RA/DHCPv6 with router lifetime 0 unless the rack is the gateway, AAAA host records from IPAM, IPv6 listen addresses and URLs, rack interfaces and region certificate with IPv6, agent reports IPv6 Tests for CA/join/pinning/CSR signing, Redfish (iDRAC, iLO fallbacks, probe), Proxmox, manual, IPv6 candidates, dual-stack plans and dnsmasq IPv6. Verified in the lab: rack2 enrolled and running on mutual TLS only, revocation and single-use codes; iLO auto-detected through rack2, iDRAC and Proxmox drivers, wrong password not saved; Debian 13 (bond+VLAN), Rocky 9 and AlmaLinux 9 dual-stack with Redfish-driven deployments; CA trusted on Debian and AlmaLinux; admin CLI over HTTPS on IPv4 and IPv6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
3.9 KiB
Bash
63 lines
3.9 KiB
Bash
#!/bin/sh
|
|
# Install a rollcall rack controller on this host (Debian/Ubuntu). Run as root
|
|
# from this directory with the rollcall binary next to it, after enrolling:
|
|
#
|
|
# region$ rollcall controller join-code -name hall-b -addr REGION:443
|
|
# rack$ ./rollcall rack join <code> -url http://ADDR-ON-THE-SEGMENT
|
|
#
|
|
# which writes /etc/rollcall/rack.json and the rack's certificate; from then on
|
|
# rack and region talk over mutual TLS (rack.example.json shows the fields).
|
|
#
|
|
# The rack controller serves DHCP/TFTP (dnsmasq) on the VLANs the region
|
|
# assigns to it, keeps boot and OS images in a local cache, runs BMC commands
|
|
# and passes the node API through to the region.
|
|
set -eu
|
|
cd "$(dirname "$0")"
|
|
[ -r /etc/rollcall/rack.json ] || { echo "enroll first: ./rollcall rack join CODE -url http://ADDR (code from: rollcall controller join-code on the region)" >&2; exit 1; }
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get install -y -q dnsmasq ipmitool pxelinux syslinux-common syslinux-efi jq >/dev/null
|
|
|
|
getent passwd rollcall >/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin --user-group rollcall
|
|
[ -x ./rollcall ] && install -m 0755 rollcall /usr/local/bin/rollcall
|
|
chgrp rollcall /etc/rollcall/rack.json && chmod 0640 /etc/rollcall/rack.json
|
|
# certificate from rollcall rack join: readable by the service's group, the key by nobody else
|
|
if [ -d /etc/rollcall/rack ]; then chgrp -R rollcall /etc/rollcall/rack && chmod 0750 /etc/rollcall/rack && chmod 0640 /etc/rollcall/rack/*-key.pem; fi
|
|
|
|
TFTP=$(jq -r '(.tftp_root // "") | if . == "" then "/srv/tftp" else . end' /etc/rollcall/rack.json)
|
|
CACHE=$(jq -r '(.cache_dir // "") | if . == "" then "/var/cache/rollcall" else . end' /etc/rollcall/rack.json)
|
|
DNS=$(jq -r '(.dnsmasq_dir // "") | if . == "" then "/var/lib/rollcall/dnsmasq" else . end' /etc/rollcall/rack.json)
|
|
|
|
# image cache: kernels/initrds are prefetched (TFTP cannot fetch on demand), the rest fills on first use
|
|
install -d -o rollcall -g rollcall "$CACHE" "$CACHE/rollcall" "$CACHE/images" "$CACHE/os"
|
|
|
|
# TFTP root: syslinux for BIOS and UEFI, per-MAC files written by the rack controller
|
|
install -d "$TFTP" "$TFTP/efi64"
|
|
install -d -o rollcall -g rollcall -m 2775 "$TFTP/pxelinux.cfg"
|
|
cp /usr/lib/PXELINUX/pxelinux.0 "$TFTP/"
|
|
for f in ldlinux.c32 menu.c32 libutil.c32 libcom32.c32 poweroff.c32; do cp /usr/lib/syslinux/modules/bios/$f "$TFTP/"; done
|
|
cp /usr/lib/SYSLINUX.EFI/efi64/syslinux.efi "$TFTP/efi64/"
|
|
for f in ldlinux.e64 menu.c32 libutil.c32 libcom32.c32; do cp /usr/lib/syslinux/modules/efi64/$f "$TFTP/efi64/"; done
|
|
ln -sfn ../pxelinux.cfg "$TFTP/efi64/pxelinux.cfg"
|
|
ln -sfn "$CACHE/rollcall" "$TFTP/rollcall"
|
|
ln -sfn "$CACHE/images" "$TFTP/images"
|
|
ln -sfn ../rollcall "$TFTP/efi64/rollcall"
|
|
ln -sfn ../images "$TFTP/efi64/images"
|
|
|
|
# dnsmasq reads what the rack controller generates; until then it only answers local DNS
|
|
install -d -o rollcall -g rollcall "$DNS" "$DNS/conf.d"
|
|
for f in hosts hosts.dhcp; do [ -e "$DNS/$f" ] || install -m 0644 -o rollcall -g rollcall /dev/null "$DNS/$f"; done
|
|
# bind-dynamic binds address by address, so systemd-resolved's 127.0.0.53 stub does not get in the way;
|
|
# until the region assigns a VLAN only loopback is served
|
|
printf '# rollcall rack controller: the configuration is generated from the region'"'"'s network model\nbind-dynamic\ninterface=lo\nconf-dir=%s/conf.d/,*.conf\n' "$DNS" > /etc/dnsmasq.d/rollcall.conf
|
|
install -m 0755 rollcall-dnsmasq-reload.sh /usr/local/bin/rollcall-dnsmasq-reload.sh
|
|
install -m 0644 rollcall-dnsmasq.path rollcall-dnsmasq.service rollcall-rack.service /etc/systemd/system/
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable dnsmasq.service rollcall-dnsmasq.path >/dev/null 2>&1
|
|
systemctl restart dnsmasq.service || { journalctl -u dnsmasq -n 5 --no-pager >&2; exit 1; }
|
|
systemctl start rollcall-dnsmasq.path
|
|
systemctl enable --now rollcall-rack.service
|
|
systemctl restart rollcall-rack.service
|
|
sleep 2
|
|
systemctl --no-pager status rollcall-rack.service | head -5
|