feat(docs): add services category alongside hardware
All checks were successful
Build docs site / build (push) Successful in 32s
Build slides / build (push) Successful in 52s

Mirror the auto-indexed per-host pattern for a new docs/services/
category, seeded with the six things currently deployed on or around
makerfloss.eu: docs, slides, forgejo, gandi-dns, marp, mermaid.

Generator/hook generalisation:
- scripts/gen_overview.py: replace the hardcoded `hostname` check
  with a configurable `key_field` (default: hostname). Add a generic
  `key-link` column kind (replaces the old `hostname-link`) and a
  `url-link` kind that renders the value as a clickable link.
- scripts/overview_config.yml: declare hardware's key_field, then add
  a `services` block (key_field=name, its own kind/status enums,
  grouped by kind for the index table).
- scripts/mkdocs_hooks.py: route by `page.file.src_uri` so each
  hardware/* page gets a "Specs" table and each services/* page gets
  a "Service" table; both share the helpers in gen_overview.

Wiring:
- Makefile: docs-index and docs-check now regenerate and drift-check
  both indices.
- .forgejo/workflows/docs.yml: same on the CI runner.
- mkdocs.yml: add Services to nav.
- README.md, CLAUDE.md: list services/ in the repo-layout block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-05-18 17:48:15 +02:00
parent 96d2efc75c
commit c743416ded
15 changed files with 212 additions and 29 deletions

View file

@ -26,14 +26,16 @@ jobs:
- 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
- name: Regenerate hardware and services indices
run: |
if ! git diff --exit-code docs/hardware/index.md; then
python3 scripts/gen_overview.py --category hardware
python3 scripts/gen_overview.py --category services
- name: Fail on drift in generated indices
run: |
if ! git diff --exit-code docs/hardware/index.md docs/services/index.md; then
echo
echo "::error::docs/hardware/index.md is stale."
echo "::error::A generated index is stale."
echo "Regenerate locally via 'make docs-index' and commit the result."
exit 1
fi

View file

@ -24,6 +24,7 @@ From `notes/todo/2026-04-14-todo.md`:
```
docs/ # everything here is built and shipped to docs.makerfloss.eu
hardware/ # auto-indexed per-host frontmatter (mf00..mf03, makerfloss.eu)
services/ # auto-indexed per-service frontmatter (docs, forgejo, …)
infrastructure/ # labdesign, VPS/DNS, etc.
presentations/ # Marp decks (build-slides.sh)
notes/ # repo-only working material, not built

View file

@ -2,14 +2,15 @@
help:
@echo "Targets:"
@echo " docs-index Regenerate docs/hardware/index.md from per-host frontmatter"
@echo " docs-index Regenerate docs/{hardware,services}/index.md from per-item 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 " docs-check Drift-check: regenerate indices, fail if they differ from the committed copies"
@echo " slides Run build-slides.sh (Marp slides)"
docs-index:
python3 scripts/gen_overview.py --category hardware
python3 scripts/gen_overview.py --category services
docs-build:
mkdocs build --strict
@ -19,7 +20,8 @@ docs-serve:
docs-check:
python3 scripts/gen_overview.py --category hardware
git diff --exit-code docs/hardware/index.md
python3 scripts/gen_overview.py --category services
git diff --exit-code docs/hardware/index.md docs/services/index.md
slides:
./build-slides.sh

View file

@ -11,6 +11,7 @@ Documentation and working notes for **MakerFLOSS**, an Orange Makerspace initiat
```
docs/ # built into the public site (docs.makerfloss.eu)
hardware/ # auto-indexed per-host frontmatter (mf00..mf03, makerfloss.eu)
services/ # auto-indexed per-service frontmatter (docs, forgejo, …)
infrastructure/ # labdesign, VPS/DNS, etc.
presentations/ # Marp decks (also published to slides.makerfloss.eu)
notes/ # repo-only working material — not part of the site

14
docs/services/docs.md Normal file
View file

@ -0,0 +1,14 @@
---
name: docs
kind: static-site
status: in-use
host: makerfloss.eu
url: https://docs.makerfloss.eu
upstream: https://www.mkdocs.org
tech: MkDocs Material
tls: letsencrypt
---
## Notes
Public-facing documentation site. Built from this repo's `docs/` tree on every push to `main` by the Forgejo Actions runner (`.forgejo/workflows/docs.yml`), then rsynced to the VPS and served by nginx behind Traefik.

14
docs/services/forgejo.md Normal file
View file

@ -0,0 +1,14 @@
---
name: forgejo
kind: web-app
status: in-use
host: makerfloss.eu
url: https://forgejo.makerfloss.eu
upstream: https://codeberg.org/forgejo/forgejo
tech: Go
tls: letsencrypt
---
## Notes
Self-hosted git forge. Hosts this repository and the AnsibleBaobabV4 project. SSH access on port 7577 (`ssh://git@forgejo.makerfloss.eu:7577/<user>/<repo>.git`). Also runs the Forgejo Actions runner that builds [docs](docs.md) and [slides](slides.md) on push.

View file

@ -0,0 +1,12 @@
---
name: gandi-dns
kind: dns
status: in-use
host: Gandi.net (external)
url: https://www.gandi.net
tech: Gandi LiveDNS API
---
## Notes
External DNS provider for the `makerfloss.eu` zone. Records are managed declaratively from the [AnsibleBaobabV4](https://forgejo.nyumbani.baobab.band/sjat/AnsibleBaobabV4) project (`play_dns.yml --limit makerfloss`); **never edit DNS records directly in the Gandi web UI** — Ansible will overwrite them on the next run.

34
docs/services/index.md Normal file
View file

@ -0,0 +1,34 @@
# Services Overview
_Auto-generated from `docs/services/*.md` — do not edit by hand. Run `make docs-index` after changing a file._
## DNS
| Name | URL | Host | Tech | Status |
|---|---|---|---|---|
| [gandi-dns](gandi-dns.md) | [www.gandi.net](https://www.gandi.net) | Gandi.net (external) | Gandi LiveDNS API | in-use |
## Libraries
| Name | URL | Host | Tech | Status |
|---|---|---|---|---|
| [mermaid](mermaid.md) | [cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs](https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs) | CDN (jsdelivr) | JavaScript (ESM) | in-use |
## Slide builders
| Name | URL | Host | Tech | Status |
|---|---|---|---|---|
| [marp](marp.md) | | CI runner (Forgejo Actions) | Node.js (marp-cli) / Docker | in-use |
## Static sites
| Name | URL | Host | Tech | Status |
|---|---|---|---|---|
| [docs](docs.md) | [docs.makerfloss.eu](https://docs.makerfloss.eu) | makerfloss.eu | MkDocs Material | in-use |
| [slides](slides.md) | [slides.makerfloss.eu](https://slides.makerfloss.eu) | makerfloss.eu | Marp + Mermaid.js | in-use |
## Web applications
| Name | URL | Host | Tech | Status |
|---|---|---|---|---|
| [forgejo](forgejo.md) | [forgejo.makerfloss.eu](https://forgejo.makerfloss.eu) | makerfloss.eu | Go | in-use |

12
docs/services/marp.md Normal file
View file

@ -0,0 +1,12 @@
---
name: marp
kind: slide-builder
status: in-use
host: CI runner (Forgejo Actions)
upstream: https://marp.app
tech: Node.js (marp-cli) / Docker
---
## Notes
Markdown-to-HTML slide compiler. Invoked by `build-slides.sh` against every `docs/presentations/*.md` (and any other `.md` with `marp: true` in frontmatter), either via a local `marp` binary or the `marpteam/marp-cli` Docker image. Output feeds the [slides](slides.md) site. See [`notes/dev/marp-mermaid-setup.md`](https://forgejo.makerfloss.eu/sjat/MakerFLOSS/src/branch/main/notes/dev/marp-mermaid-setup.md) for the [mermaid](mermaid.md) integration design.

13
docs/services/mermaid.md Normal file
View file

@ -0,0 +1,13 @@
---
name: mermaid
kind: library
status: in-use
host: CDN (jsdelivr)
url: https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs
upstream: https://mermaid.js.org
tech: JavaScript (ESM)
---
## Notes
Diagram-rendering JS library, injected into built slide HTML by the `inject_mermaid` step of `build-slides.sh`. Each `<pre><code class="language-mermaid">…</code></pre>` block emitted by [marp](marp.md) is rewritten into `<div class="mermaid">`, then `mermaid.run()` renders it client-side on page load.

14
docs/services/slides.md Normal file
View file

@ -0,0 +1,14 @@
---
name: slides
kind: static-site
status: in-use
host: makerfloss.eu
url: https://slides.makerfloss.eu
upstream: https://marp.app
tech: Marp + Mermaid.js
tls: letsencrypt
---
## Notes
Slide-deck site. Decks are authored as Marp markdown in `docs/presentations/` and compiled to HTML by `build-slides.sh` (CI invokes it via the [marp](marp.md) toolchain). Built output is rsynced to the VPS and served alongside [docs](docs.md).

View file

@ -55,4 +55,6 @@ nav:
- Home: index.md
- Hardware:
- hardware/index.md
- Services:
- services/index.md
- House rules: house-rules.md

View file

@ -50,11 +50,16 @@ def validate(path: Path, fm: dict, cfg: dict) -> None:
raise SchemaError(
f"{path}: {field}={fm[field]!r} not in {allowed}"
)
stem = path.stem
hostname = fm["hostname"]
if stem != hostname:
key_field = cfg.get("key_field", "hostname")
if key_field not in fm:
raise SchemaError(
f"{path}: filename stem {stem!r} != hostname {hostname!r}"
f"{path}: missing key field {key_field!r}"
)
stem = path.stem
value = fm[key_field]
if stem != value:
raise SchemaError(
f"{path}: filename stem {stem!r} != {key_field} {value!r}"
)
@ -122,9 +127,15 @@ def fmt_nic(fm: dict) -> str:
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 == "key-link":
v = fm[col["field"]]
return f"[{v}]({v}.md)"
if kind == "url-link":
u = fm.get(col["field"], "")
if not u:
return ""
label = u.removeprefix("https://").removeprefix("http://")
return f"[{label}]({u})"
if kind == "cpu":
return fmt_cpu(fm)
if kind == "ram":

View file

@ -1,4 +1,4 @@
"""MkDocs build hook: render a Specs section on each hardware host page from its YAML frontmatter."""
"""MkDocs build hook: render a Specs section on each hardware or service page from its YAML frontmatter."""
from __future__ import annotations
import sys
@ -8,7 +8,16 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
from gen_overview import fmt_cpu, fmt_nic, fmt_ram, fmt_storage # noqa: E402
def _render_specs(meta: dict) -> str:
def _table(rows: list[tuple[str, str]]) -> str:
lines = ["| Field | Value |", "|---|---|"]
for label, value in rows:
if not value:
continue
lines.append(f"| {label} | {value} |")
return "\n".join(lines)
def _hardware_page(meta: dict, body: str) -> str:
rows = [
("Model", meta.get("model")),
("Location", meta.get("location")),
@ -18,17 +27,29 @@ def _render_specs(meta: dict) -> str:
("NIC", fmt_nic(meta)),
("Status", meta.get("status")),
]
lines = ["| Field | Value |", "|---|---|"]
for label, value in rows:
if not value:
continue
lines.append(f"| {label} | {value} |")
return "\n".join(lines)
return f"# {meta['hostname']}\n\n## Specs\n\n{_table(rows)}\n\n{body}"
def _service_page(meta: dict, body: str) -> str:
url = meta.get("url")
upstream = meta.get("upstream")
rows = [
("Kind", meta.get("kind")),
("Host", meta.get("host")),
("URL", f"[{url}]({url})" if url else ""),
("Tech", meta.get("tech")),
("Upstream", f"[{upstream}]({upstream})" if upstream else ""),
("TLS", meta.get("tls")),
("Status", meta.get("status")),
]
return f"# {meta['name']}\n\n## Service\n\n{_table(rows)}\n\n{body}"
def on_page_markdown(markdown, page, config, files): # noqa: ARG001
meta = page.meta or {}
hostname = meta.get("hostname")
if not hostname:
src = (page.file.src_uri or page.file.src_path or "").replace("\\", "/")
if src.startswith("hardware/") and meta.get("hostname"):
return _hardware_page(meta, markdown)
if src.startswith("services/") and meta.get("name"):
return _service_page(meta, markdown)
return markdown
return f"# {hostname}\n\n## Specs\n\n{_render_specs(meta)}\n\n{markdown}"

View file

@ -8,6 +8,7 @@ hardware:
title: "Hardware Overview"
source_dir: docs/hardware
output_file: docs/hardware/index.md
key_field: hostname
required_fields:
- hostname
- kind
@ -28,7 +29,7 @@ hardware:
desktop: Desktops
sort_by: hostname
columns:
- { header: Hostname, kind: hostname-link }
- { header: Hostname, kind: key-link, field: hostname }
- { header: Model, field: model }
- { header: Location, field: location }
- { header: CPU, kind: cpu }
@ -36,3 +37,32 @@ hardware:
- { header: Storage, kind: storage }
- { header: NIC, kind: nic }
- { header: Status, field: status }
services:
title: "Services Overview"
source_dir: docs/services
output_file: docs/services/index.md
key_field: name
required_fields:
- name
- kind
- status
enums:
kind: [web-app, static-site, dns, slide-builder, library, reverse-proxy, mail]
status: [in-use, staging, planned, broken, decommissioned]
group_by: kind
group_titles:
web-app: Web applications
static-site: Static sites
dns: DNS
slide-builder: Slide builders
library: Libraries
reverse-proxy: Reverse proxies
mail: Mail
sort_by: name
columns:
- { header: Name, kind: key-link, field: name }
- { header: URL, kind: url-link, field: url }
- { header: Host, field: host }
- { header: Tech, field: tech }
- { header: Status, field: status }