MakerFLOSS/notes/dev/specs/2026-06-24-rack-documentation-design.md

169 lines
7 KiB
Markdown
Raw Normal View History

# Rack Documentation Design
**Date:** 2026-06-24
**Status:** Approved
## Goal
Document the 48U server rack as hand-edited Markdown that a CI pass turns into a
clear visual presentation, mirroring the existing `docs/hardware/*.md`
`gen_overview.py` → generated-index pattern. The rendered output covers three
views: a physical rack **elevation** (SVG), a **power** distribution graph
(mermaid), and a **network cabling** graph (mermaid).
Authors (operator or AI) edit frontmatter; a push regenerates the artifacts and
CI fails on drift, so the published page at `docs.makerfloss.eu` is always in
sync with the source.
## Context
- Per-item frontmatter is already the norm: `docs/hardware/*.md` and
`docs/services/*.md` carry YAML frontmatter validated by
`scripts/gen_overview.py` against schemas in `scripts/overview_config.yml`.
- The generator writes a grouped/sorted `index.md`; CI (`.forgejo/workflows/docs.yml`)
regenerates it and runs `git diff --exit-code` to **fail on drift**.
- Mermaid already renders in the Marp slide pipeline; it is **not yet enabled in
MkDocs**. Enabling it is a small `mkdocs.yml` change (superfences custom fence;
Material ships mermaid.js).
- The rack contains devices that already have host pages (`mf01`..`mf04`).
- **The physical rack is labeled U1 at the top**, descending to U48 at the
bottom (non-standard; standard racks number U1 at the bottom). The elevation
must match the physical labels.
## Data model
Rack data is added to **host frontmatter** (decision: extend existing files
rather than introduce a separate layout file). Rack-mounted items that are not
hosts (PDUs, patch panels, shelves, blank panels, UPS, KVM) each get their own
lightweight file in `docs/hardware/` using new `kind` values.
### Frontmatter fields
```yaml
# placement (Phase 1)
rack: rack01 # rack identifier; one rack today, field enables future racks
rack_u: 12 # lowest U occupied (148)
u_height: 2 # number of U occupied
rack_face: front # front | rear | both | left | right (0U PDUs use left/right rails)
# power (Phase 2) — on each powered device
power:
- { pdu: pdu01, outlet: 3 }
- { pdu: pdu02, outlet: 3 } # a second entry expresses a redundant PSU feed
# network (Phase 3) — on each device, one entry per cable end originating here
links:
- { local: eth0, peer: sw01, peer_port: 12, speed_gbps: 1 }
```
### New `kind` enum values
Extend the `hardware` enum in `overview_config.yml` with: `pdu`, `patch-panel`,
`shelf`, `blank`, `ups`, `kvm` (joining the existing `server`, `laptop`, `sbc`,
`switch`, `ap`, `desktop`).
Non-host item files declare their own capacity where relevant:
```yaml
# docs/hardware/pdu01.md
hostname: pdu01
kind: pdu
status: in-use
rack: rack01
rack_face: left
outlets: 8
```
```yaml
# docs/hardware/pp01.md
hostname: pp01
kind: patch-panel
status: in-use
rack: rack01
rack_u: 24
u_height: 1
rack_face: front
ports: 24
```
## Generator: `scripts/gen_rack.py`
A sibling to `gen_overview.py`, sharing its style (stdlib + PyYAML, deterministic,
offline, `SchemaError` → non-zero exit). It reads every `docs/hardware/*.md` with
a `rack:` field, validates the rack schema, groups by `rack`, and writes
generated artifacts per rack.
### Outputs (do-not-edit, generated)
- `docs/infrastructure/racks/rack01-elevation.svg` — the elevation picture.
- `docs/infrastructure/racks/rack01.md` — generated page embedding, in order:
1. the elevation SVG (`![Rack rack01 elevation](rack01-elevation.svg)`),
2. a mermaid **power** graph (Phase 2),
3. a mermaid **network** graph (Phase 3),
4. an occupancy table (U-range, hostname/link, kind, face, status).
Each generated file carries the same "Auto-generated … do not edit by hand"
banner the existing indices use.
### Rendering
- **Elevation (SVG):** two side-by-side columns, **front** and **rear**. 48 U
rows numbered **U1 at the top → U48 at the bottom** to match the physical
rack. Each device is a rectangle spanning its true `u_height`, filled by a
per-`kind` color, labeled with hostname and U-range. Empty U slots drawn
faintly. 0U side-rail items (`rack_face: left|right`) drawn as thin vertical
bars beside the columns. Plain hand-written SVG strings — no external drawing
library.
- **Power (mermaid):** `flowchart` of `pdu → (outlet) → device`; redundant feeds
appear as multiple edges into one device.
- **Network (mermaid):** `flowchart` of `device[:local] -- speed --> peer[:port]`
edges built from `links`.
### Validation rules (CI-enforced, fail with a clear message)
1. `rack_u` in 148; `rack_u + u_height 1 ≤ 48`.
2. No two items overlap the same U range on the same `rack_face` within a rack.
3. Every `power[].pdu` resolves to a file whose `kind: pdu`; `outlet` within that
PDU's `outlets`.
4. Every `links[].peer` resolves to a real file; `peer_port` within the peer's
declared `ports`/port count where the peer declares one.
5. Items with `rack_face: left|right` (0U) must omit `rack_u`/`u_height`;
all other rack items must include them.
## Integration
| File | Change |
|------|--------|
| `scripts/gen_rack.py` | New generator (SVG + mermaid + table + validation) |
| `scripts/overview_config.yml` | Extend `hardware` `kind` enum with new values; optional `rack` config block if reusing config-driven validation |
| `docs/hardware/*.md` | Add placement (then power, then links) fields to rack occupants; add new non-host item files |
| `docs/infrastructure/racks/` | New dir holding generated `rack01.md` + `rack01-elevation.svg` |
| `Makefile` | `docs-index`/`docs-check` gain a `gen_rack.py` step |
| `.forgejo/workflows/docs.yml` | Run `gen_rack.py`; extend the drift `git diff` guard to the generated rack artifacts |
| `mkdocs.yml` | Enable mermaid (superfences custom fence) [Phase 3]; add the rack page to `nav` |
## Phasing
Each phase is independently shippable as its own PR.
1. **Phase 1 — Elevation.** Placement schema + new kinds + `gen_rack.py`
producing the SVG elevation and occupancy table + CI drift check + nav entry.
Populate `rack01` with the current devices.
2. **Phase 2 — Power.** `power` fields + PDU files + generated mermaid power
graph + validation rules 3.
3. **Phase 3 — Network.** `links` fields + patch-panel files + generated mermaid
network graph + validation rule 4 + enable mermaid in `mkdocs.yml`.
## Test plan
- **Unit (per phase):** run `python3 scripts/gen_rack.py`; assert it writes the
expected artifacts and exits 0 on a valid fixture set.
- **Validation:** craft fixtures that violate each rule (U overflow, overlap,
dangling `pdu`/`peer`, bad outlet/port, 0U with U fields) and assert non-zero
exit with the right message.
- **Drift:** run the generator, confirm `git diff --exit-code` is clean; mutate a
source file without regenerating and confirm CI's guard fails.
- **Visual:** `make docs-build` (or `docs-serve`), open the rack page, confirm
the SVG shows U1 at the top, devices at correct positions/faces, and (Phase
2/3) the mermaid graphs render rather than appearing as code blocks.