Documentation System¶
The project documentation (this site) is built with Zensical and published to GitHub Pages by a GitHub Actions workflow.
Deliberately not hosted on the cluster. Documentation that goes down with the thing it documents is documentation you cannot read at the exact moment you need it, which is a mistake people make once.
Workflow¶
- Authoring: Documentation is written in Markdown within
docs/. The site is configured inzensical.tomlat the repository root, andoverrides/holds the handful of theme templates this project replaces. - Build: On push to
main, thedocs.yamlworkflow runszensical build --clean --strict, producing a static site insite/. - Deploy: The workflow drops a
.nojekyllmarker intosite/and pushes it to thegh-pagesbranch, which GitHub Pages serves at https://janwelker.github.io/homelab/. Without the marker Pages runs the branch through Jekyll, which silently drops any path Jekyll considers private. The deploy cleans the branch but excludespr-preview/, so it leaves the open pull-request previews in place.
No container image, registry, or cluster is involved — the docs stay available independently of the homelab. Someone else's uptime problem, for once.
sequenceDiagram
participant Dev as Developer
participant Git as GitHub Repo
participant GA as GitHub Actions
participant Pages as GitHub Pages
Dev->>Git: Push Changes (docs/**, zensical.toml)
Git->>GA: Trigger "Publish Documentation"
GA->>GA: zensical build --clean --strict
GA->>Pages: Push site/ to gh-pages
Pages-->>Dev: janwelker.github.io/homelab
Pull request previews¶
preview.yaml builds every pull request that touches docs/, overrides/ or
zensical.toml and publishes it under pr-preview/ on the same gh-pages
branch; closing the PR removes it. Its path filter has to match docs.yaml,
because this is the only --strict build a pull request gets: a nav or theme
change in zensical.toml breaks the build as readily as a broken link, and
without that path it would reach main unbuilt. The same build works from the
preview subdirectory because the theme resolves its links relative to the page.
PRs from forks are skipped. They run without write access, so the deploy would only fail.
Working locally¶
Zensical is declared in the dev dependency group of pyproject.toml:
uv sync --dev
uv run zensical serve # http://localhost:8000, rebuilds on save
uv run zensical build # one-off build into site/
site/ is gitignored.
Conventions¶
- Every page must be reachable from the
navinzensical.toml; the build runs with--strict, so broken links and orphaned pages fail CI. Strict mode is unforgiving and that is the point — rotten cross-references are how a docs site stops being trusted. - Cross-references use relative Markdown paths (for example
platform/openbao.md,../quickstart.md) so they resolve both on the site and when browsing the repository on GitHub. - Diagrams use Mermaid fences; Zensical initialises the runtime automatically on pages that contain one.
- Markdown is linted by
lint-markdown.yamland themarkdownlint-cli2pre-commit hook. Note thatmarkdownlintreads a blank line followed by an indented one as a code block, so a multi-paragraph!!!admonition tripsMD046. Keep admonition bodies to a single paragraph. This one will catch you, probably today. - Inline HTML is limited to
<div>(MD033in.markdownlint-cli2.yaml): the card grids on the hub pages need a wrapping<div class="grid cards">, and the theme has no Markdown syntax for them. - Theme templates are overridden by dropping a same-named file under
overrides/(wired up viatheme.custom_dir). Currently onlypartials/source.html, which drops the repository-facts API call that 404s because this repository publishes no releases. - Fonts are self-hosted, so the published site makes no third-party requests.
theme.font = falseinzensical.tomlsuppresses the theme's Google Fonts<link>, anddocs/stylesheets/fonts.cssdeclares Inter and JetBrains Mono from thewoff2files indocs/assets/fonts/. Those come from the upstream releases (Inter and JetBrains Mono); the SIL OFL licence of each sits next to them.scripts/update-fonts.shpins both versions and downloads them (make fonts), and Renovate opens a PR on the pins — see Maintenance for why that PR needs a second commit.