feat(docs): add services category alongside hardware
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:
parent
96d2efc75c
commit
c743416ded
15 changed files with 212 additions and 29 deletions
|
|
@ -26,14 +26,16 @@ jobs:
|
||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
run: pip install --quiet -r requirements.txt
|
run: pip install --quiet -r requirements.txt
|
||||||
|
|
||||||
- name: Regenerate hardware index
|
- name: Regenerate hardware and services indices
|
||||||
run: python3 scripts/gen_overview.py --category hardware
|
|
||||||
|
|
||||||
- name: Fail on drift in docs/hardware/index.md
|
|
||||||
run: |
|
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
|
||||||
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."
|
echo "Regenerate locally via 'make docs-index' and commit the result."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ From `notes/todo/2026-04-14-todo.md`:
|
||||||
```
|
```
|
||||||
docs/ # everything here is built and shipped to docs.makerfloss.eu
|
docs/ # everything here is built and shipped to docs.makerfloss.eu
|
||||||
hardware/ # auto-indexed per-host frontmatter (mf00..mf03, 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.
|
infrastructure/ # labdesign, VPS/DNS, etc.
|
||||||
presentations/ # Marp decks (build-slides.sh)
|
presentations/ # Marp decks (build-slides.sh)
|
||||||
notes/ # repo-only working material, not built
|
notes/ # repo-only working material, not built
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -2,14 +2,15 @@
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Targets:"
|
@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-build Build the static MkDocs site into ./site (strict)"
|
||||||
@echo " docs-serve Run a live-reload local preview server"
|
@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)"
|
@echo " slides Run build-slides.sh (Marp slides)"
|
||||||
|
|
||||||
docs-index:
|
docs-index:
|
||||||
python3 scripts/gen_overview.py --category hardware
|
python3 scripts/gen_overview.py --category hardware
|
||||||
|
python3 scripts/gen_overview.py --category services
|
||||||
|
|
||||||
docs-build:
|
docs-build:
|
||||||
mkdocs build --strict
|
mkdocs build --strict
|
||||||
|
|
@ -19,7 +20,8 @@ docs-serve:
|
||||||
|
|
||||||
docs-check:
|
docs-check:
|
||||||
python3 scripts/gen_overview.py --category hardware
|
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:
|
slides:
|
||||||
./build-slides.sh
|
./build-slides.sh
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ Documentation and working notes for **MakerFLOSS**, an Orange Makerspace initiat
|
||||||
```
|
```
|
||||||
docs/ # built into the public site (docs.makerfloss.eu)
|
docs/ # built into the public site (docs.makerfloss.eu)
|
||||||
hardware/ # auto-indexed per-host frontmatter (mf00..mf03, 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.
|
infrastructure/ # labdesign, VPS/DNS, etc.
|
||||||
presentations/ # Marp decks (also published to slides.makerfloss.eu)
|
presentations/ # Marp decks (also published to slides.makerfloss.eu)
|
||||||
notes/ # repo-only working material — not part of the site
|
notes/ # repo-only working material — not part of the site
|
||||||
|
|
|
||||||
14
docs/services/docs.md
Normal file
14
docs/services/docs.md
Normal 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
14
docs/services/forgejo.md
Normal 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.
|
||||||
12
docs/services/gandi-dns.md
Normal file
12
docs/services/gandi-dns.md
Normal 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
34
docs/services/index.md
Normal 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
12
docs/services/marp.md
Normal 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
13
docs/services/mermaid.md
Normal 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
14
docs/services/slides.md
Normal 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).
|
||||||
|
|
@ -55,4 +55,6 @@ nav:
|
||||||
- Home: index.md
|
- Home: index.md
|
||||||
- Hardware:
|
- Hardware:
|
||||||
- hardware/index.md
|
- hardware/index.md
|
||||||
|
- Services:
|
||||||
|
- services/index.md
|
||||||
- House rules: house-rules.md
|
- House rules: house-rules.md
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,16 @@ def validate(path: Path, fm: dict, cfg: dict) -> None:
|
||||||
raise SchemaError(
|
raise SchemaError(
|
||||||
f"{path}: {field}={fm[field]!r} not in {allowed}"
|
f"{path}: {field}={fm[field]!r} not in {allowed}"
|
||||||
)
|
)
|
||||||
stem = path.stem
|
key_field = cfg.get("key_field", "hostname")
|
||||||
hostname = fm["hostname"]
|
if key_field not in fm:
|
||||||
if stem != hostname:
|
|
||||||
raise SchemaError(
|
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:
|
def cell(fm: dict, col: dict) -> str:
|
||||||
kind = col.get("kind")
|
kind = col.get("kind")
|
||||||
if kind == "hostname-link":
|
if kind == "key-link":
|
||||||
h = fm["hostname"]
|
v = fm[col["field"]]
|
||||||
return f"[{h}]({h}.md)"
|
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":
|
if kind == "cpu":
|
||||||
return fmt_cpu(fm)
|
return fmt_cpu(fm)
|
||||||
if kind == "ram":
|
if kind == "ram":
|
||||||
|
|
|
||||||
|
|
@ -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
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
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
|
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 = [
|
rows = [
|
||||||
("Model", meta.get("model")),
|
("Model", meta.get("model")),
|
||||||
("Location", meta.get("location")),
|
("Location", meta.get("location")),
|
||||||
|
|
@ -18,17 +27,29 @@ def _render_specs(meta: dict) -> str:
|
||||||
("NIC", fmt_nic(meta)),
|
("NIC", fmt_nic(meta)),
|
||||||
("Status", meta.get("status")),
|
("Status", meta.get("status")),
|
||||||
]
|
]
|
||||||
lines = ["| Field | Value |", "|---|---|"]
|
return f"# {meta['hostname']}\n\n## Specs\n\n{_table(rows)}\n\n{body}"
|
||||||
for label, value in rows:
|
|
||||||
if not value:
|
|
||||||
continue
|
def _service_page(meta: dict, body: str) -> str:
|
||||||
lines.append(f"| {label} | {value} |")
|
url = meta.get("url")
|
||||||
return "\n".join(lines)
|
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
|
def on_page_markdown(markdown, page, config, files): # noqa: ARG001
|
||||||
meta = page.meta or {}
|
meta = page.meta or {}
|
||||||
hostname = meta.get("hostname")
|
src = (page.file.src_uri or page.file.src_path or "").replace("\\", "/")
|
||||||
if not hostname:
|
if src.startswith("hardware/") and meta.get("hostname"):
|
||||||
return markdown
|
return _hardware_page(meta, markdown)
|
||||||
return f"# {hostname}\n\n## Specs\n\n{_render_specs(meta)}\n\n{markdown}"
|
if src.startswith("services/") and meta.get("name"):
|
||||||
|
return _service_page(meta, markdown)
|
||||||
|
return markdown
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ hardware:
|
||||||
title: "Hardware Overview"
|
title: "Hardware Overview"
|
||||||
source_dir: docs/hardware
|
source_dir: docs/hardware
|
||||||
output_file: docs/hardware/index.md
|
output_file: docs/hardware/index.md
|
||||||
|
key_field: hostname
|
||||||
required_fields:
|
required_fields:
|
||||||
- hostname
|
- hostname
|
||||||
- kind
|
- kind
|
||||||
|
|
@ -28,7 +29,7 @@ hardware:
|
||||||
desktop: Desktops
|
desktop: Desktops
|
||||||
sort_by: hostname
|
sort_by: hostname
|
||||||
columns:
|
columns:
|
||||||
- { header: Hostname, kind: hostname-link }
|
- { header: Hostname, kind: key-link, field: hostname }
|
||||||
- { header: Model, field: model }
|
- { header: Model, field: model }
|
||||||
- { header: Location, field: location }
|
- { header: Location, field: location }
|
||||||
- { header: CPU, kind: cpu }
|
- { header: CPU, kind: cpu }
|
||||||
|
|
@ -36,3 +37,32 @@ hardware:
|
||||||
- { header: Storage, kind: storage }
|
- { header: Storage, kind: storage }
|
||||||
- { header: NIC, kind: nic }
|
- { header: NIC, kind: nic }
|
||||||
- { header: Status, field: status }
|
- { 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 }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue