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, 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:

  1. Concatenate all docs in curated order (README → CLAUDE → Architecture → … → Operations)
  2. Prepend a YAML metadata block (title, author, TOC settings)
  3. Pandoc converts the combined markdown to Typst source
  4. Typst compiles with our custom template (scripts/pandoc-template.typ) → PDF

Why Pandoc + Typst

FactorPandoc + TypstXeLaTeX / pdfTeX
Binary size~30 MB (typst)~300 MB+ (texlive)
CJK / UnicodeNative — no extra fontsRequires xeCJK + font setup
Compile speed~27× fasterSlow
Error messagesSource location + plain EnglishCryptic LaTeX traces
Nix code safetyBackslashes don’t leak into engineMust escape \, {, }, _, %
Template languageTypst (modern, typed)LaTeX (macro-based, 40+ years)

Pandoc Markdown → Typst Mapping

Pandoc 3.0+ supports --to=typst as a native output format. The conversion:

MarkdownTypst 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:

FeatureImplementation
Chapter pages#show heading.where(level: 1) — page break, 22pt blue title, thick rule
Running headersBook title in right-aligned 7.5pt subdued text
Styled TOCBlue rule separator, proper outline indent
Title pageCentered half-title (32pt), description block, full-bleed rule
Code blocks7.5pt DejaVu Sans Mono (~100 chars/line), light gray background
Inline codeLight gray box, outset spacing to preserve line-height
BlockquotesLeft 3pt blue border, italic, no background fill
Bold textColored red for emphasis without background
Tables0.9em font, compact inset, subdued strokes
Lists0.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

FilePurpose
scripts/build-docs-pdf.shLocal/CI PDF build script
scripts/pandoc-template.typCustom Typst template (~210 lines: fonts, colors, #show rules)
.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

  • 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 .md file (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:

  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