From b197bd5f58c1b6122d30bfb25c7f44099c2a64c6 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Wed, 11 Feb 2026 16:33:16 -0800 Subject: [PATCH] Adopt Dagger CI for docs build (Phase 2) (#157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Migrates the docs build pipeline to Dagger (Phase 2 of the Dagger CI adoption plan). - **Backfill `date-modified` frontmatter** on all 80 docs — Dagger's `--src=.` excludes `.git`, so Quartz can't use git history for page dates. Frontmatter dates work with or without git. - **New `docs-check-frontmatter` mise task + pre-commit hook** — validates all docs have `title`, `tags`, and `date-modified` - **New Dagger functions** — `build_changelog` (towncrier in Python container) and `build_docs` (chains changelog → Quartz build in Node container, returns tarball) - **Simplified CI workflow** — the ~44-line inline Quartz build (clone, npm ci, build, tar, cleanup) is replaced by `dagger call build-docs`. Changelog step remains local on the runner since towncrier needs to modify the host working tree for the git commit. ### Design decisions - **Towncrier runs twice in CI**: once inside Dagger (for the docs tarball) and once on the runner (for the git commit). This is intentional — Dagger's directory export is additive and can't delete the consumed changelog fragments from the host. - **Artifact hosting stays on Forgejo Releases** (not migrated to Forgejo Packages as the plan doc originally suggested). That migration can happen independently. - **`date-modified` frontmatter** preserved even though `build_changelog` installs git — the git there is only for towncrier's `git add` call, not for history. The local iteration story (`dagger call build-docs --src=. --version=dev` with uncommitted changes) depends on frontmatter dates. ### Local iteration ```bash dagger call build-docs --src=. --version=dev export --path=./docs-dev.tar.gz tar tf docs-dev.tar.gz | head -20 ``` ## Deployment and Testing - [x] `dagger call build-docs --src=. --version=dev` produces valid 1.1MB tarball (149 HTML pages) - [x] Pre-commit hooks pass (including new `docs-check-frontmatter`) - [ ] Full `workflow_dispatch` run after merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/157 --- .dagger/src/blumeops_ci/main.py | 64 +++++++++++++- .forgejo/workflows/build-blumeops.yaml | 78 +++++++---------- .pre-commit-config.yaml | 6 ++ .../changelog.d/dagger-phase2-docs.feature.md | 1 + docs/explanation/architecture.md | 1 + docs/explanation/explanation.md | 1 + docs/explanation/security-model.md | 1 + docs/explanation/why-gitops.md | 1 + docs/how-to/add-ansible-role.md | 1 + docs/how-to/deploy-k8s-service.md | 1 + docs/how-to/expose-service-publicly.md | 1 + docs/how-to/gandi-operations.md | 1 + docs/how-to/how-to.md | 1 + .../knowledgebase/review-documentation.md | 1 + docs/how-to/manage-flyio-proxy.md | 1 + docs/how-to/plans/add-unifi-pulumi-stack.md | 1 + docs/how-to/plans/adopt-dagger-ci.md | 13 +-- docs/how-to/plans/adopt-oidc-provider.md | 1 + .../how-to/plans/forgejo-actions-dashboard.md | 1 + docs/how-to/plans/harden-zot-registry.md | 1 + .../how-to/plans/migrate-forgejo-from-brew.md | 1 + .../plans/operationalize-reolink-camera.md | 1 + docs/how-to/plans/plans.md | 1 + docs/how-to/plans/upstream-fork-strategy.md | 1 + docs/how-to/restart-indri.md | 1 + docs/how-to/restore-1password-backup.md | 1 + docs/how-to/troubleshooting.md | 1 + docs/how-to/update-documentation.md | 1 + docs/how-to/update-tailscale-acls.md | 1 + docs/how-to/use-pypi-proxy.md | 1 + docs/index.md | 1 + docs/reference/ansible/roles.md | 1 + docs/reference/infrastructure/gandi.md | 1 + docs/reference/infrastructure/gilbert.md | 1 + docs/reference/infrastructure/hosts.md | 1 + docs/reference/infrastructure/indri.md | 1 + docs/reference/infrastructure/power.md | 1 + docs/reference/infrastructure/routing.md | 1 + docs/reference/infrastructure/tailscale.md | 1 + docs/reference/infrastructure/unifi.md | 1 + docs/reference/kubernetes/apps.md | 1 + docs/reference/kubernetes/cluster.md | 1 + docs/reference/kubernetes/external-secrets.md | 1 + .../kubernetes/tailscale-operator.md | 1 + docs/reference/operations/backup.md | 1 + .../reference/operations/disaster-recovery.md | 1 + docs/reference/operations/observability.md | 1 + docs/reference/reference.md | 1 + docs/reference/services/1password.md | 1 + docs/reference/services/alloy.md | 1 + docs/reference/services/argocd.md | 1 + docs/reference/services/automounter.md | 1 + docs/reference/services/borgmatic.md | 1 + docs/reference/services/caddy.md | 1 + docs/reference/services/devpi.md | 1 + docs/reference/services/docs.md | 1 + docs/reference/services/flyio-proxy.md | 1 + docs/reference/services/forgejo.md | 1 + docs/reference/services/grafana.md | 1 + docs/reference/services/immich.md | 1 + docs/reference/services/jellyfin.md | 1 + docs/reference/services/kiwix.md | 1 + docs/reference/services/loki.md | 1 + docs/reference/services/miniflux.md | 1 + docs/reference/services/navidrome.md | 1 + docs/reference/services/postgresql.md | 1 + docs/reference/services/prometheus.md | 1 + docs/reference/services/teslamate.md | 1 + docs/reference/services/transmission.md | 1 + docs/reference/services/zot.md | 1 + docs/reference/storage/backups.md | 1 + docs/reference/storage/postgresql-storage.md | 1 + docs/reference/storage/sifaka.md | 1 + docs/tutorials/adding-a-service.md | 1 + docs/tutorials/ai-assistance-guide.md | 1 + docs/tutorials/contributing.md | 1 + docs/tutorials/exploring-the-docs.md | 1 + docs/tutorials/replicating-blumeops.md | 1 + docs/tutorials/replication/argocd-config.md | 1 + docs/tutorials/replication/core-services.md | 1 + .../replication/kubernetes-bootstrap.md | 1 + .../replication/observability-stack.md | 1 + docs/tutorials/replication/tailscale-setup.md | 1 + docs/tutorials/tutorials.md | 1 + mise-tasks/docs-check-frontmatter | 86 +++++++++++++++++++ 85 files changed, 272 insertions(+), 55 deletions(-) create mode 100644 docs/changelog.d/dagger-phase2-docs.feature.md create mode 100755 mise-tasks/docs-check-frontmatter diff --git a/.dagger/src/blumeops_ci/main.py b/.dagger/src/blumeops_ci/main.py index 5084e57..851720d 100644 --- a/.dagger/src/blumeops_ci/main.py +++ b/.dagger/src/blumeops_ci/main.py @@ -1,5 +1,5 @@ import dagger -from dagger import function, object_type +from dagger import dag, function, object_type @object_type @@ -22,3 +22,65 @@ class BlumeopsCi: ctr = self.build(src, container_name) ref = f"{registry}/blumeops/{container_name}:{version}" return await ctr.publish(ref) + + @function + async def build_changelog( + self, src: dagger.Directory, version: str + ) -> dagger.Directory: + """Run towncrier to build changelog, return modified source tree.""" + return await ( + dag.container() + .from_("python:3.12-slim") + # git is required because towncrier stages CHANGELOG.md via git add + .with_exec(["apt-get", "update", "-qq"]) + .with_exec(["apt-get", "install", "-y", "-qq", "git"]) + .with_exec(["pip", "install", "towncrier"]) + .with_directory("/workspace", src) + .with_workdir("/workspace") + .with_exec(["git", "init"]) + .with_exec(["towncrier", "build", "--version", version, "--yes"]) + .directory("/workspace") + ) + + @function + async def build_docs(self, src: dagger.Directory, version: str) -> dagger.File: + """Build changelog then Quartz site. Returns docs tarball.""" + updated_src = await self.build_changelog(src, version) + return await ( + dag.container() + .from_("node:20-slim") + .with_exec(["apt-get", "update", "-qq"]) + .with_exec(["apt-get", "install", "-y", "-qq", "git"]) + .with_directory("/workspace", updated_src) + .with_workdir("/workspace") + .with_exec( + [ + "git", + "clone", + "--depth=1", + "https://github.com/jackyzha0/quartz.git", + "/tmp/quartz", + ] + ) + .with_exec( + [ + "sh", + "-c", + "cp -r /tmp/quartz/quartz /tmp/quartz/package*.json " + "/tmp/quartz/tsconfig.json .", + ] + ) + .with_exec(["npm", "ci"]) + .with_exec(["cp", "docs/quartz.config.ts", "."]) + .with_exec(["cp", "docs/quartz.layout.ts", "."]) + .with_exec(["cp", "CHANGELOG.md", "docs/"]) + .with_exec(["npx", "quartz", "build", "-d", "docs"]) + .with_exec( + [ + "sh", + "-c", + f"tar -czf /docs-{version}.tar.gz -C public .", + ] + ) + .file(f"/docs-{version}.tar.gz") + ) diff --git a/.forgejo/workflows/build-blumeops.yaml b/.forgejo/workflows/build-blumeops.yaml index 14f8679..0b0f60e 100644 --- a/.forgejo/workflows/build-blumeops.yaml +++ b/.forgejo/workflows/build-blumeops.yaml @@ -106,14 +106,43 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - # Need full history for git operations fetch-depth: 0 + - name: Ensure Dagger CLI + run: | + # Bootstrap: install dagger if not already in the runner image. + # Remove once all runners include dagger (Phase 3). + if ! command -v dagger &>/dev/null; then + echo "Dagger not found, installing..." + curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=0.19.11 sh + mv ./bin/dagger /usr/local/bin/dagger && rmdir ./bin + fi + dagger version + + - name: Build docs + run: | + VERSION="${{ steps.version.outputs.version }}" + TARBALL="docs-${VERSION}.tar.gz" + echo "Building docs via Dagger..." + # build-docs calls build_changelog internally (towncrier runs inside + # the Dagger container). The host working tree is not modified — only + # the tarball is exported. Towncrier runs a second time on the runner + # in the next step so that CHANGELOG.md and fragment deletion are + # captured in the git commit. + dagger call build-docs --src=. --version="$VERSION" \ + export --path="./$TARBALL" + echo "Build complete!" + ls -lh "$TARBALL" + - name: Build changelog id: changelog run: | VERSION="${{ steps.version.outputs.version }}" + # Run towncrier on the runner (not in Dagger) so that CHANGELOG.md + # updates and fragment deletions appear in the working tree for the + # git commit step. This is intentionally a second towncrier run — + # the first happened inside the Dagger build-docs container above. # Check if there are any changelog fragments FRAGMENTS=$(find docs/changelog.d -name "*.md" -not -name ".gitkeep" 2>/dev/null | wc -l) @@ -123,7 +152,6 @@ jobs: echo "changelog_updated=true" >> "$GITHUB_OUTPUT" # Extract the changelog section for this release to include in release body - # The section starts with "## [$VERSION]" and ends before the next "## [" or EOF RELEASE_NOTES=$(awk -v ver="$VERSION" ' /^## \[/ { if (found) exit @@ -132,7 +160,6 @@ jobs: found {print} ' CHANGELOG.md | tail -n +2) - # Save release notes to a file for later use (handles multiline content) echo "$RELEASE_NOTES" > /tmp/release_notes.md echo "Release notes extracted for $VERSION" else @@ -141,51 +168,6 @@ jobs: echo "" > /tmp/release_notes.md fi - - name: Build docs - run: | - VERSION="${{ steps.version.outputs.version }}" - echo "Node version: $(node --version)" - echo "NPM version: $(npm --version)" - - # Clone Quartz to temp location - git clone --depth 1 https://github.com/jackyzha0/quartz.git /tmp/quartz - - # Copy Quartz build system into blumeops workspace - # This allows building from within the repo so git can find file history - cp -r /tmp/quartz/quartz "$GITHUB_WORKSPACE/" - cp /tmp/quartz/package.json "$GITHUB_WORKSPACE/" - cp /tmp/quartz/package-lock.json "$GITHUB_WORKSPACE/" - cp /tmp/quartz/tsconfig.json "$GITHUB_WORKSPACE/" - - cd "$GITHUB_WORKSPACE" - - # Install dependencies - npm ci - - # Copy our configuration to workspace root - cp docs/quartz.config.ts . - cp docs/quartz.layout.ts . - - # Copy CHANGELOG.md into docs so it's part of the content - cp CHANGELOG.md docs/ - - # Build using -d docs so git can find file history at correct paths - echo "Building static site..." - npx quartz build -d docs - - # Create tarball - TARBALL="docs-${VERSION}.tar.gz" - echo "Creating tarball: $TARBALL" - tar -czf "$TARBALL" -C public . - - echo "Build complete!" - ls -lh "$TARBALL" - - # Clean up Quartz build artifacts (keep tarball) - rm -rf quartz public node_modules - rm -f package.json package-lock.json tsconfig.json quartz.config.ts quartz.layout.ts - rm -f docs/CHANGELOG.md # Remove copied file - - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aca9eb3..7b5fd1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -110,3 +110,9 @@ repos: language: system files: ^docs/.*\.md$ pass_filenames: false + - id: docs-check-frontmatter + name: docs-check-frontmatter + entry: mise run docs-check-frontmatter + language: system + files: ^docs/.*\.md$ + pass_filenames: false diff --git a/docs/changelog.d/dagger-phase2-docs.feature.md b/docs/changelog.d/dagger-phase2-docs.feature.md new file mode 100644 index 0000000..82d9c8f --- /dev/null +++ b/docs/changelog.d/dagger-phase2-docs.feature.md @@ -0,0 +1 @@ +Migrate docs build pipeline to Dagger (Phase 2): `dagger call build-docs --src=. --version=dev` now runs the full Quartz build locally, identically to CI. Adds `date-modified` frontmatter to all docs and a `docs-check-frontmatter` pre-commit hook. diff --git a/docs/explanation/architecture.md b/docs/explanation/architecture.md index bf52db6..e2c69c8 100644 --- a/docs/explanation/architecture.md +++ b/docs/explanation/architecture.md @@ -1,5 +1,6 @@ --- title: Architecture +date-modified: 2026-02-09 last-reviewed: 2026-02-09 tags: - explanation diff --git a/docs/explanation/explanation.md b/docs/explanation/explanation.md index 703273d..4511e6d 100644 --- a/docs/explanation/explanation.md +++ b/docs/explanation/explanation.md @@ -1,5 +1,6 @@ --- title: Explanation +date-modified: 2026-02-10 last-reviewed: 2026-02-10 tags: - explanation diff --git a/docs/explanation/security-model.md b/docs/explanation/security-model.md index f019aa9..a1989b1 100644 --- a/docs/explanation/security-model.md +++ b/docs/explanation/security-model.md @@ -1,5 +1,6 @@ --- title: Security Model +date-modified: 2026-02-11 last-reviewed: 2026-02-11 tags: - explanation diff --git a/docs/explanation/why-gitops.md b/docs/explanation/why-gitops.md index 54a9e8e..d21dc87 100644 --- a/docs/explanation/why-gitops.md +++ b/docs/explanation/why-gitops.md @@ -1,5 +1,6 @@ --- title: Why GitOps +date-modified: 2026-02-07 tags: - explanation - philosophy diff --git a/docs/how-to/add-ansible-role.md b/docs/how-to/add-ansible-role.md index 47d478f..8abfb2b 100644 --- a/docs/how-to/add-ansible-role.md +++ b/docs/how-to/add-ansible-role.md @@ -1,5 +1,6 @@ --- title: Add Ansible Role +date-modified: 2026-02-07 tags: - how-to - ansible diff --git a/docs/how-to/deploy-k8s-service.md b/docs/how-to/deploy-k8s-service.md index 0ee29c1..b08ffe6 100644 --- a/docs/how-to/deploy-k8s-service.md +++ b/docs/how-to/deploy-k8s-service.md @@ -1,5 +1,6 @@ --- title: Deploy K8s Service +date-modified: 2026-02-07 tags: - how-to - kubernetes diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index 7fbd79b..f6324fa 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -1,5 +1,6 @@ --- title: Expose a Service Publicly +date-modified: 2026-02-08 tags: - how-to - fly-io diff --git a/docs/how-to/gandi-operations.md b/docs/how-to/gandi-operations.md index bebdd52..7688857 100644 --- a/docs/how-to/gandi-operations.md +++ b/docs/how-to/gandi-operations.md @@ -1,5 +1,6 @@ --- title: Gandi Operations +date-modified: 2026-02-08 tags: - how-to - dns diff --git a/docs/how-to/how-to.md b/docs/how-to/how-to.md index c9b3230..8b5810e 100644 --- a/docs/how-to/how-to.md +++ b/docs/how-to/how-to.md @@ -1,5 +1,6 @@ --- title: How-To +date-modified: 2026-02-11 tags: - how-to --- diff --git a/docs/how-to/knowledgebase/review-documentation.md b/docs/how-to/knowledgebase/review-documentation.md index 7455596..04f62ba 100644 --- a/docs/how-to/knowledgebase/review-documentation.md +++ b/docs/how-to/knowledgebase/review-documentation.md @@ -1,5 +1,6 @@ --- title: Review Documentation +date-modified: 2026-02-09 tags: - how-to - documentation diff --git a/docs/how-to/manage-flyio-proxy.md b/docs/how-to/manage-flyio-proxy.md index b8c04bb..08b5364 100644 --- a/docs/how-to/manage-flyio-proxy.md +++ b/docs/how-to/manage-flyio-proxy.md @@ -1,5 +1,6 @@ --- title: Manage Fly.io Proxy +date-modified: 2026-02-08 tags: - how-to - fly-io diff --git a/docs/how-to/plans/add-unifi-pulumi-stack.md b/docs/how-to/plans/add-unifi-pulumi-stack.md index d7f4730..8eb55c8 100644 --- a/docs/how-to/plans/add-unifi-pulumi-stack.md +++ b/docs/how-to/plans/add-unifi-pulumi-stack.md @@ -1,5 +1,6 @@ --- title: "Plan: Add UniFi Pulumi Stack" +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/plans/adopt-dagger-ci.md b/docs/how-to/plans/adopt-dagger-ci.md index 8ebb1e6..f44561b 100644 --- a/docs/how-to/plans/adopt-dagger-ci.md +++ b/docs/how-to/plans/adopt-dagger-ci.md @@ -1,5 +1,6 @@ --- title: "Plan: Adopt Dagger as CI/CD Build Engine" +date-modified: 2026-02-11 tags: - how-to - plans @@ -9,7 +10,7 @@ tags: # Plan: Adopt Dagger as CI/CD Build Engine -> **Status:** Phase 1 implemented +> **Status:** Phase 2 implemented ## Background @@ -496,11 +497,11 @@ BuildKit caches aggressively, making repeated builds fast. Since the Forgejo run - [x] Existing `mise run container-tag-and-release` workflow still works end-to-end ### Phase 2 (Docs) -- [ ] `dagger call build-docs --src=. --version=dev` produces valid tarball locally -- [ ] Tarball contents match current Quartz build output -- [ ] `dagger call release-docs` uploads to Forgejo packages successfully -- [ ] Quartz container starts and serves docs from Forgejo packages URL -- [ ] ArgoCD sync works from within Dagger +- [x] `dagger call build-docs --src=. --version=dev` produces valid tarball locally +- [x] Tarball contents match current Quartz build output +- [ ] ~~`dagger call release-docs` uploads to Forgejo packages successfully~~ (deferred — artifact hosting stays on Forgejo Releases) +- [ ] ~~Quartz container starts and serves docs from Forgejo packages URL~~ (deferred) +- [ ] ~~ArgoCD sync works from within Dagger~~ (deferred) - [ ] Forgejo Actions workflow_dispatch completes full release cycle - [ ] CHANGELOG.md and fragment cleanup committed correctly diff --git a/docs/how-to/plans/adopt-oidc-provider.md b/docs/how-to/plans/adopt-oidc-provider.md index e49dfba..bd24acd 100644 --- a/docs/how-to/plans/adopt-oidc-provider.md +++ b/docs/how-to/plans/adopt-oidc-provider.md @@ -1,5 +1,6 @@ --- title: "Plan: Adopt OIDC Identity Provider" +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/plans/forgejo-actions-dashboard.md b/docs/how-to/plans/forgejo-actions-dashboard.md index 00d6c7d..e593611 100644 --- a/docs/how-to/plans/forgejo-actions-dashboard.md +++ b/docs/how-to/plans/forgejo-actions-dashboard.md @@ -1,5 +1,6 @@ --- title: "Plan: Forgejo Actions Dashboard" +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/plans/harden-zot-registry.md b/docs/how-to/plans/harden-zot-registry.md index a8f9618..29deecd 100644 --- a/docs/how-to/plans/harden-zot-registry.md +++ b/docs/how-to/plans/harden-zot-registry.md @@ -1,5 +1,6 @@ --- title: "Plan: Harden Zot Registry" +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/plans/migrate-forgejo-from-brew.md b/docs/how-to/plans/migrate-forgejo-from-brew.md index 1f148da..f0d241c 100644 --- a/docs/how-to/plans/migrate-forgejo-from-brew.md +++ b/docs/how-to/plans/migrate-forgejo-from-brew.md @@ -1,5 +1,6 @@ --- title: "Plan: Migrate Forgejo from Brew to Source Build" +date-modified: 2026-02-10 tags: - how-to - plans diff --git a/docs/how-to/plans/operationalize-reolink-camera.md b/docs/how-to/plans/operationalize-reolink-camera.md index 46006a6..0797ac1 100644 --- a/docs/how-to/plans/operationalize-reolink-camera.md +++ b/docs/how-to/plans/operationalize-reolink-camera.md @@ -1,5 +1,6 @@ --- title: "Plan: Operationalize ReoLink Camera" +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/plans/plans.md b/docs/how-to/plans/plans.md index 1e34bab..d5409de 100644 --- a/docs/how-to/plans/plans.md +++ b/docs/how-to/plans/plans.md @@ -1,5 +1,6 @@ --- title: Plans +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/plans/upstream-fork-strategy.md b/docs/how-to/plans/upstream-fork-strategy.md index fb11ec8..93402ba 100644 --- a/docs/how-to/plans/upstream-fork-strategy.md +++ b/docs/how-to/plans/upstream-fork-strategy.md @@ -1,5 +1,6 @@ --- title: "Plan: Upstream Fork Strategy" +date-modified: 2026-02-11 tags: - how-to - plans diff --git a/docs/how-to/restart-indri.md b/docs/how-to/restart-indri.md index 6e9f522..f0f5600 100644 --- a/docs/how-to/restart-indri.md +++ b/docs/how-to/restart-indri.md @@ -1,5 +1,6 @@ --- title: Restart Indri +date-modified: 2026-02-10 tags: - how-to - operations diff --git a/docs/how-to/restore-1password-backup.md b/docs/how-to/restore-1password-backup.md index d1508b0..14df7e5 100644 --- a/docs/how-to/restore-1password-backup.md +++ b/docs/how-to/restore-1password-backup.md @@ -1,5 +1,6 @@ --- title: Restore 1Password Backup +date-modified: 2026-02-10 tags: - how-to - operations diff --git a/docs/how-to/troubleshooting.md b/docs/how-to/troubleshooting.md index bf6ec4a..a04f31f 100644 --- a/docs/how-to/troubleshooting.md +++ b/docs/how-to/troubleshooting.md @@ -1,5 +1,6 @@ --- title: Troubleshooting +date-modified: 2026-02-07 tags: - how-to - operations diff --git a/docs/how-to/update-documentation.md b/docs/how-to/update-documentation.md index 400cdaf..e758de7 100644 --- a/docs/how-to/update-documentation.md +++ b/docs/how-to/update-documentation.md @@ -1,5 +1,6 @@ --- title: Update Documentation +date-modified: 2026-02-08 tags: - how-to - documentation diff --git a/docs/how-to/update-tailscale-acls.md b/docs/how-to/update-tailscale-acls.md index 73fcd5e..f3a34d8 100644 --- a/docs/how-to/update-tailscale-acls.md +++ b/docs/how-to/update-tailscale-acls.md @@ -1,5 +1,6 @@ --- title: Update Tailscale ACLs +date-modified: 2026-02-07 tags: - how-to - tailscale diff --git a/docs/how-to/use-pypi-proxy.md b/docs/how-to/use-pypi-proxy.md index 1554f06..0f2ff8a 100644 --- a/docs/how-to/use-pypi-proxy.md +++ b/docs/how-to/use-pypi-proxy.md @@ -1,5 +1,6 @@ --- title: Use PyPI Proxy +date-modified: 2026-02-07 tags: - how-to - python diff --git a/docs/index.md b/docs/index.md index 5a5c1c5..2209970 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,6 @@ --- title: BlumeOps +date-modified: 2026-02-08 aliases: [] id: index tags: [] diff --git a/docs/reference/ansible/roles.md b/docs/reference/ansible/roles.md index 10aed7a..c1cd6bb 100644 --- a/docs/reference/ansible/roles.md +++ b/docs/reference/ansible/roles.md @@ -1,5 +1,6 @@ --- title: Roles +date-modified: 2026-02-07 tags: - ansible - reference diff --git a/docs/reference/infrastructure/gandi.md b/docs/reference/infrastructure/gandi.md index 58a54e9..5fecf59 100644 --- a/docs/reference/infrastructure/gandi.md +++ b/docs/reference/infrastructure/gandi.md @@ -1,5 +1,6 @@ --- title: Gandi +date-modified: 2026-02-08 tags: - infrastructure - networking diff --git a/docs/reference/infrastructure/gilbert.md b/docs/reference/infrastructure/gilbert.md index 5f669a7..dbc56fb 100644 --- a/docs/reference/infrastructure/gilbert.md +++ b/docs/reference/infrastructure/gilbert.md @@ -1,5 +1,6 @@ --- title: Gilbert +date-modified: 2026-02-07 tags: - infrastructure - host diff --git a/docs/reference/infrastructure/hosts.md b/docs/reference/infrastructure/hosts.md index 3029b11..8098c95 100644 --- a/docs/reference/infrastructure/hosts.md +++ b/docs/reference/infrastructure/hosts.md @@ -1,5 +1,6 @@ --- title: Hosts +date-modified: 2026-02-10 tags: - infrastructure --- diff --git a/docs/reference/infrastructure/indri.md b/docs/reference/infrastructure/indri.md index 672cdc4..4ddfd60 100644 --- a/docs/reference/infrastructure/indri.md +++ b/docs/reference/infrastructure/indri.md @@ -1,5 +1,6 @@ --- title: Indri +date-modified: 2026-02-09 tags: - infrastructure - host diff --git a/docs/reference/infrastructure/power.md b/docs/reference/infrastructure/power.md index 2e885e1..9812c24 100644 --- a/docs/reference/infrastructure/power.md +++ b/docs/reference/infrastructure/power.md @@ -1,5 +1,6 @@ --- title: Power +date-modified: 2026-02-09 tags: - infrastructure --- diff --git a/docs/reference/infrastructure/routing.md b/docs/reference/infrastructure/routing.md index 9270909..20a7baa 100644 --- a/docs/reference/infrastructure/routing.md +++ b/docs/reference/infrastructure/routing.md @@ -1,5 +1,6 @@ --- title: Routing +date-modified: 2026-02-09 tags: - infrastructure - networking diff --git a/docs/reference/infrastructure/tailscale.md b/docs/reference/infrastructure/tailscale.md index e9aa582..8cccf40 100644 --- a/docs/reference/infrastructure/tailscale.md +++ b/docs/reference/infrastructure/tailscale.md @@ -1,5 +1,6 @@ --- title: Tailscale +date-modified: 2026-02-08 tags: - infrastructure - networking diff --git a/docs/reference/infrastructure/unifi.md b/docs/reference/infrastructure/unifi.md index 12f9815..95bc6c6 100644 --- a/docs/reference/infrastructure/unifi.md +++ b/docs/reference/infrastructure/unifi.md @@ -1,5 +1,6 @@ --- title: UniFi +date-modified: 2026-02-10 tags: - infrastructure - networking diff --git a/docs/reference/kubernetes/apps.md b/docs/reference/kubernetes/apps.md index 88b5780..e5b1216 100644 --- a/docs/reference/kubernetes/apps.md +++ b/docs/reference/kubernetes/apps.md @@ -1,5 +1,6 @@ --- title: Apps +date-modified: 2026-02-07 tags: - kubernetes - argocd diff --git a/docs/reference/kubernetes/cluster.md b/docs/reference/kubernetes/cluster.md index 890bc1a..2621f34 100644 --- a/docs/reference/kubernetes/cluster.md +++ b/docs/reference/kubernetes/cluster.md @@ -1,5 +1,6 @@ --- title: Cluster +date-modified: 2026-02-07 tags: - kubernetes --- diff --git a/docs/reference/kubernetes/external-secrets.md b/docs/reference/kubernetes/external-secrets.md index 7df344d..fba8945 100644 --- a/docs/reference/kubernetes/external-secrets.md +++ b/docs/reference/kubernetes/external-secrets.md @@ -1,5 +1,6 @@ --- title: External Secrets +date-modified: 2026-02-07 tags: - kubernetes - secrets diff --git a/docs/reference/kubernetes/tailscale-operator.md b/docs/reference/kubernetes/tailscale-operator.md index aa7b1a8..7fe3b78 100644 --- a/docs/reference/kubernetes/tailscale-operator.md +++ b/docs/reference/kubernetes/tailscale-operator.md @@ -1,5 +1,6 @@ --- title: Tailscale Operator +date-modified: 2026-02-08 tags: - kubernetes - tailscale diff --git a/docs/reference/operations/backup.md b/docs/reference/operations/backup.md index 483a3c6..893608c 100644 --- a/docs/reference/operations/backup.md +++ b/docs/reference/operations/backup.md @@ -1,5 +1,6 @@ --- title: Backup +date-modified: 2026-02-07 tags: - operations --- diff --git a/docs/reference/operations/disaster-recovery.md b/docs/reference/operations/disaster-recovery.md index a521aa1..6755808 100644 --- a/docs/reference/operations/disaster-recovery.md +++ b/docs/reference/operations/disaster-recovery.md @@ -1,5 +1,6 @@ --- title: Disaster Recovery +date-modified: 2026-02-10 tags: - operations --- diff --git a/docs/reference/operations/observability.md b/docs/reference/operations/observability.md index f0123e2..2aca35b 100644 --- a/docs/reference/operations/observability.md +++ b/docs/reference/operations/observability.md @@ -1,5 +1,6 @@ --- title: Observability +date-modified: 2026-02-07 tags: - operations --- diff --git a/docs/reference/reference.md b/docs/reference/reference.md index c1bd418..d4f0b6f 100644 --- a/docs/reference/reference.md +++ b/docs/reference/reference.md @@ -1,5 +1,6 @@ --- title: Reference +date-modified: 2026-02-10 tags: - reference --- diff --git a/docs/reference/services/1password.md b/docs/reference/services/1password.md index 46f95da..a15871a 100644 --- a/docs/reference/services/1password.md +++ b/docs/reference/services/1password.md @@ -1,5 +1,6 @@ --- title: 1Password +date-modified: 2026-02-10 tags: - service - secrets diff --git a/docs/reference/services/alloy.md b/docs/reference/services/alloy.md index c7958bf..3635432 100644 --- a/docs/reference/services/alloy.md +++ b/docs/reference/services/alloy.md @@ -1,5 +1,6 @@ --- title: Alloy +date-modified: 2026-02-08 tags: - service - observability diff --git a/docs/reference/services/argocd.md b/docs/reference/services/argocd.md index 3b93a0e..c5b70f2 100644 --- a/docs/reference/services/argocd.md +++ b/docs/reference/services/argocd.md @@ -1,5 +1,6 @@ --- title: ArgoCD +date-modified: 2026-02-07 tags: - service - gitops diff --git a/docs/reference/services/automounter.md b/docs/reference/services/automounter.md index c26edc7..8f11199 100644 --- a/docs/reference/services/automounter.md +++ b/docs/reference/services/automounter.md @@ -1,5 +1,6 @@ --- title: Automounter +date-modified: 2026-02-07 tags: - services - macos diff --git a/docs/reference/services/borgmatic.md b/docs/reference/services/borgmatic.md index a543ab9..d10b824 100644 --- a/docs/reference/services/borgmatic.md +++ b/docs/reference/services/borgmatic.md @@ -1,5 +1,6 @@ --- title: Borgmatic +date-modified: 2026-02-10 tags: - service - backup diff --git a/docs/reference/services/caddy.md b/docs/reference/services/caddy.md index ed3f943..e7b1c2e 100644 --- a/docs/reference/services/caddy.md +++ b/docs/reference/services/caddy.md @@ -1,5 +1,6 @@ --- title: Caddy +date-modified: 2026-02-08 tags: - service - networking diff --git a/docs/reference/services/devpi.md b/docs/reference/services/devpi.md index 321a514..b6b874f 100644 --- a/docs/reference/services/devpi.md +++ b/docs/reference/services/devpi.md @@ -1,5 +1,6 @@ --- title: Devpi +date-modified: 2026-02-07 tags: - service - python diff --git a/docs/reference/services/docs.md b/docs/reference/services/docs.md index 79fe5da..fe77bea 100644 --- a/docs/reference/services/docs.md +++ b/docs/reference/services/docs.md @@ -1,5 +1,6 @@ --- title: Docs +date-modified: 2026-02-08 tags: - service - documentation diff --git a/docs/reference/services/flyio-proxy.md b/docs/reference/services/flyio-proxy.md index f03c667..1e46ed0 100644 --- a/docs/reference/services/flyio-proxy.md +++ b/docs/reference/services/flyio-proxy.md @@ -1,5 +1,6 @@ --- title: Fly.io Proxy +date-modified: 2026-02-08 tags: - service - networking diff --git a/docs/reference/services/forgejo.md b/docs/reference/services/forgejo.md index 1bed75e..a45ab93 100644 --- a/docs/reference/services/forgejo.md +++ b/docs/reference/services/forgejo.md @@ -1,5 +1,6 @@ --- title: Forgejo +date-modified: 2026-02-08 tags: - service - git diff --git a/docs/reference/services/grafana.md b/docs/reference/services/grafana.md index 43f2a2c..176c0a5 100644 --- a/docs/reference/services/grafana.md +++ b/docs/reference/services/grafana.md @@ -1,5 +1,6 @@ --- title: Grafana +date-modified: 2026-02-08 tags: - service - observability diff --git a/docs/reference/services/immich.md b/docs/reference/services/immich.md index 418724c..2b6a177 100644 --- a/docs/reference/services/immich.md +++ b/docs/reference/services/immich.md @@ -1,5 +1,6 @@ --- title: Immich +date-modified: 2026-02-07 tags: - service - media diff --git a/docs/reference/services/jellyfin.md b/docs/reference/services/jellyfin.md index 28087a9..7c9bdf6 100644 --- a/docs/reference/services/jellyfin.md +++ b/docs/reference/services/jellyfin.md @@ -1,5 +1,6 @@ --- title: Jellyfin +date-modified: 2026-02-07 tags: - service - media diff --git a/docs/reference/services/kiwix.md b/docs/reference/services/kiwix.md index 1159a8a..5885871 100644 --- a/docs/reference/services/kiwix.md +++ b/docs/reference/services/kiwix.md @@ -1,5 +1,6 @@ --- title: Kiwix +date-modified: 2026-02-07 tags: - service - knowledge diff --git a/docs/reference/services/loki.md b/docs/reference/services/loki.md index b015802..b80dd8f 100644 --- a/docs/reference/services/loki.md +++ b/docs/reference/services/loki.md @@ -1,5 +1,6 @@ --- title: Loki +date-modified: 2026-02-08 tags: - service - observability diff --git a/docs/reference/services/miniflux.md b/docs/reference/services/miniflux.md index 6605e86..c06c79e 100644 --- a/docs/reference/services/miniflux.md +++ b/docs/reference/services/miniflux.md @@ -1,5 +1,6 @@ --- title: Miniflux +date-modified: 2026-02-07 tags: - service - rss diff --git a/docs/reference/services/navidrome.md b/docs/reference/services/navidrome.md index d3537e6..741343f 100644 --- a/docs/reference/services/navidrome.md +++ b/docs/reference/services/navidrome.md @@ -1,5 +1,6 @@ --- title: Navidrome +date-modified: 2026-02-07 tags: - service - media diff --git a/docs/reference/services/postgresql.md b/docs/reference/services/postgresql.md index 906e286..ee58b66 100644 --- a/docs/reference/services/postgresql.md +++ b/docs/reference/services/postgresql.md @@ -1,5 +1,6 @@ --- title: PostgreSQL +date-modified: 2026-02-07 tags: - service - database diff --git a/docs/reference/services/prometheus.md b/docs/reference/services/prometheus.md index 67e491f..4075ad8 100644 --- a/docs/reference/services/prometheus.md +++ b/docs/reference/services/prometheus.md @@ -1,5 +1,6 @@ --- title: Prometheus +date-modified: 2026-02-08 tags: - service - observability diff --git a/docs/reference/services/teslamate.md b/docs/reference/services/teslamate.md index 357c701..fc80baf 100644 --- a/docs/reference/services/teslamate.md +++ b/docs/reference/services/teslamate.md @@ -1,5 +1,6 @@ --- title: TeslaMate +date-modified: 2026-02-07 tags: - service - vehicle diff --git a/docs/reference/services/transmission.md b/docs/reference/services/transmission.md index 37d58a0..e34c5a9 100644 --- a/docs/reference/services/transmission.md +++ b/docs/reference/services/transmission.md @@ -1,5 +1,6 @@ --- title: Transmission +date-modified: 2026-02-07 tags: - service - torrent diff --git a/docs/reference/services/zot.md b/docs/reference/services/zot.md index 298dff4..62bf729 100644 --- a/docs/reference/services/zot.md +++ b/docs/reference/services/zot.md @@ -1,5 +1,6 @@ --- title: Zot +date-modified: 2026-02-07 tags: - service - registry diff --git a/docs/reference/storage/backups.md b/docs/reference/storage/backups.md index f8b15b7..f1b798f 100644 --- a/docs/reference/storage/backups.md +++ b/docs/reference/storage/backups.md @@ -1,5 +1,6 @@ --- title: Backups +date-modified: 2026-02-10 tags: - storage - backup diff --git a/docs/reference/storage/postgresql-storage.md b/docs/reference/storage/postgresql-storage.md index 91b13b8..51277b6 100644 --- a/docs/reference/storage/postgresql-storage.md +++ b/docs/reference/storage/postgresql-storage.md @@ -1,5 +1,6 @@ --- title: PostgreSQL Storage +date-modified: 2026-02-07 tags: - storage - database diff --git a/docs/reference/storage/sifaka.md b/docs/reference/storage/sifaka.md index cd751cd..9e13d13 100644 --- a/docs/reference/storage/sifaka.md +++ b/docs/reference/storage/sifaka.md @@ -1,5 +1,6 @@ --- title: Sifaka +date-modified: 2026-02-09 tags: - storage --- diff --git a/docs/tutorials/adding-a-service.md b/docs/tutorials/adding-a-service.md index 6c28a05..73daeeb 100644 --- a/docs/tutorials/adding-a-service.md +++ b/docs/tutorials/adding-a-service.md @@ -1,5 +1,6 @@ --- title: Adding a Service +date-modified: 2026-02-07 tags: - tutorials - argocd diff --git a/docs/tutorials/ai-assistance-guide.md b/docs/tutorials/ai-assistance-guide.md index 5f34db2..716fd04 100644 --- a/docs/tutorials/ai-assistance-guide.md +++ b/docs/tutorials/ai-assistance-guide.md @@ -1,5 +1,6 @@ --- title: AI Assistance Guide +date-modified: 2026-02-09 tags: - tutorials - ai diff --git a/docs/tutorials/contributing.md b/docs/tutorials/contributing.md index ce7d068..eca8a18 100644 --- a/docs/tutorials/contributing.md +++ b/docs/tutorials/contributing.md @@ -1,5 +1,6 @@ --- title: Contributing +date-modified: 2026-02-07 tags: - tutorials - contributing diff --git a/docs/tutorials/exploring-the-docs.md b/docs/tutorials/exploring-the-docs.md index afbd255..36a5f34 100644 --- a/docs/tutorials/exploring-the-docs.md +++ b/docs/tutorials/exploring-the-docs.md @@ -1,5 +1,6 @@ --- title: Exploring the Docs +date-modified: 2026-02-10 tags: - tutorials - getting-started diff --git a/docs/tutorials/replicating-blumeops.md b/docs/tutorials/replicating-blumeops.md index 2a19717..913ba0f 100644 --- a/docs/tutorials/replicating-blumeops.md +++ b/docs/tutorials/replicating-blumeops.md @@ -1,5 +1,6 @@ --- title: Replicating BlumeOps +date-modified: 2026-02-07 tags: - tutorials - replication diff --git a/docs/tutorials/replication/argocd-config.md b/docs/tutorials/replication/argocd-config.md index 175fbd1..3fe2fff 100644 --- a/docs/tutorials/replication/argocd-config.md +++ b/docs/tutorials/replication/argocd-config.md @@ -1,5 +1,6 @@ --- title: ArgoCD Config +date-modified: 2026-02-07 tags: - tutorials - replication diff --git a/docs/tutorials/replication/core-services.md b/docs/tutorials/replication/core-services.md index a8c02b1..c82c8b0 100644 --- a/docs/tutorials/replication/core-services.md +++ b/docs/tutorials/replication/core-services.md @@ -1,5 +1,6 @@ --- title: Core Services +date-modified: 2026-02-07 tags: - tutorials - replication diff --git a/docs/tutorials/replication/kubernetes-bootstrap.md b/docs/tutorials/replication/kubernetes-bootstrap.md index 7ac33d2..0d60f83 100644 --- a/docs/tutorials/replication/kubernetes-bootstrap.md +++ b/docs/tutorials/replication/kubernetes-bootstrap.md @@ -1,5 +1,6 @@ --- title: Kubernetes Bootstrap +date-modified: 2026-02-07 tags: - tutorials - replication diff --git a/docs/tutorials/replication/observability-stack.md b/docs/tutorials/replication/observability-stack.md index cb85e92..5d7eee9 100644 --- a/docs/tutorials/replication/observability-stack.md +++ b/docs/tutorials/replication/observability-stack.md @@ -1,5 +1,6 @@ --- title: Observability Stack +date-modified: 2026-02-07 tags: - tutorials - replication diff --git a/docs/tutorials/replication/tailscale-setup.md b/docs/tutorials/replication/tailscale-setup.md index 854f78b..a76a449 100644 --- a/docs/tutorials/replication/tailscale-setup.md +++ b/docs/tutorials/replication/tailscale-setup.md @@ -1,5 +1,6 @@ --- title: Tailscale Setup +date-modified: 2026-02-07 tags: - tutorials - replication diff --git a/docs/tutorials/tutorials.md b/docs/tutorials/tutorials.md index d865864..ed854d9 100644 --- a/docs/tutorials/tutorials.md +++ b/docs/tutorials/tutorials.md @@ -1,5 +1,6 @@ --- title: Tutorials +date-modified: 2026-02-07 tags: - tutorials --- diff --git a/mise-tasks/docs-check-frontmatter b/mise-tasks/docs-check-frontmatter new file mode 100755 index 0000000..30c1a5d --- /dev/null +++ b/mise-tasks/docs-check-frontmatter @@ -0,0 +1,86 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.12" +# dependencies = ["rich>=13.0.0"] +# /// +#MISE description="Check that all docs have required frontmatter fields" +"""Validate that all documentation files have required YAML frontmatter. + +Required fields: title, tags, date-modified + +Scans all markdown files in docs/ (excluding changelog.d/) and checks +that each file has YAML frontmatter containing the required fields. + +Usage: mise run docs-check-frontmatter +""" + +import re +import sys +from pathlib import Path + +from rich.console import Console +from rich.table import Table + +DOCS_DIR = Path(__file__).parent.parent / "docs" +REQUIRED_FIELDS = {"title", "tags", "date-modified"} + +# Match YAML frontmatter block +FRONTMATTER_PATTERN = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL) + +# Match top-level YAML keys (not indented) +KEY_PATTERN = re.compile(r"^([a-zA-Z][a-zA-Z0-9_-]*):", re.MULTILINE) + + +def extract_frontmatter_keys(file_path: Path) -> set[str] | None: + """Extract top-level keys from YAML frontmatter. Returns None if no frontmatter.""" + content = file_path.read_text() + match = FRONTMATTER_PATTERN.match(content) + if not match: + return None + frontmatter = match.group(1) + return set(KEY_PATTERN.findall(frontmatter)) + + +def main() -> int: + console = Console() + console.print("[bold]Frontmatter Validation[/bold]") + console.print(f"Required fields: {', '.join(sorted(REQUIRED_FIELDS))}") + console.print() + + issues: list[tuple[str, set[str]]] = [] + + for md_file in sorted(DOCS_DIR.rglob("*.md")): + if "changelog.d" in md_file.parts: + continue + + rel_path = str(md_file.relative_to(DOCS_DIR)) + keys = extract_frontmatter_keys(md_file) + + if keys is None: + issues.append((rel_path, REQUIRED_FIELDS)) + continue + + missing = REQUIRED_FIELDS - keys + if missing: + issues.append((rel_path, missing)) + + if issues: + console.print("[bold red]Missing Required Frontmatter[/bold red]") + console.print() + table = Table(show_header=True, header_style="bold") + table.add_column("File") + table.add_column("Missing Fields") + + for rel_path, missing in issues: + table.add_row(rel_path, ", ".join(sorted(missing))) + + console.print(table) + console.print() + return 1 + + console.print("[bold green]All docs have required frontmatter![/bold green]") + return 0 + + +if __name__ == "__main__": + sys.exit(main())