Update Documentation
How to publish documentation changes to https://docs.eblu.me.
Releasing docs
Documentation ships with BlumeOps releases (the release-PR model, eblume/blumeops#1628). To ship docs changes:
- Prepare the cut with the shared release kit’s
cut-release(eblume/release-kitdocs/how-to/cut-a-release.md): it builds the towncrier section fromdocs/changelog.d/fragments (fragments consumed), places a blurb under the section heading for minor/major releases, rotates older minor series verbatim intodocs/changelog/archive/, and bumps the rootversionfile and the indridocs_versionpin (the repo hook) — review, commit, and open it as arelease vX.Y.ZPR. - A human merges. On push to main,
release.yamldetects the version change, builds the docs tarball, and tags + publishes the Forgejo release with the CHANGELOG section as its body. - Deploy:
mise run provision-indri -- --tags docsfrom gilbert, then purge the flyio-proxy nginx cache (fly ssh console -a blumeops-proxy -C "sh -c 'rm -rf /tmp/cache && nginx -s reload'") so the new docs are served immediately.
Direct workflow link: https://forge.ops.eblu.me/eblume/blumeops/actions?workflow=release.yaml
What the Workflow Does
The release workflow (.forgejo/workflows/release.yaml) fires on every push to
main:
detect(on the unprivilegedindri-buildrunner) — verifies the forge server URL, compares the rootversionfile against the push’s before-commit, and requires a matching## [vX.Y.Z]section inCHANGELOG.md; a push that is not a release exits cleanpublish(onindri-build) — builds the docs tarball withmise run docs-build-tarball(Quartz build in a node:22-slim container via colima) intorelease-assets/, then tags and creates the Forgejo release via the shared kit’spublish-releaseaction (eblume/release-kit@v0.1.1), attaching the tarball
The changelog is cut in the release PR itself (towncrier runs there, not in CI),
so CI never writes to CHANGELOG.md and never pushes main.
Changelog Fragments (Towncrier)
When making changes, add a changelog fragment to docs/changelog.d/:
# Format: <identifier>.<type>.md
# Types: feature, bugfix, infra, doc, ai, misc
# Using branch name (preferred)
echo "Add new feature X" > docs/changelog.d/my-feature.feature.md
# Orphan fragment (when no branch fits)
echo "Fix bug Y" > docs/changelog.d/+fix-bug.bugfix.mdFragments are collected into CHANGELOG.md (at repo root) by cut-release when the release PR is cut.
Fragment types:
| Type | Description |
|---|---|
feature | New features |
bugfix | Bug fixes |
infra | Infrastructure changes |
doc | Documentation updates |
ai | AI assistance changes |
misc | Other changes |
Runner Environment
All of the workflow’s jobs run on the unprivileged launchd forgejo
indri-build runner on indri (colima container engine)
(configure-launchd-runner):
- Runner: launchd services on indri (the
indriuser LaunchAgent; theindri-buildLaunchDaemon plus its colima VM); the main runner’s LaunchAgent,config.yamland the cache maintenance agents are generation-owned (the indri flake, eblume/blumeops#1528), theindri-buildrunner’s config/colima profile stay in theforgejo_runneransible role (no Kubernetes, no job container) - Toolchain: all jobs use indri-build’s mise toolchain (Node.js, uv/Python, …)
- Build engine: the docs build runs a node:22-slim container via the
docs-build-tarballtask in indri-build’s colima — not Docker Desktop (no Dagger)
Quartz Static Site Generator
Quartz builds the documentation into a static site with:
- Wiki-link support (
[[page]]syntax) - Backlinks panel showing what references each page
- Graph view of document connections
- Full-text search
Configuration file (in docs/):
quartz.config.yaml- Site metadata, plugins, theme, and page layout (v5, single file)
Quartz is cloned fresh during each build (not vendored) to use the latest version.
Manual Build (Local)
To test docs locally without triggering a release:
# Build docs tarball (identical to CI)
mise run docs-build-tarball ./docs-dev.tar.gz
# Inspect the output
tar tf docs-dev.tar.gz | head -20
# Debug a Quartz build failure interactively: same setup as the task, then stay in
docker run --rm -it -v "$PWD":/workspace:ro node:22-slim sh -c '
set -e
apt-get update -qq && apt-get install -y -qq git
mkdir -p /build && cd /build
cp -r /workspace/docs . && cp /workspace/CHANGELOG.md docs/
git clone --depth=1 https://github.com/jackyzha0/quartz.git /tmp/quartz
cp -r /tmp/quartz/quartz /tmp/quartz/package*.json /tmp/quartz/tsconfig.json /tmp/quartz/quartz.ts /tmp/quartz/.npmrc .
npm install
cp docs/quartz.config.yaml .
npx quartz build -d docs
echo "Build done — inspect /build/public"
exec sh
'Troubleshooting
Release push fails in detect:
- The
versionfile must have changed vs the push’s parent commit CHANGELOG.mdmust carry a matching## [vX.Y.Z]section (cut-release produces both)
Docs not updating after deploy:
- Confirm
docs_versionwas bumped inansible/roles/docs/defaults/main.ymland the provision ran - Check the installed version sentinel:
ssh indri 'cat ~/blumeops/docs/.installed-version' - If stale content is served publicly, purge the flyio-proxy cache (see above)
Towncrier not finding fragments:
- Fragments must be in
docs/changelog.d/ - Must have
.mdextension - Must match pattern
<name>.<type>.md