Documentation Infra

Documentation Infrastructure

How the dotfiles docs are published as a website, PDF, and GitHub Releases.

Architecture Overview

Three repos, three concerns:

dotfiles_dev (docs/ source-of-truth)

    ├── git submodule ──► alienzj.github.io (Astro site)
    │                         └── builds /dotfiles/* HTML pages
    │                         └── deploys to alienzj.org/dotfiles

    ├── .github/workflows/release.yml
    │   └── on v* tag: pandoc → PDF, then create GitHub Release

    └── scripts/build-docs-pdf.sh
        └── Concatenates ordered docs → pandoc + typst → PDF

Design principle: Docs live in one place (docs/ in the dotfiles repo). The website and PDF are derivative outputs — the website pulls via git submodule, PDF is built in CI during release.


1. Website (alienzj.org/dotfiles)

Design

The dotfiles docs are rendered as a section of the user’s existing Astro v6 personal site. This avoids a separate site, shares the theme/header/footer, and reuses the existing GitHub Pages deploy pipeline.

Stack:

  • Astro v6 with MDX support
  • Shiki for syntax highlighting (Nix code blocks get github-dark/github-light theme)
  • Git submodule pins the dotfiles repo at src/content/dotfiles/
  • Astro content collections (glob loader) scan docs/**/*.md
  • Static generation: all pages pre-rendered to HTML at build time

Key Files

FilePurpose
src/content/dotfiles/Git submodule → dotfiles repo
src/content.config.tsdotfiles collection definition (glob: docs/**/*.md)
src/lib/doc-categories.tsMaps each doc slug to a section + display title
src/layouts/DocLayout.astro3-column layout: sticky sidebar nav + content + page TOC
src/pages/dotfiles/index.astroListing page with docs grouped by section
src/pages/dotfiles/[...slug].astroDynamic route for individual doc pages
src/components/Header.astroNav bar with “Dotfiles” link
.github/workflows/deploy.ymlAstro deploy to GitHub Pages (submodules: recursive)

Doc Categories (Sidebar Sections)

Docs are organized into 10 sections defined in src/lib/doc-categories.ts:

  • Architecture — nix-expressions, packages, toolchain
  • Desktop — desktop, themes, software, keybindings, tmux
  • Editors — editors, neovim, ai-language-idioms, ai-parallel-workflow
  • Security — security, security-hardening, security-auth-logic, git-signing
  • Networking — networking-vpn, networking-proxy, web-services
  • Services — sso-identity, containers-virt, systemd-services
  • Storage & Hardware — storage-disko, hardware, power-management
  • Data & AI — data-science, ai-ml
  • Hosts — auto-collected from docs/hosts/*.md
  • Operations — ops, workflow, refactor-plan, sbc-opi5p

Hosts are auto-detected: any file under docs/hosts/ gets the section “Hosts” and a title derived from the filename.


2. PDF Generation

Design

PDF is generated only during releases (not on every push) to avoid bloating CI. The pipeline:

  1. Concatenate all docs in a curated order (README → CLAUDE → Architecture → Desktop → … → Operations)
  2. Prepend a YAML metadata block for pandoc (title, author, TOC settings)
  3. Run pandoc --pdf-engine=typst with a custom template (scripts/pandoc-template.typ) to produce the final PDF

Why Pandoc + Typst:

  • Pandoc is the universal converter (markdown → any format), Typst is the modern typesetting engine
  • Typst is a single ~30MB binary (vs ~300MB+ for texlive-xetex), installs instantly in CI
  • Native Unicode support — CJK, emoji, and Nix code render correctly without extra font config
  • ~27× faster compilation than XeLaTeX
  • Much better error messages — points to exact source location instead of cryptic LaTeX traces
  • Backslash-heavy Nix code doesn’t leak into the typesetting layer (no LaTeX escape issues)

Key Files

FilePurpose
scripts/build-docs-pdf.shLocal/CI PDF build script
scripts/pandoc-template.typCustom Typst template (font fallback, TOC, helpers)
.github/workflows/release.ymlCI workflow: runs script, attaches PDF to release

Doc Order (in build-docs-pdf.sh)

The order array defines the concatenation sequence. New docs should be inserted in their logical position. The order mirrors the sidebar sections:

  1. README, CLAUDE
  2. Architecture docs
  3. Desktop docs
  4. Editors docs
  5. Security docs
  6. Networking docs
  7. Services docs
  8. Storage & Hardware docs
  9. Data & AI docs
  10. Hosts (docs/hosts/* — globbed automatically)
  11. Operations docs

PDF Features

  • Title page with author
  • Auto-generated table of contents (3 levels deep)
  • Section numbering
  • 11pt DejaVu Serif / DejaVu Sans Mono with CJK fallback

3. GitHub Releases

Design

Every v* tag pushed to the dotfiles repo triggers a Release workflow:

# .github/workflows/release.yml
on:
  push:
    tags: ['v*']
  workflow_dispatch:  # manual trigger, supports draft mode

The workflow:

  1. Installs pandoc + typst via nix shell nixpkgs#pandoc nixpkgs#typst
  2. Runs scripts/build-docs-pdf.sh → produces dotfiles-docs.pdf
  3. Creates a GitHub Release via softprops/action-gh-release@v2
  4. Attaches the PDF to the release
  5. Auto-generates release notes from merged PRs

When to Tag

  • After a significant refactor or feature addition
  • Before a risky change (as a snapshot)
  • After shipping user-facing documentation changes
  • Versioning: semver-ish (v0.1.0, v0.2.0). Major zero = pre-1.0 config.

4. Literate Nix Config

Current Approach (v1)

The /dotfiles site renders the existing markdown docs. It is a documentation site, not a literate config site. The .nix source files are not directly rendered.

Future: True Literate Config

The goal is to render .nix files with their ##-style comments extracted as prose, alongside syntax-highlighted code blocks. This requires a custom Astro content loader that:

  1. Reads .nix files from the dotfiles submodule
  2. Parses ## comment blocks as markdown
  3. Renders remaining code as fenced code blocks with language=nix
  4. Generates slugged pages at /dotfiles/config/<path>

The same pipeline (submodule → Astro build → static HTML) stays intact; only the content loader changes.


Manual Operations

Update the Dotfiles Submodule (after new docs)

When you add or update docs in the dotfiles repo, update the submodule pin in the Astro site:

cd ~/toolkits/ohblog/alienzj.github.io
cd src/content/dotfiles
git pull origin dev
cd ..
git add src/content/dotfiles
git commit -m "chore: bump dotfiles submodule for updated docs"
git push

The push triggers the Astro deploy workflow → alienzj.org/dotfiles updates automatically.

Create a Release with PDF

cd ~/toolkits/ohlinux/nixos/dotfiles_dev
git tag v0.2.0
git push --tags

This triggers .github/workflows/release.yml which:

  • Generates dotfiles-docs.pdf
  • Creates a GitHub Release at https://github.com/alienzj/dotfiles/releases/tag/v0.2.0

Generate PDF Locally

cd ~/toolkits/ohlinux/nixos/dotfiles_dev
# Requires: pandoc >= 3.0, typst
bash scripts/build-docs-pdf.sh output.pdf

Add a New Doc

  1. Write the .md file in docs/ (or docs/hosts/)
  2. Add an entry in src/lib/doc-categories.ts in the Astro site (unless it’s a host doc)
  3. Add the doc to the order array in scripts/build-docs-pdf.sh
  4. Update the submodule and deploy (see above)

Add a New Section

  1. Add entries to src/lib/doc-categories.ts
  2. Add the section name to the sectionOrder array in the same file
  3. Add the docs to the order array in scripts/build-docs-pdf.sh

CI Reference

Dotfiles repo workflows

WorkflowTriggerWhat it does
ci.ymlPush to dev/master, PRSyntax check (hey check syntax)
release.ymlTag v*, manualPDF build + GitHub Release + attach PDF

Astro site workflow

WorkflowTriggerWhat it does
deploy.ymlPush to mainCheckout (with submodules) → Astro build → GitHub Pages deploy

Updating CLAUDE.md / Memory

When adding or changing documentation infrastructure, update:

  1. This file (docs/documentation-infra.md) — workflow changes, new sections
  2. src/lib/doc-categories.ts — new docs or renamed docs
  3. scripts/build-docs-pdf.sh — new docs in the PDF order
  4. CLAUDE.md — if architecture or agent-relevant rules change