Commit Graph
100 Commits
Author SHA1 Message Date
Ilker ManapandClaude Opus 5.5 05861bd413 Intermediates renew themselves through the region
CI / go (push) Successful in 51s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
The region keeps every intermediate it signs: its name constraints,
certificate, the root that signed it and a renewal token (stored hashed,
printed once). `rollcall ca intermediate` goes through the API now (so it
works from any admin client) and writes NAME.token beside NAME.pem;
`rollcall ca intermediates` lists them.

The host with the intermediate's key runs deploy/rollcall-intermediate-
renew.sh daily (rollcall-intermediate-renew@NAME.timer): it posts a CSR
from the same key with the token to /api/v1/ca/intermediates/NAME/renew.
The region signs again in the last third of the certificate's life, or
at once when another root signs since a rotation's switch, always with
the constraints it was first given; otherwise 204. The script checks the
answer (a CA certificate for its key, from a root it holds), keeps the
old file, keeps the region's roots and the active root current from
/ca-bundle.pem, and reloads (RELOAD).

Attention: an intermediate under 30 days from its end, whose last renewal
failed or whose host has not asked for three days shows in rollcall
doctor, on the web UI's overview, and one from the old root in the
rotation status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:33:31 +01:00
Ilker ManapandClaude Opus 5.5 a5378dc291 Release 0.4.0
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 43s
release / release (push) Successful in 34s
release / image (push) Successful in 9m1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.4.0
2026-11-01 16:00:47 +01:00
Ilker ManapandClaude Opus 5.5 0569ecf1cc Review fixes before v0.4.0: NetBox on its own origin, pinned root refresh
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 49s
From a review of everything since v0.3.0:

- NetBox's UI was served on Rollcall's origin, so a script in a NetBox
  page (stored XSS, a custom link) could read the Rollcall UI's stored
  admin token and call Rollcall's API with the admin certificate. It now
  has a listener of its own (ipam.ui_listen, default :8444): another
  origin. The Rollcall UI builds its NetBox links from that port.
- The proxy dropped X-Rollcall-User but not X-Rollcall_User, which a
  WSGI server reads as the same HTTP_X_ROLLCALL_USER; every spelling is
  dropped now.
- Deployed machines fetched the root bundle over TLS checked against the
  system's trust store: a public CA's certificate for the rack's name
  would have done. The refresh is pinned to the Rollcall roots installed
  on the machine and takes only CA certificates (tried on node001 and
  node002).
- A NetBox device type NetBox will not resize (0U while its devices have
  positions) no longer stops those machines' device sync; it is logged.
- A NetBox cable on a device whose machine is gone here rolled back all
  cable and switch sync; such cables are skipped.
- Switch names are looked up in NetBox 50 at a time (a large site would
  outgrow one URL).
- rack_units: "vendor model" wins over the bare model when both are set
  (map order chose before); cable precedence comments match the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:35:45 +01:00
Ilker ManapandClaude Opus 5.5 6a7b158644 3D rooms: rail posts, solid servers of unknown height, a word when no cabling is known
CI / go (push) Successful in 44s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
Through the see-through cabinet both rail faces showed as two grey
boards, and a server of unknown height (drawn 1U, see-through) read as
an empty shelf. Racks now have their four rail posts instead of panels;
every server is solid, one of unknown height carries an amber outline
(the elevation's ?U). A room whose machines' LLDP saw no switch and
that NetBox has no cables for says so in the side panel, and how to
enter cables by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:56:16 +01:00
Ilker ManapandClaude Opus 5.5 1d41a83f27 3D rooms: switches show from the front; UI files revalidate
CI / site (push) Successful in 2s
CI / agent (push) Successful in 3s
CI / go (push) Successful in 42s
Switches stood only at the back of the rack and read as a dark slab
from the cold aisle. Each now has a front at its unit on the front
rails, with its name on a label in its cable colour, LEDs and fans, and
clicks and hovers like the rest of it.

The web UI's files carry Cache-Control: no-cache and an ETag per build
(and start), so a browser asks each time, gets 304 while nothing
changed, and the new files right after a deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:51:34 +01:00
Ilker ManapandClaude Opus 5.5 7111327dd7 Web UI: a toggle button that is on looks on outside a segmented group
CI / go (push) Successful in 43s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
The 3D room's Cables button turned the cables on and off but looked the
same either way: the "on" style was only defined inside .seg-btns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:44:42 +01:00
Ilker ManapandClaude Opus 5.5 3c7af23be2 3D rooms: cables as busbars, switches in the rack elevation
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 43s
Single thin cables were lost at room distance. Per rack and switch the
cables now run as one bus up the back of the rack, thicker the more
cables it carries, with a short stub from each port into it and a comb
under the switch with a tail into each switch port; a bus to a switch in
another rack crosses over the tops as a tray, to one in another room it
rises to the ceiling. Each switch's bus has its own lane behind the
rack. Everything is drawn as a few instanced meshes, picking resolves
the instance (a bus names its cable count, a stub its port).

Switches now show in the side panel's rack elevation at their unit (the
3D room's placement, estimates marked), their body runs deeper into the
rack, and on the selected rack they carry a short name. The panel is
drawn again once the switches are placed, also with a rack selected.
A switch placed by hand mid-rack (place…) gets its bus there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:39:54 +01:00
Ilker ManapandClaude Opus 5.5 dc1ca9f2c6 3D rooms: cabinets apart from servers, cables you can see
CI / go (push) Successful in 50s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
The cabinet and the servers were both dark grey, and the cables behind
an opaque cabinet could only be seen from the back. Cabinets are now
light steel and see-through like a perforated door; every server has a
solid dark chassis between its front and its back, so it stands out
from the cabinet. Cables are thicker and lit, each switch's cables run
up their own lane beside the rack instead of on top of one another, and
the switch colours start away from the selection's blue. The legend
says the cables are at the back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:28:16 +01:00
Ilker ManapandClaude Opus 5.5 44ac453bde NetBox through Rollcall: the admin certificate signs in to NetBox
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 43s
With ipam.ui_login_secret_file, the proxy tells NetBox who the admin is
(X-Rollcall-User: the certificate's admin name) together with a shared
secret (X-Rollcall-Proxy-Secret). NetBox takes the user from that header
(REMOTE_AUTH) and creates it on first use; the web server in front of
NetBox drops the header from any request that lacks the secret or does
not come from the region, so reaching NetBox directly claims no one.
Both headers coming from a browser are always dropped by Rollcall; an
admin signed in with a token instead of a certificate logs in to NetBox
by hand. A secret under 16 characters or an unreadable file leaves the
proxy off, with a log line.

The lab NetBox install sets it up: REMOTE_AUTH settings, a guarded
Caddyfile (readable by caddy only: the secret is in it), the secret in
secrets.env, and a rollcall-admins group that may do everything. Rollcall's
NetBox user may now view cables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:19:10 +01:00
Ilker ManapandClaude Opus 5.5 a613014073 Cabling: BMC ports to the out-of-band switches, NetBox cables, cables by hand
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 43s
The host's LLDP never sees the BMC's own port. A port's cable now comes
from LLDP first (what is really there), then from a cable recorded in
NetBox on the machine's device (read with the layout, traced through
patch panels to the far interface via connected_endpoints), then from
one entered by hand (POST/DELETE /api/v1/cables, the pencil in the
cabling list). A NetBox interface marked management-only or named like
a BMC (iDRAC, iLO, XCC, IMM, IPMI, BMC, OOB, mgmt) is the machine's
"bmc" port, so BMCs get their cable to the out-of-band switch. When
NetBox records another switch port than LLDP sees, the port says what
NetBox says.

A switch whose NetBox role names out-of-band or management, or that
only BMCs are cabled to, is marked OOB. In 3D the BMC cable leaves the
BMC port on the back (thinner); the side panel lists every machine's
ports with their source, the BMC always, and switches carry an OOB tag.
Estimated switch places now go around the units placed switches take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:05:17 +01:00
Ilker ManapandClaude Opus 5.5 d2624b2fc2 NetBox proxy: one route per method, beside the UI's "GET /"
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 43s
A bare "/netbox/" pattern conflicts with "GET /" in ServeMux (neither is
more specific), and the region panicked at start once ipam.url carried a
path. The proxy is registered per method now, and a test builds the
routes with it. The lab install script's own API calls use the new
/netbox/ base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:29:13 +01:00
Ilker ManapandClaude Opus 5.5 cc1245da1e NetBox's web UI through Rollcall, and links to it from the web UI
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 42s
With NetBox as the IPAM under a path (its BASE_PATH) and ipam.url
carrying it, Rollcall serves NetBox's UI at that same path, for admins
only (the admin certificate, as for the API); NetBox keeps its own
login. Redirects that name NetBox's own address are turned back to
Rollcall's, Rollcall's credentials are not passed on. NetBox opens
wherever Rollcall does: a lab NetBox on a segment the browser cannot
reach, say.

The web UI links to NetBox: the sidebar, sites, rooms, racks, switches
and a machine's device (by its rollcall_machine field). ipam.ui_url
points the links at NetBox directly instead.

NetBox paging under a BASE_PATH doubled the path on the next page
(the next link carries it already); fixed. The lab NetBox moves under
/netbox/ and trusts the region's addresses for its forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:21:22 +01:00
Ilker ManapandClaude Opus 5.5 c31a89482e 3D rooms: server backs, switches and the cables between them (LLDP)
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s
GET /api/v1/cabling gives each located machine's ports (from its
inventory: name, speed, link, the PCI card they sit on) and the switch
port each is plugged into, from the LLDP its commissioning saw (lldpcli's
neighbours, either JSON shape). Switches get a place: NetBox's (the
device's site, location, rack and position, read with the layout on
every reconcile) or one set by hand (POST /api/v1/switches, which NetBox
does not move); a switch with neither is drawn where most of its cables
are, at the highest free unit (on top of the rack when none is free),
and marked as estimated.

In 3D every server now has a back too: power supplies, the BMC port, USB
and VGA, and its network cards with their ports (SFP cages from 10G,
RJ45 below), per sled for a multi-node chassis. Switches stand at the
back with their ports in two rows. Cables run from each port out of the
back, down the rack's side, over the top to another rack's switch, into
the switch port; one colour per switch, grey without link, and up
through the ceiling to a switch in another room. Hovering a cable names
both ends and the VLAN; with a rack selected the other racks' cables
fade. "Cables" turns them off, "Turn around" looks from the other side.
The side panel lists the room's switches with their place (place… sets
it) and, for a selected rack, every port and the switch port it goes to,
in 2D as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:41:40 +01:00
Ilker ManapandClaude Opus 5.5 0d6f227819 3D rooms: server fronts drawn from the model
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s
Servers in the 3D room now show the front their model family has: 2.5"
bays standing or 3.5" bays lying in their groups, rack ears with the
power button, vents where there are no bays, in the vendor's colours
(fronts.js; drawn, no photos or logos). A family's bay options are tried
in order and the first with room for the machine's disks is drawn, empty
bays as blanks. A multi-node chassis shows one bay group per sled (six
each on Dell C-series), each sled clickable and lit in its own state.
The state shows as a strip and LED on the left ear and, when busy or
failed, a frame around the front. "Fronts / State" switches back to the
coloured blocks.

Seen from the aisle a rack's local +x is on the left: sleds were laid
out mirrored; fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:16:26 +01:00
Ilker ManapandClaude Opus 5.5 d9546fe264 Racks: one rack whatever the case of its name
CI / agent (push) Successful in 2s
CI / site (push) Successful in 3s
CI / go (push) Successful in 45s
A rack created from a machine's location ("lab / pxe hall / R1") and the
same rack from NetBox ("Lab / PXE Hall / R1") showed twice on the Racks
page. The NetBox layout sync now folds such a rack into NetBox's spelling:
its floor place and comment carry over where the NetBox one has none, and
the machines located in it move to the NetBox spelling. A machine's
location no longer creates a rack that differs only in case, rack machine
counts ignore case, and the Racks page keys racks without case, NetBox's
spelling first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:59:16 +01:00
Ilker ManapandClaude Opus 5.5 6da78311f3 Rooms in 3D: racks at their places and height, servers on their units
CI / agent (push) Successful in 4s
CI / site (push) Successful in 3s
CI / go (push) Successful in 49s
The room page gets a 2D / 3D switch (remembered per browser). The 3D view
draws the floor's 600 mm tiles, each rack at its place, facing and real
height (see-through while it has no place yet), and every server as a block
on the rack's front over its units, coloured by state; the sleds of a
multi-node chassis sit side by side, servers of unknown height are
see-through. Drag turns, right or shift drag moves, wheel or pinch zooms,
a double click brings a rack close; a click selects a rack (same address
and side panel as the floor plan), and a click on a server of the selected
rack opens the machine. The servers in the side panel's elevation now open
their machine on the floor plan page too.

The renderer outlives the 5 s re-render: its canvas moves into the new page
and the scene is rebuilt only when racks, machines, the selection or the
theme changed, so the camera stays where it was. Leaving the page frees the
WebGL context. Racks are still placed in the 2D editor.

three.js 0.160.0 (MIT) is vendored as web/vendor/three/three.module.min.js,
taken from the npm tarball whose sha512 matched the registry's integrity
field, and loaded with import() on first use: nothing is fetched from
outside. rackElev's block placement is split out as rackBlocks so the
elevation and the 3D fronts agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:47:25 +01:00
Ilker ManapandClaude Opus 5.5 54bda28b82 Sites: rooms and racks match whatever their case
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 44s
A rack typed in by hand (lab / pxe hall / R1) and the same rack from
NetBox (Lab / PXE Hall / R1) showed as two rooms and two racks. Site
views and the rack front match room and rack names ignoring case, as the
NetBox device sync does; one rack is shown, NetBox's spelling and a
placed one first. Found on the lab region.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:25:14 +01:00
Ilker ManapandClaude Opus 5.5 148c602b82 Sites on a map, rooms, floor plans with racks you can place
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s
CI / agent (push) Successful in 3s
The Sites page shows sites on an OpenStreetMap map (Leaflet 1.9.4 in the
binary, checked against cdnjs's SRI; the CSP allows OSM tiles and the
tile layer sends a referrer as OSM's policy asks). A site opens its
rooms, a room its floor plan: racks on 600 mm tiles, one wide and two
deep, front edge marked, coloured by their machines with a bar for the
units used; a rack opens its front, and the rack is part of the URL.
An edit mode drags racks into place and rotates them; racks nobody
placed are laid out in rows facing alternate aisles.

With NetBox as the IPAM authority, sites (coordinates, address),
locations and racks are read on every reconcile, never written back.
Otherwise sites are placed by clicking the map. Rack positions are
Rollcall's (NetBox has none).

Checked in headless Chrome with a synthetic site, including a scripted
drag, rotate and save of a rack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:20:56 +01:00
Ilker ManapandClaude Opus 5.5 9b0b27fce6 Server heights from firmware and model; rack elevation at real size
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 41s
Nobody types in how many units a server takes. Commissioning reads the
SMBIOS chassis height; without one the model decides, from a table of
heights checked against vendor documentation (Dell, HPE, Lenovo,
xFusion, Fujitsu, Inspur/KAYTUS, GIGABYTE; Supermicro model numbers do
not carry the height), and rack_units in config.json covers the rest and
overrides both. Sleds of a multi-node chassis share its units.

NetBox device types get the height (0 for sleds, which go into the rack
without a position; Rollcall's own types are corrected). The rack
elevation spans each server over its height, draws a chassis's sleds as
one block, marks unknown heights and lists overlaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:16:10 +01:00
Ilker ManapandClaude Opus 5.5 355401465c Network page for large sites: problems, filters, matrix, machine path
CI / agent (push) Successful in 2s
CI / site (push) Successful in 3s
CI / go (push) Successful in 41s
The topology drawing became an unreadable strip at a few hundred
machines and dozens of VLANs. The page now starts with what needs
attention (offline rack controllers and the VLANs they leave without
DHCP/PXE, provisioning VLANs without it, nearly full subnets, IPAM
errors), has filters by fabric, purpose and rack controller and a
search that shows a machine's path to the region, and a VLAN x rack
controller matrix that is the default once a site is large. The
topology groups machines by rack and VLAN, then by rack, when boxes
would not fit, and scrolls instead of shrinking; the overview shows one
row per fabric on a large site. Filters can come from the URL.

Checked with a synthetic site (3 fabrics, 9 rack controllers, 43
subnets, 360 machines) and the lab in headless Chrome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:55:42 +01:00
Ilker ManapandClaude Opus 5.5 a25b5c1f33 Rotation: refresh says when it is too late, admin certificates noted
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 42s
rollcall-ca-refresh reports a server it no longer trusts (the rotation
went past prepare before the machine refreshed) instead of exiting
quietly; the README asks for a day between prepare and switch. The
status lists admin certificates from before the switch, and retire
prints them: they stop working with the old root.

Tested end to end in the lab: prepare, switch and retire on pxe-server
with pxe-rack2 following without a new join (bundle installed by its
root unit, client and server certificates renewed), node001 (Debian) and
node002 (Rocky) moving to the new root with the refresh timer, and the
netops Caddy intermediate signed again by the new root.

Closes #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:36:24 +01:00
Ilker ManapandClaude Opus 5.5 a9235c85d8 Rack state re-pins rollcall.ca in stored boot entries
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 41s
Commission, deploy and stateless entries are stored with the root's
fingerprint of the moment; after a rotation switch a machine booting
from one refused its rack. The state now writes the root the rack serves.
Found in the lab rotation (node002's entry on pxe-rack2).

Refs #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:40:13 +01:00
Ilker ManapandClaude Opus 5.5 2f708e8f69 Root rotation without re-joining: prepare, switch, retire
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 47s
The CA directory holds the active root and, during a rotation, the next
or the previous one; everything that trusts the region gets the trust
bundle (all of them). Rack controllers receive it in their state, signed
by the active root; the root update unit installs it only when the
signature verifies against the roots it already trusts, and binary
signatures verify against any of them. After switch racks renew their
client and server certificates from the new root by themselves and
report what they run on; switch and retire wait for every rack (-force).

/ca.pem stays one certificate, the root of that server's certificate, and
the PXE command line names that root, so agents in older images keep
working; /ca-bundle.pem is the bundle. Deployed machines get one file per
root and a daily timer that refreshes them from their rack over HTTPS.

Refs #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:37:44 +01:00
Ilker ManapandClaude Opus 5.5 ff0de7e0b2 Release image upload: the image directory belongs to the deploy user
CI / go (push) Successful in 53s
CI / agent (push) Successful in 4s
CI / site (push) Successful in 2s
The image job built as root and uploaded as the deploy user, so
publish-image.sh could not write SHA256SUMS (v0.3.0's image job failed).
The job hands the directory over after the build, and publish-image.sh
checks the SHA256SUMS build-image.sh wrote instead of rewriting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:32:54 +01:00
Ilker ManapandClaude Opus 5.5 ea47438f81 Release 0.3.0
CI / agent (push) Successful in 2s
CI / site (push) Successful in 3s
CI / go (push) Successful in 41s
release / release (push) Successful in 33s
release / image (push) Failing after 8m25s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.3.0
2026-10-30 23:56:47 +01:00
Ilker ManapandClaude Opus 5.5 554b7bffd8 Release builds the tag's commissioning image, without SSH keys
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s
build-image.sh copied the build user's authorized_keys into the image
unless told otherwise, so the images published with v0.2.0 and v0.2.1
let three lab keys in as root on commissioning machines. Keys are now
opt-in (AUTHKEYS=); the lab deploy passes the deploy user's keys.

The release workflow gets an image job: it builds the tag's agent/ on the
PXE server in a directory of its own (the lab image stays), refuses an
image with authorized_keys, and uploads it as commissioning-* (token
over stdin). Tried by hand on pxe-server: built, no keys, new agents.

Closes #29

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:47:28 +01:00
Ilker ManapandClaude Opus 5.5 008fa3f66f Infoblox WAPI driver for the IPAM authority
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 41s
ipam.provider infoblox: one record:host per address, named by its DNS
name and configured for DNS in dns_view ("-" = IPAM only); ownership is
the rollcall_machine extensible attribute, changed with extattrs+ so a
host's other attributes stay. Anything else on the address (a host, a
lease, a fixed address, a DHCP or reserved range, network/broadcast) makes
it taken. Delegated networks carry the rollcall attribute. WAPI paging.

The reconcile no longer compares fields an authority does not keep
(Infoblox has no prefix length or status on a host). Built from the WAPI
reference and tested against a fake WAPI; no real grid yet.

Closes #11

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:22:17 +01:00
Ilker ManapandClaude Opus 5.5 eb2b7baa9f INSTALL: point at NetBox device sync and the lab NetBox
CI / agent (push) Successful in 3s
CI / go (push) Successful in 42s
CI / site (push) Successful in 2s
Closes #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:21:54 +01:00
Ilker ManapandClaude Opus 5.5 dc6eb55b26 Location changes kick the IPAM reconcile
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s
A machine's NetBox device follows a move at once, not at the next pass.

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:11:38 +01:00
Ilker ManapandClaude Opus 5.5 2e1a2176cd rollcall ipam sync: print the device report
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 42s
The previous commit's message promised this; the edit had not applied.

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:08:39 +01:00
Ilker ManapandClaude Opus 5.5 c3032683ab Device sync: v6 primary IP, DMI placeholders, CLI report
CI / site (push) Successful in 3s
CI / agent (push) Successful in 3s
CI / go (push) Successful in 41s
The primary IP of each family is the management address, else the first
address on the management interface (the v6 address next to a v4
management address). DMI placeholders ("Not Specified", "To Be Filled
By O.E.M.") are not written as serials. rollcall ipam sync prints the
device report.

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:07:22 +01:00
Ilker ManapandClaude Opus 5.5 133b5f38d9 NetBox device sync; lab NetBox container
CI / go (push) Successful in 43s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
ipam.device_sync mirrors commissioned machines as dcim devices: serial,
device type and role (created when missing), status, interfaces with MAC
objects, Rollcall's addresses assigned to them and the management address
as primary IP. Site, location and rack are matched by name from the
machine's location and never created. Ownership by tag and
rollcall_machine; a foreign device of the same name is a conflict.

lab/netbox/install.sh sets up a NetBox on the lab segment so the drivers
are tested without writing to a real NetBox.

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:00:43 +01:00
Ilker ManapandClaude Opus 5.5 c5688314b5 docs: fixed leases on the site's DHCP server
CI / go (push) Successful in 46s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:13:18 +01:00
Ilker ManapandClaude Opus 5.5 dbb0d93dc7 Kea: reservations in memory when there is no hosts database
CI / go (push) Successful in 41s
CI / agent (push) Successful in 2s
CI / site (push) Successful in 2s
Found against Kea 3.0.5 in the lab: reservation-add answers "Host
database not available" on a server that keeps its configuration in a
file. Then reservations go into the running configuration
(operation-target memory) and config-write saves them; with a hosts
database nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:13:05 +01:00
Ilker ManapandClaude Opus 5.5 482a9d5987 DHCP reservations on the site's DHCP server: Kea and OpenWrt
CI / go (push) Successful in 43s
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
On a VLAN in proxy mode the site's DHCP server hands out addresses and
Rollcall answers only PXE. With dhcp_reservations in config.json the
machines still get the addresses Rollcall planned: each address on a
proxy-mode IPv4 subnet becomes a fixed lease (MAC of its interface ->
IP, hostname) on that server, reconciled on every address change and
every sync_interval.

- kea: control API (Kea 3.0's HTTP socket or the Control Agent; the
  "service" field is learned) with the host_cmds hook, open source
  since Kea 3.0: reservation-add/-del/-get-all, subnet ids from
  config-get, ownership in user-context {"rollcall": machine},
  config-write afterwards (persist) for reservations kept in memory;
  a clear error without host_cmds (Debian's Kea 2.6 lacks it)
- openwrt: rpcd/ubus JSON-RPC, host sections in /etc/config/dhcp with
  option rollcall, uci commit and dnsmasq reload; relogin on expiry
- reservations others made are never touched; one holding the MAC or
  the address is a conflict

rollcall reservations [status] | sync [-dry]; API under
/api/v1/dhcp-reservations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:46:44 +01:00
Ilker ManapandClaude Opus 5.5 b2ad0210bf GRUB keeps the firmware's text console; phone-home back to HTTP
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 40s
Lab, Secure Boot VM: the deployed Debian booted under Secure Boot
through the chainload, but its console was garbled after the network
GRUB had switched the display mode; GRUB menus now use the firmware's
text console (terminal_output console), which also suits serial/SOL.

phone-home over HTTPS failed once on that first boot (TLS: bad record
MAC on the region's side; the same request works from the machine
since), and cloud-init spends its 10 tries within milliseconds. Its
content is not secret and its token only works from the machine's
address, so it goes over HTTP again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:51:22 +01:00
Ilker ManapandClaude Opus 5.5 c494c7b226 GRUB local boot chainloads the disk's signed loader; image sums
CI / go (push) Successful in 45s
CI / agent (push) Successful in 4s
CI / site (push) Successful in 2s
On the Secure Boot lab VM, GRUB's "exit" back to the firmware did not
reach the disk: OVMF went on to its other network boot options and
stopped in its own boot menu. The local-disk entry now finds the
installed OS's signed loader on the EFI partition (shimx64.efi under
EFI/debian, ubuntu, rocky, almalinux, redhat, centos; shim.efi for
SUSE; else EFI/BOOT/BOOTX64.EFI) and chainloads it, as MAAS does.

build-image.sh writes SHA256SUMS next to the image (the old one was
stale after a rebuild; bootstrap.sh --image-url checks it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:28:56 +01:00
Ilker ManapandClaude Opus 5.5 a0df7295ea UEFI PXE through signed shim and GRUB (Secure Boot)
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 40s
UEFI machines were given syslinux.efi, which is unsigned: with Secure
Boot on (the default on much server hardware) the firmware refuses it
("Access Denied", seen on an OVMF lab VM), so such a machine could not
PXE-boot at all. They now get Debian's signed shim (bootx64.efi), which
loads the signed network GRUB (grubx64.efi); GRUB reads
grub/grub.cfg-<mac>, else grub/grub.cfg-default, and loads kernels
only as shim verifies them. Rack controllers write the GRUB menus from
the pxelinux menus they already get (same entries: kernel and initrd,
local disk via exit, power off via halt), so both always agree; BIOS
machines keep pxelinux. install.sh, install-rack.sh and bootstrap.sh
install shim-signed and grub-efi-amd64-signed and lay out the files.

Secure Boot covers the kernel; the initrd and the squashfs it fetches
are not covered by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:25:54 +01:00
Ilker ManapandClaude Opus 5.5 ab20c277b2 CHANGELOG: stateless overlays and phone-home over HTTPS [image]
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 39s
Rebuilds the commissioning image with the HTTPS-capable agents (#29).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:22:42 +01:00
Ilker ManapandClaude Opus 5.5 47210a9e69 gofmt rackctl.go
CI / agent (push) Successful in 2s
CI / site (push) Successful in 3s
CI / go (push) Successful in 40s
CI's gofmt step failed on it since a9ca326, so CI and the deploy that
follows it stopped running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:16:17 +01:00
Ilker ManapandClaude Opus 5.5 179fdcdec5 HTTPS for stateless overlays and phone-home
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Failing after 21s
The stateless overlay client (rollcall-overlay in the compute image)
takes rollcall.tls= and rollcall.ca= like the agents: the CA is accepted
only by its fingerprint and the overlay (munge.key and the like) comes
over HTTPS, with no fallback. A deployed OS calls home over HTTPS when
its rack serves it (it trusts the Rollcall CA from first boot).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:12:55 +01:00
Ilker ManapandClaude Opus 5.5 97959af899 docs: agents over HTTPS
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Failing after 20s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:26:25 +01:00
Ilker ManapandClaude Opus 5.5 686bfecbee CD: ship all of deploy/ and agent/
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Failing after 20s
install.sh installs rollcall-dnsmasq-reload.sh and the systemd units
from the deploy directory, but CD copied only five files: every deploy
since a549f01 reinstalled a stale, pre-security reload script on the
region from an old copy left in /opt/rollcall/deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:13:20 +01:00
Ilker ManapandClaude Opus 5.5 a9ca326dd2 Agents over HTTPS: rack server certificates, fingerprinted CA
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Failing after 20s
Rack controllers ask the region for a server certificate for their own
URL's host (POST /api/v1/rack/server-cert; CN rack-server:NAME,
ServerAuth only, never one of the region's names or addresses) and serve
the agents' endpoints and images over HTTPS too (tls_listen, default
<host>:443), reporting the HTTPS URL when they register (accepted only
on the rack's own host). The pxelinux command lines name it with the CA
fingerprint (rollcall.tls=, rollcall.ca=); the embedded rack uses the
region's listener. Agents fetch /ca.pem, accept it only by that
fingerprint and then use HTTPS with it, including the image download; a
job fetched over HTTPS gets an HTTPS image URL. No fallback to HTTP once
TLS was asked for; old agents ignore the new words. The CLI refuses a
rack controller's certificate as the region.

PXE itself (TFTP, kernel, initrd) stays unauthenticated: this protects
what agents send and receive from listeners and from an HTTP server
posing as the rack, not from an attacker rewriting boot files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:51:17 +01:00
Ilker ManapandClaude Opus 5.5 74922f7491 docs: BMC certificate pinning
CI / go (push) Successful in 39s
CI / agent (push) Successful in 2s
CI / site (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:42:42 +01:00
Ilker ManapandClaude Opus 5.5 d0ea29d2a9 Power: say why a BMC command failed (CLI, UI); certificate changes are events
CI / agent (push) Successful in 2s
CI / site (push) Successful in 3s
CI / go (push) Successful in 40s
Found in the lab test of certificate pinning: the CLI printed only
'ipmitool failed on 1 machine(s)'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:37:02 +01:00
Ilker ManapandClaude Opus 5.5 9ab081aa5a BMC certificates pinned on first use (Redfish, Proxmox)
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 42s
The Redfish and Proxmox clients skipped TLS verification (BMCs ship
self-signed certificates), so a host posing as a BMC on the network got
the Basic-auth password. Before any credentials go to an HTTPS BMC the
region now reads its certificate with a handshake-only operation
(tls-fingerprint, run on the rack controller) and pins the SHA-256 on
the BMC record (extra tls_fingerprint); every later connection must show
the same certificate or nothing is sent. Changing the address, clearing
tls_fingerprint, or a BMC firmware update run by Rollcall (new firmware
often means a new certificate) pins anew. verify_tls=true still uses the
system CAs instead. The lab emulator keeps its certificate like a BMC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:32:38 +01:00
Ilker ManapandClaude Opus 5.5 24730d03d1 CHANGELOG: token binding, BMC hints, rack renewal
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 40s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:29:19 +01:00
Ilker ManapandClaude Opus 5.5 dc093391a5 Machine tokens only from the machine's own address
CI / agent (push) Successful in 2s
CI / site (push) Successful in 2s
CI / go (push) Successful in 43s
Job and node tokens travel in the per-MAC pxelinux file, readable by any
host on the provisioning network over TFTP; with a job token one could
fetch the deploy seed (user-data, tag cloud-config) or report the job,
with a node token the stateless overlay (e.g. munge.key).

The region knows the address it gives each machine (static DHCP leases
from the network model): /jobs/self, job progress, phone-home and the
node overlay are now refused from anywhere else (403, an event on the
machine). Through a remote rack controller the address is the rack's
own X-Forwarded-For entry, believed only from an authenticated rack. A
machine without a fixed address (proxy DHCP) binds a job to the first
address that uses it. agent_source_check: false turns this off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:50:01 +01:00
Ilker ManapandClaude Opus 5.5 e474fbcf16 BMC hints: the password stays on the region
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 41s
GET /api/v1/bmc-hint answered with the BMC password to anyone with the
agent token (public on the PXE command line) and a MAC (visible on the
wire). It now returns the address and user only. When a machine reports
in, the region matches its MACs against bmc_hints itself, seals the
configured password into the record, and checks the BMC with a status
call from the rack controller (verified_oob). The agent skips its own
check when no password comes; old agents keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:28:00 +01:00