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-lighttheme) - Git submodule pins the dotfiles repo at
src/content/dotfiles/ - Astro content collections (
globloader) scandocs/**/*.md - Static generation: all pages pre-rendered to HTML at build time
Key Files
| File | Purpose |
|---|---|
src/content/dotfiles/ | Git submodule → dotfiles repo |
src/content.config.ts | dotfiles collection definition (glob: docs/**/*.md) |
src/lib/doc-categories.ts | Maps each doc slug to a section + display title |
src/layouts/DocLayout.astro | 3-column layout: sticky sidebar nav + content + page TOC |
src/pages/dotfiles/index.astro | Listing page with docs grouped by section |
src/pages/dotfiles/[...slug].astro | Dynamic route for individual doc pages |
src/components/Header.astro | Nav bar with “Dotfiles” link |
.github/workflows/deploy.yml | Astro 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:
- Concatenate all docs in a curated order (README → CLAUDE → Architecture → Desktop → … → Operations)
- Prepend a YAML metadata block for pandoc (title, author, TOC settings)
- Run
pandoc --pdf-engine=typstwith 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
| File | Purpose |
|---|---|
scripts/build-docs-pdf.sh | Local/CI PDF build script |
scripts/pandoc-template.typ | Custom Typst template (font fallback, TOC, helpers) |
.github/workflows/release.yml | CI 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:
- README, CLAUDE
- Architecture docs
- Desktop docs
- Editors docs
- Security docs
- Networking docs
- Services docs
- Storage & Hardware docs
- Data & AI docs
- Hosts (
docs/hosts/*— globbed automatically) - 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:
- Installs pandoc + typst via
nix shell nixpkgs#pandoc nixpkgs#typst - Runs
scripts/build-docs-pdf.sh→ producesdotfiles-docs.pdf - Creates a GitHub Release via
softprops/action-gh-release@v2 - Attaches the PDF to the release
- 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:
- Reads
.nixfiles from the dotfiles submodule - Parses
##comment blocks as markdown - Renders remaining code as fenced code blocks with
language=nix - 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
- Write the
.mdfile indocs/(ordocs/hosts/) - Add an entry in
src/lib/doc-categories.tsin the Astro site (unless it’s a host doc) - Add the doc to the
orderarray inscripts/build-docs-pdf.sh - Update the submodule and deploy (see above)
Add a New Section
- Add entries to
src/lib/doc-categories.ts - Add the section name to the
sectionOrderarray in the same file - Add the docs to the
orderarray inscripts/build-docs-pdf.sh
CI Reference
Dotfiles repo workflows
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml | Push to dev/master, PR | Syntax check (hey check syntax) |
release.yml | Tag v*, manual | PDF build + GitHub Release + attach PDF |
Astro site workflow
| Workflow | Trigger | What it does |
|---|---|---|
deploy.yml | Push to main | Checkout (with submodules) → Astro build → GitHub Pages deploy |
Updating CLAUDE.md / Memory
When adding or changing documentation infrastructure, update:
- This file (
docs/documentation-infra.md) — workflow changes, new sections src/lib/doc-categories.ts— new docs or renamed docsscripts/build-docs-pdf.sh— new docs in the PDF orderCLAUDE.md— if architecture or agent-relevant rules change