Add doc-random task and documentation improvements (#98)

## Summary
- Add `doc-random` mise task that selects a random documentation card for review
- Add how-to/knowledgebase section with review-documentation guide
- Add Caddy reference card with proxy configuration details
- Fix replication tutorial sequence (tailscale-setup now links to core-services)
- Fix "BluemeOps" typo in tailscale-setup
- Clean up obsolete zk/ directory references from doc-links

## Deployment and Testing
- [x] `mise run doc-random` works and displays a random card
- [x] `mise run doc-links` passes (all wiki-links valid)
- [x] Pre-commit hooks pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/98
This commit is contained in:
Erich Blume 2026-02-03 21:17:58 -08:00
commit 5c79a8dbe2
11 changed files with 302 additions and 7 deletions

View file

@ -0,0 +1 @@
Add Caddy reference card and fix replication tutorial sequence

View file

@ -0,0 +1 @@
Add doc-random mise task for random documentation review

View file

@ -28,6 +28,12 @@ Task-oriented instructions for common BlumeOps operations. These guides assume y
|-------|-------------|
| [[update-documentation]] | Publish docs via build-blumeops workflow |
## Knowledge Base
| Guide | Description |
|-------|-------------|
| [[review-documentation]] | Periodically review and maintain documentation |
## Operations
| Guide | Description |

View file

@ -0,0 +1,97 @@
---
title: review-documentation
tags:
- how-to
- documentation
- maintenance
---
# Review Documentation
How to periodically review and maintain the BlumeOps knowledge base.
## Quick Random Review
Select a random documentation card for review:
```bash
mise run doc-random
```
This displays a random card with a review checklist to guide your assessment.
## Review Checklist
When reviewing a documentation card, consider:
| Check | Description |
|-------|-------------|
| **Accuracy** | Is the information current and correct? |
| **Links** | Are wiki-links working? Should more be added? |
| **Scope** | Is the card appropriately sized (not too large/small)? |
| **Category** | Is it in the right section (reference/how-to/tutorial/explanation)? |
| **Frontmatter** | Are title and tags appropriate? |
| **Related** | Should it link to related cards? |
## Verify Deployed State
For service reference cards, verify the documentation matches reality:
### ArgoCD Apps (Kubernetes services)
Check if the app is synced and healthy:
```bash
argocd app get <app-name>
argocd app diff <app-name> # Show pending changes
```
If out of sync, either the docs are stale or a deployment is pending.
### Ansible Roles (indri services)
Check if the role applies idempotently (no changes needed):
```bash
mise run provision-indri -- --tags <role> --check --diff
```
If changes would be made, either the docs are stale or the host has drifted.
### Pulumi (Tailscale ACLs, DNS)
Check for drift:
```bash
# Tailscale ACLs
cd pulumi/tailscale && pulumi preview
# DNS (Gandi)
cd pulumi/gandi && pulumi preview
```
If changes are pending, investigate whether docs or infrastructure is stale.
## When to Review
Consider running `mise run doc-random` during:
- Start of work sessions (quick maintenance)
- After major infrastructure changes (verify docs reflect reality)
- When learning the system (random exploration)
## Making Changes
If a card needs updates:
1. Create a feature branch
2. Make the edits
3. Run `mise run doc-links` to verify links
4. Create a PR for review
See [[update-documentation]] for publishing changes.
## Related
- [[update-documentation]] - Publishing documentation changes
- [[exploring-the-docs]] - Navigating the documentation

View file

@ -17,6 +17,7 @@ Individual service reference cards with URLs and configuration details.
| [[alloy | Alloy]] | Observability collector (metrics & logs) | indri + k8s |
| [[argocd]] | GitOps continuous delivery | k8s |
| [[borgmatic]] | Backup system | indri |
| [[caddy]] | Reverse proxy & TLS termination | indri |
| [[1password]] | Secrets management | cloud + k8s |
| [[forgejo]] | Git forge & CI/CD | indri |
| [[grafana]] | Dashboards & visualization | k8s |

View file

@ -0,0 +1,98 @@
---
title: caddy
tags:
- service
- networking
- tls
---
# Caddy
Reverse proxy for `*.ops.eblu.me` services with automatic TLS via ACME DNS-01.
## Quick Reference
| Property | Value |
|----------|-------|
| **Domain** | `*.ops.eblu.me` |
| **HTTPS Port** | 443 |
| **Config** | `ansible/roles/caddy/templates/Caddyfile.j2` |
| **Binary** | Custom build with Gandi DNS plugin |
## Why Caddy?
Caddy provides a single TLS termination point for all BlumeOps services:
- **Wildcard certificate** for `*.ops.eblu.me` via Let's Encrypt
- **DNS-01 challenge** using Gandi API (no port 80 needed)
- **Unified access** from k8s pods, containers, and tailnet clients
See [[routing]] for when to use `*.ops.eblu.me` vs `*.tail8d86e.ts.net`.
## Proxied Services
### Indri-Local Services
| Subdomain | Backend | Service |
|-----------|---------|---------|
| `forge.ops.eblu.me` | `localhost:3001` | [[forgejo]] |
| `registry.ops.eblu.me` | `localhost:5050` | [[zot]] |
| `jellyfin.ops.eblu.me` | `localhost:8096` | [[jellyfin]] |
### Kubernetes Services
K8s services are proxied via their Tailscale Ingress endpoints:
| Subdomain | Backend | Service |
|-----------|---------|---------|
| `grafana.ops.eblu.me` | `grafana.tail8d86e.ts.net` | [[grafana]] |
| `argocd.ops.eblu.me` | `argocd.tail8d86e.ts.net` | [[argocd]] |
| `docs.ops.eblu.me` | `docs.tail8d86e.ts.net` | [[docs]] |
| `feed.ops.eblu.me` | `feed.tail8d86e.ts.net` | [[miniflux]] |
| ... | ... | (see defaults/main.yml for full list) |
### TCP Services (Layer 4)
| Port | Backend | Service |
|------|---------|---------|
| 2222 | `localhost:2200` | Forgejo SSH |
| 5432 | `pg.tail8d86e.ts.net:5432` | [[postgresql]] |
## Configuration
Caddy is managed via the `caddy` Ansible role:
```bash
# Deploy caddy changes
mise run provision-indri -- --tags caddy
```
**Key files:**
- `ansible/roles/caddy/defaults/main.yml` - Service definitions
- `ansible/roles/caddy/templates/Caddyfile.j2` - Caddy config template
## Secrets
| Secret | Source | Description |
|--------|--------|-------------|
| `GANDI_BEARER_TOKEN` | 1Password | API token for DNS-01 challenges |
The token is written to `~/.config/caddy/gandi-token` (chmod 0600) and sourced by the Caddy wrapper script.
## Custom Build
Caddy is built from source with the Gandi DNS plugin:
```bash
# Build location
~/code/3rd/caddy/bin/caddy
```
The build includes the `github.com/caddy-dns/gandi` plugin for ACME DNS-01 challenges.
## Related
- [[routing]] - Service routing architecture
- [[forgejo]] - Git forge (proxied by Caddy)
- [[zot]] - Container registry (proxied by Caddy)
- [[tailscale-operator]] - K8s services use Tailscale Ingress, then Caddy

View file

@ -88,6 +88,7 @@ BlumeOps operations are driven by mise tasks. Run `mise tasks` to list all avail
| `doc-links` | Validate wiki-links in documentation |
| `doc-titles` | Check for duplicate doc titles |
| `doc-filenames` | Check for duplicate doc filenames |
| `doc-random` | Select a random doc card for review |
| `indri-runner-logs` | View Forgejo workflow logs from local runner |
For ArgoCD operations, use the `argocd` CLI directly:

View file

@ -9,6 +9,8 @@ tags:
# Core Services Setup
> **Audiences:** Replicator
>
> **Prerequisites:** [[tutorials/replication/tailscale-setup | Tailscale Setup]]
This tutorial walks through setting up the foundational services that your GitOps infrastructure depends on: a git forge and optionally a container registry.

View file

@ -112,12 +112,12 @@ Tags must be defined in ACLs before use.
## Next Steps
With networking established:
- [[tutorials/replication/core-services | Set Up Core Services]] - Install Forgejo and optionally a container registry
- [[tutorials/replication/kubernetes-bootstrap | Bootstrap Kubernetes]] - Your cluster will join the tailnet
- Set up your server and storage devices
## BlumeOps Specifics
BluemeOps' Tailscale configuration includes:
BlumeOps' Tailscale configuration includes:
- Multiple device tags (`homelab`, `nas`, `registry`, `k8s-api`)
- Group-based access for family members
- SSH access rules with authentication requirements

View file

@ -7,7 +7,7 @@
"""Validate that all wiki-links in documentation point to existing files.
This script scans all markdown files in the docs/ directory (excluding
changelog.d/ and zk/), extracts wiki-links, and verifies each link target
changelog.d/), extracts wiki-links, and verifies each link target
exists as a filename or path in the documentation.
Wiki-link formats supported:
@ -61,9 +61,9 @@ def main() -> int:
# Track which filenames are ambiguous (appear multiple times)
filename_counts: dict[str, list[str]] = {}
# Scan all markdown files (excluding zk/ and changelog.d/)
# Scan all markdown files (excluding changelog.d/)
for md_file in DOCS_DIR.rglob("*.md"):
if "changelog.d" in md_file.parts or "zk" in md_file.parts:
if "changelog.d" in md_file.parts:
continue
# Track filename occurrences
filename = md_file.stem
@ -86,9 +86,9 @@ def main() -> int:
broken_links: list[tuple[str, int, str]] = []
ambiguous_links: list[tuple[str, int, str, list[str]]] = []
# Scan all markdown files for wiki-links (excluding zk/ and changelog.d/)
# Scan all markdown files for wiki-links (excluding changelog.d/)
for md_file in sorted(DOCS_DIR.rglob("*.md")):
if "changelog.d" in md_file.parts or "zk" in md_file.parts:
if "changelog.d" in md_file.parts:
continue
rel_path = str(md_file.relative_to(DOCS_DIR))

88
mise-tasks/doc-random Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["rich>=13.0.0"]
# ///
#MISE description="Select a random documentation card for review"
"""Select a random documentation card for review.
This script scans all markdown files in the docs/ directory (excluding
changelog.d/), selects one at random, and displays it for review.
Useful for periodic knowledge base maintenance and verification.
Usage: mise run doc-random
"""
import random
import sys
from pathlib import Path
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
DOCS_DIR = Path(__file__).parent.parent / "docs"
def get_all_docs() -> list[Path]:
"""Get all documentation markdown files, excluding changelog.d."""
docs = []
for md_file in DOCS_DIR.rglob("*.md"):
# Skip changelog fragments
if "changelog.d" in md_file.parts:
continue
docs.append(md_file)
return docs
def main() -> int:
console = Console()
docs = get_all_docs()
if not docs:
console.print("[bold red]No documentation files found![/bold red]")
return 1
# Select a random document
selected = random.choice(docs)
rel_path = selected.relative_to(DOCS_DIR)
# Display header
console.print()
console.print(Panel(
f"[bold cyan]{rel_path}[/bold cyan]\n"
f"[dim]{len(docs)} total docs in knowledge base[/dim]",
title="[bold]Random Documentation Card[/bold]",
border_style="cyan",
))
console.print()
# Display the file content
content = selected.read_text()
console.print(Markdown(content))
# Review checklist
console.print()
console.print(Panel(
"[bold]Review Checklist:[/bold]\n\n"
"• Is the information accurate and up-to-date?\n"
"• Are there broken or missing wiki-links?\n"
"• Should this card link to other related cards?\n"
"• Is the card too large and should be split?\n"
"• Is the card too small and should be merged?\n"
"• Does the frontmatter (tags, title) make sense?\n"
"• Is the card in the correct category (reference/how-to/etc)?\n\n"
"[bold]Verify Deployed State:[/bold]\n\n"
"• If ArgoCD app: is it synced? (argocd app get <app>)\n"
"• If Ansible role: does it apply idempotently? (--check --diff)\n"
"• If Pulumi: is there drift? (pulumi preview)",
title="[bold yellow]Review Guidance[/bold yellow]",
border_style="yellow",
))
return 0
if __name__ == "__main__":
sys.exit(main())