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, path
- Desktop — desktop, themes, software, keybindings, shell-history, tmux, noter
- Editors — editors, neovim, ai-language-idioms, ai-parallel-workflow
- Security — security, security-hardening, security-auth-logic, vulnerability-response, git-signing
- Networking — networking-vpn, networking-proxy, networking-topology, networking-ddns, networking-firewall, router, sing-box, web-services
- Services — sso-identity, containers-virt, systemd-services, email
- Storage & Hardware — storage-disko, persistence, hardware, hardware-ai-architectures, power-management, backup
- Data & AI — data-science, ai-ml
- Hosts — auto-collected from
docs/hosts/*.md - Operations — ops, workflow, refactor-plan, sbc-opi5p, service-audit, homelab-bootstrap, documentation-infra, bin-scripts
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 curated order (README → CLAUDE → Architecture → … → Operations)
- Prepend a YAML metadata block (title, author, TOC settings)
- Pandoc converts the combined markdown to Typst source
- Typst compiles with our custom template (
scripts/pandoc-template.typ) → PDF
Why Pandoc + Typst
| Factor | Pandoc + Typst | XeLaTeX / pdfTeX |
|---|---|---|
| Binary size | ~30 MB (typst) | ~300 MB+ (texlive) |
| CJK / Unicode | Native — no extra fonts | Requires xeCJK + font setup |
| Compile speed | ~27× faster | Slow |
| Error messages | Source location + plain English | Cryptic LaTeX traces |
| Nix code safety | Backslashes don’t leak into engine | Must escape \, {, }, _, % |
| Template language | Typst (modern, typed) | LaTeX (macro-based, 40+ years) |
Pandoc Markdown → Typst Mapping
Pandoc 3.0+ supports --to=typst as a native output format. The conversion:
| Markdown | Typst output |
|---|---|
# Heading | = Heading (with numbering from --number-sections) |
**bold** | #strong[bold] |
`code` | #raw(lang: "", `code`) |
```nix … ``` | #raw(lang: "nix", block: true, …) |
> quote | #quote[quote] |
| table | | #table(…) |
[link](url) | #link("url")[link] |
Our template overrides each of these Typst primitives via #show rules to apply
Catppuccin Latte styling. This means the Pandoc output doesn’t need post-processing —
the template handles all visual presentation.
Template Design (scripts/pandoc-template.typ)
The template uses a #show-rule architecture: Pandoc outputs standard Typst
primitives and our #show rules intercept them to apply styling. No patching of
Pandoc output needed.
Color palette: Catppuccin Latte (light theme).
Chosen over dark themes (Mocha) for print readability — light backgrounds use
less toner/ink and read better on paper. All colors are defined as rgb(...)
variables at the top of the template for easy customization.
Book-style features:
| Feature | Implementation |
|---|---|
| Chapter pages | #show heading.where(level: 1) — page break, 22pt blue title, thick rule |
| Running headers | Book title in right-aligned 7.5pt subdued text |
| Styled TOC | Blue rule separator, proper outline indent |
| Title page | Centered half-title (32pt), description block, full-bleed rule |
| Code blocks | 7.5pt DejaVu Sans Mono (~100 chars/line), light gray background |
| Inline code | Light gray box, outset spacing to preserve line-height |
| Blockquotes | Left 3pt blue border, italic, no background fill |
| Bold text | Colored red for emphasis without background |
| Tables | 0.9em font, compact inset, subdued strokes |
| Lists | 0.35em item spacing for breathing room |
Why not bookly: bookly (Typst Universe)
is a full-featured academic book template with themes, chapter system, and helper
functions. However, all versions (2.0–3.1.1) require Typst ≥0.15, while nixpkgs
unstable ships Typst 0.14.2. The theme.with() method dispatch used by bookly
doesn’t exist in 0.14. Alternative investigation: nix4vscode-style overlays or
custom Typst packages could wrap the template if bookly becomes available after a
Typst upgrade.
Key Files
| File | Purpose |
|---|---|
scripts/build-docs-pdf.sh | Local/CI PDF build script |
scripts/pandoc-template.typ | Custom Typst template (~210 lines: fonts, colors, #show rules) |
.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
- Professional title page with half-title layout
- Auto-generated table of contents (3 levels deep)
- Auto-generated section numbering
- Chapter pages: each top-level doc starts on a fresh page with blue rule
- 10.5pt DejaVu Serif body + 7.5pt DejaVu Sans Mono code
- CJK fallback via DejaVu font family
- Color-coded headings (blue → teal → green → yellow by depth)
- Left-border blockquotes (no dark fill)
- Red-colored bold text for emphasis
- Centered figures and images
Template Customization
To modify the PDF appearance, edit scripts/pandoc-template.typ:
Change color palette — edit the #let latte-* variables at the top:
#let latte-blue = rgb("#1e66f5") // headings, links, blockquote border
#let latte-text = rgb("#4c4f69") // body text
#let latte-crust = rgb("#dce0e8") // code block backgrounds
Adjust spacing — edit #set par(leading: …, spacing: …) for line density:
#set par(leading: 0.75em, spacing: 0.65em) // current (loose)
#set par(leading: 0.60em, spacing: 0.45em) // tighter (saves pages)
Code font size — edit #show raw.where(block: true):
set text(size: 7.5pt) // ~100 chars/line on A4
set text(size: 8.5pt) // ~88 chars/line — use if code overflows
Troubleshooting PDF Issues
Code blocks overflow page margins: The template uses 7.5pt code font with
overhang: false. If lines still overflow (>100 chars), either:
- Reduce font further (7pt) in the template
- Break long lines in the source
.mdfile (wrapping at ~95 chars) - Use
\line continuation in Nix code examples
Tables overflow: Tables use 0.9em font with compact padding. If a table still overflows, reduce column count or use shorter cell content. Typst doesn’t support horizontal scrolling in PDF output.
Font warnings: Ensure dejavu_fonts or equivalent is installed. The template
uses “DejaVu Serif”, “DejaVu Sans”, “DejaVu Sans Mono”. On NixOS:
nix shell nixpkgs#dejavu_fonts
Typst version: The template requires Typst ≥0.12 (uses #show raw.where(block: true)).
Current: Typst 0.14.2 (nixpkgs unstable).
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