MakerFLOSS/notes/dev/specs/2026-06-24-rack-documentation-design.md
sjat c362c93f65 docs(spec): rack documentation design (md → CI → SVG/mermaid)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:25:28 +02:00

168 lines
7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.