169 lines
7 KiB
Markdown
169 lines
7 KiB
Markdown
|
|
# 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 (1–48)
|
|||
|
|
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 (``),
|
|||
|
|
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 1–48; `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.
|