feat(docs): MkDocs Material site + auto-generated hardware overview
Bootstraps an MkDocs Material documentation site (rendered to
docs.makerfloss.eu by the Forgejo Actions runner). The first feature
is an auto-generated hardware overview built from per-host YAML
frontmatter blocks under docs/hardware/.
- mkdocs.yml, requirements.txt: MkDocs Material 9.5 + pyyaml
- Makefile: docs-index | docs-build | docs-serve | docs-check
- scripts/gen_overview.py: stdlib + pyyaml generator, deterministic and
offline. Reads scripts/overview_config.yml — category-driven so
services/vms can plug in later without touching the script.
- scripts/overview_config.yml: hardware schema and index layout
- docs/hardware/{makerfloss,fisi,tembo}.md: 3 sample entries
- docs/hardware/index.md: GENERATED, committed (CI fails on drift)
- docs/index.md: site landing page
- .forgejo/workflows/docs.yml: drift-check + mkdocs build --strict +
rsync site/ to /srv/docs-makerfloss/html on push to main
- .gitignore: site/, .venv, __pycache__
Schema:
- hostname, kind, status (required; kind/status are enums)
- model, location, cpu, cpu_cores, cpu_threads, ram_gb, storage_gb,
storage_type (enum), storage_notes, nic_gbps (all optional)
- Filename stem MUST equal hostname (enforced by generator)
- Extra optional fields are accepted silently and live on the per-page
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d81c3af3b3
commit
6b1a61461b
12 changed files with 494 additions and 0 deletions
46
.forgejo/workflows/docs.yml
Normal file
46
.forgejo/workflows/docs.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
name: Build docs site
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: self-hosted
|
||||||
|
container:
|
||||||
|
image: python:3.13-bookworm
|
||||||
|
volumes:
|
||||||
|
- /srv/docs-makerfloss/html:/output
|
||||||
|
steps:
|
||||||
|
- name: Install git for actions/checkout
|
||||||
|
run: |
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y --no-install-recommends git rsync
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: pip install --quiet -r requirements.txt
|
||||||
|
|
||||||
|
- name: Regenerate hardware index
|
||||||
|
run: python3 scripts/gen_overview.py --category hardware
|
||||||
|
|
||||||
|
- name: Fail on drift in docs/hardware/index.md
|
||||||
|
run: |
|
||||||
|
if ! git diff --exit-code docs/hardware/index.md; then
|
||||||
|
echo
|
||||||
|
echo "::error::docs/hardware/index.md is stale."
|
||||||
|
echo "Regenerate locally via 'make docs-index' and commit the result."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build site (strict)
|
||||||
|
run: mkdocs build --strict
|
||||||
|
|
||||||
|
- name: Publish to /output (main only)
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
run: rsync -a --delete site/ /output/
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -5,6 +5,15 @@
|
||||||
slides/*.html
|
slides/*.html
|
||||||
slides/*.pdf
|
slides/*.pdf
|
||||||
|
|
||||||
|
# ---> MkDocs build output
|
||||||
|
site/
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# ---> Python venvs / caches
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
# ---> Local secrets (Forgejo API token, etc.)
|
# ---> Local secrets (Forgejo API token, etc.)
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|
|
||||||
25
Makefile
Normal file
25
Makefile
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
.PHONY: help docs-index docs-build docs-serve docs-check slides
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Targets:"
|
||||||
|
@echo " docs-index Regenerate docs/hardware/index.md from per-host frontmatter"
|
||||||
|
@echo " docs-build Build the static MkDocs site into ./site (strict)"
|
||||||
|
@echo " docs-serve Run a live-reload local preview server"
|
||||||
|
@echo " docs-check Drift-check: regenerate index, fail if it differs from the committed copy"
|
||||||
|
@echo " slides Run build-slides.sh (Marp slides)"
|
||||||
|
|
||||||
|
docs-index:
|
||||||
|
python3 scripts/gen_overview.py --category hardware
|
||||||
|
|
||||||
|
docs-build:
|
||||||
|
mkdocs build --strict
|
||||||
|
|
||||||
|
docs-serve:
|
||||||
|
mkdocs serve
|
||||||
|
|
||||||
|
docs-check:
|
||||||
|
python3 scripts/gen_overview.py --category hardware
|
||||||
|
git diff --exit-code docs/hardware/index.md
|
||||||
|
|
||||||
|
slides:
|
||||||
|
./build-slides.sh
|
||||||
25
docs/hardware/fisi.md
Normal file
25
docs/hardware/fisi.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
hostname: fisi
|
||||||
|
kind: server
|
||||||
|
status: in-use
|
||||||
|
model: HP MicroServer Gen10 Plus
|
||||||
|
location: home rack
|
||||||
|
cpu: Xeon E-2226G
|
||||||
|
cpu_cores: 6
|
||||||
|
cpu_threads: 12
|
||||||
|
ram_gb: 64
|
||||||
|
storage_gb: 8000
|
||||||
|
storage_type: hdd
|
||||||
|
storage_notes: ZFS mirror 2×8 TB HDD + 1 TB NVMe cache
|
||||||
|
nic_gbps: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# fisi
|
||||||
|
|
||||||
|
Primary home server in the baobab.band homelab. Hosts the bulk of
|
||||||
|
self-hosted services: Nextcloud, Jellyfin + *arr stack, Technitium DNS,
|
||||||
|
PhotoPrism, Matrix (conduwuit + Element), Forgejo (internal), Vaultwarden,
|
||||||
|
and more.
|
||||||
|
|
||||||
|
Not part of the MakerFLOSS infrastructure proper, listed here for
|
||||||
|
Proxmox-style placement planning when we eventually share workloads.
|
||||||
16
docs/hardware/index.md
Normal file
16
docs/hardware/index.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Hardware Overview
|
||||||
|
|
||||||
|
_Auto-generated from `docs/hardware/*.md` — do not edit by hand. Run `make docs-index` after changing a file._
|
||||||
|
|
||||||
|
## Laptops
|
||||||
|
|
||||||
|
| Hostname | Model | Location | CPU | RAM | Storage | NIC | Status |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| [tembo](tembo.md) | ThinkPad T480 | Orange Makerspace (kiosk) | Intel Core i5-8350U · 4c/8t | 16 GB | 512 GB NVME | 1 GbE | in-use |
|
||||||
|
|
||||||
|
## Servers
|
||||||
|
|
||||||
|
| Hostname | Model | Location | CPU | RAM | Storage | NIC | Status |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| [fisi](fisi.md) | HP MicroServer Gen10 Plus | home rack | Xeon E-2226G · 6c/12t | 64 GB | 8 TB HDD | 1 GbE | in-use |
|
||||||
|
| [makerfloss](makerfloss.md) | Hetzner CX22 | Hetzner HEL1 (cloud) | AMD EPYC (shared vCPU) · 2c | 4 GB | 40 GB NVME | 1 GbE | in-use |
|
||||||
24
docs/hardware/makerfloss.md
Normal file
24
docs/hardware/makerfloss.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
hostname: makerfloss
|
||||||
|
kind: server
|
||||||
|
status: in-use
|
||||||
|
model: Hetzner CX22
|
||||||
|
location: Hetzner HEL1 (cloud)
|
||||||
|
cpu: AMD EPYC (shared vCPU)
|
||||||
|
cpu_cores: 2
|
||||||
|
cpu_threads: 2
|
||||||
|
ram_gb: 4
|
||||||
|
storage_gb: 40
|
||||||
|
storage_type: nvme
|
||||||
|
nic_gbps: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# makerfloss
|
||||||
|
|
||||||
|
Hetzner Cloud VPS running the public-facing MakerFLOSS stack: Forgejo
|
||||||
|
(self-hosted git forge), Traefik with Let's Encrypt, poste.io mail
|
||||||
|
server, a Forgejo Actions runner, and the nginx services that serve
|
||||||
|
`slides.makerfloss.eu` and `docs.makerfloss.eu`.
|
||||||
|
|
||||||
|
Managed via the [`AnsibleBaobabV4`](https://forgejo.nyumbani.baobab.band/sjat/AnsibleBaobabV4)
|
||||||
|
Ansible project. SSH on port 7576.
|
||||||
24
docs/hardware/tembo.md
Normal file
24
docs/hardware/tembo.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
hostname: tembo
|
||||||
|
kind: laptop
|
||||||
|
status: in-use
|
||||||
|
model: ThinkPad T480
|
||||||
|
location: Orange Makerspace (kiosk)
|
||||||
|
cpu: Intel Core i5-8350U
|
||||||
|
cpu_cores: 4
|
||||||
|
cpu_threads: 8
|
||||||
|
ram_gb: 16
|
||||||
|
storage_gb: 512
|
||||||
|
storage_type: nvme
|
||||||
|
nic_gbps: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# tembo
|
||||||
|
|
||||||
|
XFCE-based touchscreen kiosk laptop at Orange Makerspace. Runs a
|
||||||
|
rotation of dashboards (Grafana, the Marp slides at
|
||||||
|
`slides.makerfloss.eu`, the radio page) and serves as the local
|
||||||
|
display for the room. Also runs a Grafana/Loki/Prometheus stack.
|
||||||
|
|
||||||
|
Acts as an example of a re-purposed laptop being treated as a fixed
|
||||||
|
piece of infrastructure rather than a personal device.
|
||||||
22
docs/index.md
Normal file
22
docs/index.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# MakerFLOSS Docs
|
||||||
|
|
||||||
|
Documentation for the [MakerFLOSS](https://forgejo.makerfloss.eu/sjat/MakerFLOSS)
|
||||||
|
initiative at [Orange Makerspace](https://orangemakers.dk) — a bi-weekly FLOSS
|
||||||
|
jam-session community focused on self-hosted, open-source infrastructure.
|
||||||
|
|
||||||
|
## Sections
|
||||||
|
|
||||||
|
- [Hardware](hardware/index.md) — every machine in the lab, auto-indexed from per-host
|
||||||
|
frontmatter blocks. Use this when planning where to deploy a new service.
|
||||||
|
- [House rules](makerFLOSS_house_rules.md) — working norms, governance, and
|
||||||
|
what we do (and don't) do.
|
||||||
|
|
||||||
|
## Working norms (summary)
|
||||||
|
|
||||||
|
- **Language:** English for code and docs. Danish allowed in meeting notes and
|
||||||
|
community communications.
|
||||||
|
- **Environments:** containerised and reproducible.
|
||||||
|
- **Hardware:** all setups documented (this site) and physically labelled.
|
||||||
|
- **Decisions:** lightweight markdown decision logs under
|
||||||
|
[`docs/superpowers/`](https://forgejo.makerfloss.eu/sjat/MakerFLOSS/src/branch/main/docs/superpowers).
|
||||||
|
- **License:** FLOSS by default; MIT for what we build.
|
||||||
55
mkdocs.yml
Normal file
55
mkdocs.yml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
site_name: MakerFLOSS Docs
|
||||||
|
site_url: https://docs.makerfloss.eu/
|
||||||
|
site_description: Documentation for the MakerFLOSS initiative at Orange Makerspace.
|
||||||
|
|
||||||
|
repo_url: https://forgejo.makerfloss.eu/sjat/MakerFLOSS
|
||||||
|
repo_name: sjat/MakerFLOSS
|
||||||
|
edit_uri: _edit/main/
|
||||||
|
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
features:
|
||||||
|
- navigation.indexes
|
||||||
|
- navigation.sections
|
||||||
|
- navigation.top
|
||||||
|
- content.code.copy
|
||||||
|
- search.suggest
|
||||||
|
- search.highlight
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: deep orange
|
||||||
|
toggle:
|
||||||
|
icon: material/weather-night
|
||||||
|
name: Switch to dark mode
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: deep orange
|
||||||
|
toggle:
|
||||||
|
icon: material/weather-sunny
|
||||||
|
name: Switch to light mode
|
||||||
|
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- tables
|
||||||
|
- attr_list
|
||||||
|
- md_in_html
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.highlight:
|
||||||
|
anchor_linenums: true
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
|
- pymdownx.tasklist:
|
||||||
|
custom_checkbox: true
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- search
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Home: index.md
|
||||||
|
- Hardware:
|
||||||
|
- hardware/index.md
|
||||||
|
- House rules: makerFLOSS_house_rules.md
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mkdocs==1.6.*
|
||||||
|
mkdocs-material==9.5.*
|
||||||
|
pyyaml==6.*
|
||||||
207
scripts/gen_overview.py
Executable file
207
scripts/gen_overview.py
Executable file
|
|
@ -0,0 +1,207 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate a category overview Markdown file from per-item YAML frontmatter.
|
||||||
|
|
||||||
|
Reads `scripts/overview_config.yml`, picks the block named by `--category`,
|
||||||
|
walks `source_dir/*.md` (excluding `output_file`), validates each file's
|
||||||
|
frontmatter, and writes a grouped+sorted table to `output_file`.
|
||||||
|
|
||||||
|
Exits non-zero on any schema violation. Deterministic, offline, stdlib + PyYAML.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
CONFIG_PATH = REPO_ROOT / "scripts" / "overview_config.yml"
|
||||||
|
|
||||||
|
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def parse_frontmatter(path: Path) -> dict | None:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
m = FRONTMATTER_RE.match(text)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(m.group(1))
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
raise SchemaError(f"{path}: invalid YAML frontmatter: {e}") from e
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise SchemaError(f"{path}: frontmatter is not a mapping")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def validate(path: Path, fm: dict, cfg: dict) -> None:
|
||||||
|
for field in cfg["required_fields"]:
|
||||||
|
if field not in fm:
|
||||||
|
raise SchemaError(f"{path}: missing required field '{field}'")
|
||||||
|
for field, allowed in cfg.get("enums", {}).items():
|
||||||
|
if field in fm and fm[field] not in allowed:
|
||||||
|
raise SchemaError(
|
||||||
|
f"{path}: {field}={fm[field]!r} not in {allowed}"
|
||||||
|
)
|
||||||
|
stem = path.stem
|
||||||
|
hostname = fm["hostname"]
|
||||||
|
if stem != hostname:
|
||||||
|
raise SchemaError(
|
||||||
|
f"{path}: filename stem {stem!r} != hostname {hostname!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_cpu(fm: dict) -> str:
|
||||||
|
model = fm.get("cpu", "")
|
||||||
|
cores = fm.get("cpu_cores")
|
||||||
|
threads = fm.get("cpu_threads")
|
||||||
|
suffix = ""
|
||||||
|
if cores and threads and threads != cores:
|
||||||
|
suffix = f" · {cores}c/{threads}t"
|
||||||
|
elif cores:
|
||||||
|
suffix = f" · {cores}c"
|
||||||
|
return (model + suffix).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_ram(fm: dict) -> str:
|
||||||
|
n = fm.get("ram_gb")
|
||||||
|
return f"{n} GB" if isinstance(n, int) else ""
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_storage(fm: dict) -> str:
|
||||||
|
n = fm.get("storage_gb")
|
||||||
|
t = fm.get("storage_type", "").upper() if fm.get("storage_type") else ""
|
||||||
|
if not isinstance(n, int):
|
||||||
|
return t # type alone if no capacity
|
||||||
|
if n >= 1000 and n % 1000 == 0:
|
||||||
|
size = f"{n // 1000} TB"
|
||||||
|
elif n >= 1000:
|
||||||
|
size = f"{n / 1000:.1f} TB"
|
||||||
|
else:
|
||||||
|
size = f"{n} GB"
|
||||||
|
return f"{size} {t}".strip()
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_nic(fm: dict) -> str:
|
||||||
|
g = fm.get("nic_gbps")
|
||||||
|
if g is None:
|
||||||
|
return ""
|
||||||
|
if isinstance(g, float) and not g.is_integer():
|
||||||
|
return f"{g} GbE"
|
||||||
|
return f"{int(g)} GbE"
|
||||||
|
|
||||||
|
|
||||||
|
def cell(fm: dict, col: dict) -> str:
|
||||||
|
kind = col.get("kind")
|
||||||
|
if kind == "hostname-link":
|
||||||
|
h = fm["hostname"]
|
||||||
|
return f"[{h}]({h}.md)"
|
||||||
|
if kind == "cpu":
|
||||||
|
return fmt_cpu(fm)
|
||||||
|
if kind == "ram":
|
||||||
|
return fmt_ram(fm)
|
||||||
|
if kind == "storage":
|
||||||
|
return fmt_storage(fm)
|
||||||
|
if kind == "nic":
|
||||||
|
return fmt_nic(fm)
|
||||||
|
value = fm.get(col["field"], "")
|
||||||
|
return "" if value is None else str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def render(cfg: dict, items: list[dict]) -> str:
|
||||||
|
columns = cfg["columns"]
|
||||||
|
group_by = cfg.get("group_by")
|
||||||
|
sort_by = cfg.get("sort_by", "hostname")
|
||||||
|
group_titles = cfg.get("group_titles", {})
|
||||||
|
|
||||||
|
if group_by:
|
||||||
|
groups: dict[str, list[dict]] = {}
|
||||||
|
for fm in items:
|
||||||
|
groups.setdefault(fm.get(group_by, ""), []).append(fm)
|
||||||
|
ordered = sorted(groups.items())
|
||||||
|
else:
|
||||||
|
ordered = [("", items)]
|
||||||
|
|
||||||
|
lines: list[str] = []
|
||||||
|
lines.append(f"# {cfg['title']}")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(
|
||||||
|
f"_Auto-generated from `{cfg['source_dir']}/*.md` — do not edit by hand. "
|
||||||
|
f"Run `make docs-index` after changing a file._"
|
||||||
|
)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
for group_key, rows in ordered:
|
||||||
|
rows.sort(key=lambda r: r.get(sort_by, ""))
|
||||||
|
if group_by:
|
||||||
|
title = group_titles.get(group_key, group_key.title() + "s")
|
||||||
|
lines.append(f"## {title}")
|
||||||
|
lines.append("")
|
||||||
|
header = "| " + " | ".join(c["header"] for c in columns) + " |"
|
||||||
|
sep = "|" + "|".join("---" for _ in columns) + "|"
|
||||||
|
lines.append(header)
|
||||||
|
lines.append(sep)
|
||||||
|
for fm in rows:
|
||||||
|
lines.append("| " + " | ".join(cell(fm, c) for c in columns) + " |")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||||
|
parser.add_argument("--category", required=True, help="Category key from overview_config.yml")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config_all = yaml.safe_load(CONFIG_PATH.read_text(encoding="utf-8"))
|
||||||
|
if args.category not in config_all:
|
||||||
|
print(f"ERROR: category {args.category!r} not in {CONFIG_PATH}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
cfg = config_all[args.category]
|
||||||
|
|
||||||
|
source_dir = REPO_ROOT / cfg["source_dir"]
|
||||||
|
output_file = REPO_ROOT / cfg["output_file"]
|
||||||
|
output_abs = output_file.resolve()
|
||||||
|
|
||||||
|
items: list[dict] = []
|
||||||
|
errors: list[str] = []
|
||||||
|
for path in sorted(source_dir.glob("*.md")):
|
||||||
|
if path.resolve() == output_abs:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
fm = parse_frontmatter(path)
|
||||||
|
except SchemaError as e:
|
||||||
|
errors.append(str(e))
|
||||||
|
continue
|
||||||
|
if fm is None:
|
||||||
|
print(f"WARNING: {path}: no YAML frontmatter, skipping", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
validate(path, fm, cfg)
|
||||||
|
except SchemaError as e:
|
||||||
|
errors.append(str(e))
|
||||||
|
continue
|
||||||
|
items.append(fm)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
for err in errors:
|
||||||
|
print(f"ERROR: {err}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
tmp = output_file.with_suffix(output_file.suffix + ".tmp")
|
||||||
|
tmp.write_text(render(cfg, items), encoding="utf-8")
|
||||||
|
tmp.replace(output_file)
|
||||||
|
print(f"Wrote {output_file.relative_to(REPO_ROOT)} ({len(items)} item(s))")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
38
scripts/overview_config.yml
Normal file
38
scripts/overview_config.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Configuration for scripts/gen_overview.py
|
||||||
|
#
|
||||||
|
# Each top-level key is a category. The generator is invoked with
|
||||||
|
# --category <name> and looks up its block here. To add a new category
|
||||||
|
# (services, vms, ...) later, copy a block and adjust the fields.
|
||||||
|
|
||||||
|
hardware:
|
||||||
|
title: "Hardware Overview"
|
||||||
|
source_dir: docs/hardware
|
||||||
|
output_file: docs/hardware/index.md
|
||||||
|
required_fields:
|
||||||
|
- hostname
|
||||||
|
- kind
|
||||||
|
- status
|
||||||
|
enums:
|
||||||
|
kind: [server, laptop, sbc, switch, ap, desktop]
|
||||||
|
status: [in-use, spare, broken, donated]
|
||||||
|
storage_type: [nvme, ssd, hdd, mixed]
|
||||||
|
group_by: kind
|
||||||
|
# Human-friendly H2 names per group_by value. Anything missing falls back
|
||||||
|
# to the raw value title-cased + "s".
|
||||||
|
group_titles:
|
||||||
|
server: Servers
|
||||||
|
laptop: Laptops
|
||||||
|
sbc: Single-board computers
|
||||||
|
switch: Switches
|
||||||
|
ap: Access points
|
||||||
|
desktop: Desktops
|
||||||
|
sort_by: hostname
|
||||||
|
columns:
|
||||||
|
- { header: Hostname, kind: hostname-link }
|
||||||
|
- { header: Model, field: model }
|
||||||
|
- { header: Location, field: location }
|
||||||
|
- { header: CPU, kind: cpu }
|
||||||
|
- { header: RAM, kind: ram }
|
||||||
|
- { header: Storage, kind: storage }
|
||||||
|
- { header: NIC, kind: nic }
|
||||||
|
- { header: Status, field: status }
|
||||||
Loading…
Add table
Reference in a new issue