Convert wiki-link titles to lowercase slugs (#92)

## Summary
- Convert all frontmatter titles to lowercase-hyphenated format (e.g., `grafana-alloy` instead of `Grafana Alloy`)
- Update all wiki-links to use the new slug format
- Update `doc-titles` task to validate slug format (lowercase, hyphens only)

Quartz appears to require titles without spaces for wiki-link resolution.

## Deployment and Testing
- [x] Pre-commit hooks pass (`doc-titles` and `doc-links`)
- [ ] Build docs v1.0.8 and deploy
- [ ] Verify wiki-links resolve correctly (e.g., `[[grafana-alloy]]`)

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

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/92
This commit is contained in:
Erich Blume 2026-02-03 16:06:35 -08:00
commit 3e4b5c2dd3
36 changed files with 288 additions and 246 deletions

View file

@ -1,5 +1,5 @@
--- ---
title: README title: readme
--- ---
# BlumeOps Documentation # BlumeOps Documentation

View file

@ -0,0 +1 @@
Convert wiki-link titles to lowercase slugs for reliable Quartz resolution

View file

@ -1,11 +1,11 @@
--- ---
title: BlumeOps Documentation title: blumeops-documentation
--- ---
Welcome to the BlumeOps documentation. Welcome to the BlumeOps documentation.
[[README|Documentation Home]] - Temporary home while docs are being restructured (see [Diataxis](https://diataxis.fr/) restructuring plan) [[readme|Documentation Home]] - Temporary home while docs are being restructured (see [Diataxis](https://diataxis.fr/) restructuring plan)
## Sections ## Sections
- [[Reference]] - Technical reference cards for services, infrastructure, and operations - [[reference]] - Technical reference cards for services, infrastructure, and operations

View file

@ -1,5 +1,5 @@
--- ---
title: Reference title: reference
tags: tags:
- reference - reference
--- ---
@ -14,54 +14,54 @@ Individual service reference cards with URLs and configuration details.
| Service | Description | Location | | Service | Description | Location |
|---------|-------------|----------| |---------|-------------|----------|
| [[Grafana Alloy|Alloy]] | Observability collector (metrics & logs) | indri + k8s | | [[grafana-alloy|Alloy]] | Observability collector (metrics & logs) | indri + k8s |
| [[ArgoCD]] | GitOps continuous delivery | k8s | | [[argocd]] | GitOps continuous delivery | k8s |
| [[Borgmatic]] | Backup system | indri | | [[borgmatic]] | Backup system | indri |
| [[1Password]] | Secrets management | cloud + k8s | | [[1password]] | Secrets management | cloud + k8s |
| [[Forgejo]] | Git forge & CI/CD | indri | | [[forgejo]] | Git forge & CI/CD | indri |
| [[Grafana]] | Dashboards & visualization | k8s | | [[grafana]] | Dashboards & visualization | k8s |
| [[Immich]] | Photo management | k8s | | [[immich]] | Photo management | k8s |
| [[Jellyfin]] | Media server | indri | | [[jellyfin]] | Media server | indri |
| [[Kiwix]] | Offline Wikipedia & ZIM archives | k8s | | [[kiwix]] | Offline Wikipedia & ZIM archives | k8s |
| [[Loki]] | Log aggregation | k8s | | [[loki]] | Log aggregation | k8s |
| [[Miniflux]] | RSS feed reader | k8s | | [[miniflux]] | RSS feed reader | k8s |
| [[Navidrome]] | Music streaming | k8s | | [[navidrome]] | Music streaming | k8s |
| [[PostgreSQL]] | Database cluster | k8s | | [[postgresql]] | Database cluster | k8s |
| [[Prometheus]] | Metrics collection | k8s | | [[prometheus]] | Metrics collection | k8s |
| [[TeslaMate]] | Tesla data logger | k8s | | [[teslamate]] | Tesla data logger | k8s |
| [[Transmission]] | BitTorrent daemon | k8s | | [[transmission]] | BitTorrent daemon | k8s |
| [[Zot]] | Container registry | indri | | [[zot]] | Container registry | indri |
## Infrastructure ## Infrastructure
Host inventory and network configuration. Host inventory and network configuration.
- [[Host Inventory|Hosts]] - Device inventory - [[host-inventory|Hosts]] - Device inventory
- [[Indri]] - Primary server - [[indri]] - Primary server
- [[Gilbert]] - Development workstation - [[gilbert]] - Development workstation
- [[Tailscale]] - ACLs, groups, tags - [[tailscale]] - ACLs, groups, tags
- [[Service Routing|Routing]] - DNS domains, port mappings - [[service-routing|Routing]] - DNS domains, port mappings
## Kubernetes ## Kubernetes
Cluster configuration and application registry. Cluster configuration and application registry.
- [[Kubernetes Cluster|Cluster]] - Minikube specs, storage, networking - [[kubernetes-cluster|Cluster]] - Minikube specs, storage, networking
- [[ArgoCD Applications|Apps]] - ArgoCD application registry - [[argocd-applications|Apps]] - ArgoCD application registry
- [[External Secrets]] - Secrets management - [[external-secrets]] - Secrets management
## Storage ## Storage
Network storage and backup configuration. Network storage and backup configuration.
- [[Sifaka NAS|Sifaka]] - Synology NAS configuration - [[sifaka-nas|Sifaka]] - Synology NAS configuration
- [[PostgreSQL Storage]] - Database cluster - [[postgresql-storage]] - Database cluster
- [[Backup Policy|Backups]] - Backup policy and schedule - [[backup-policy|Backups]] - Backup policy and schedule
## Operations ## Operations
Operational concerns and their components. Operational concerns and their components.
- [[Observability]] - Metrics, logs, dashboards - [[observability]] - Metrics, logs, dashboards
- [[Backup]] - Data protection - [[backup]] - Data protection
- [[Disaster Recovery]] - Recovery procedures (TBD) - [[disaster-recovery]] - Recovery procedures (TBD)

View file

@ -1,5 +1,5 @@
--- ---
title: Gilbert title: gilbert
tags: tags:
- infrastructure - infrastructure
- host - host
@ -23,5 +23,5 @@ Managed via `Brewfile` and `mise.toml` in the blumeops repo.
## Related ## Related
- [[Indri]] - Server accessed from gilbert - [[indri]] - Server accessed from gilbert
- [[Kubernetes Cluster|Cluster]] - Remote k8s access - [[kubernetes-cluster|Cluster]] - Remote k8s access

View file

@ -1,5 +1,5 @@
--- ---
title: Host Inventory title: host-inventory
tags: tags:
- infrastructure - infrastructure
--- ---
@ -12,14 +12,14 @@ All devices connected via [Tailscale](https://login.tailscale.com/) tailnet `tai
| Host | Description | Card | | Host | Description | Card |
|------|-------------|------| |------|-------------|------|
| **Indri** | Mac Mini M1, 2020 - Primary server | [[Indri|Details]] | | **Indri** | Mac Mini M1, 2020 - Primary server | [[indri|Details]] |
| **Gilbert** | MacBook Air M4, 2025 - Workstation | [[Gilbert|Details]] | | **Gilbert** | MacBook Air M4, 2025 - Workstation | [[gilbert|Details]] |
| **[[Sifaka NAS|Sifaka]]** | Synology NAS - Storage & backups | [[Sifaka NAS|Details]] | | **[[sifaka-nas|Sifaka]]** | Synology NAS - Storage & backups | [[sifaka-nas|Details]] |
| **Mouse** | MacBook Air M2 - Allison's laptop | - | | **Mouse** | MacBook Air M2 - Allison's laptop | - |
| **UniFi** | UniFi Express 7 - Home WiFi | - | | **UniFi** | UniFi Express 7 - Home WiFi | - |
| **Dwarf** | iPad Air - Employer-provided, off tailnet | - | | **Dwarf** | iPad Air - Employer-provided, off tailnet | - |
## Related ## Related
- [[Tailscale]] - Network configuration - [[tailscale]] - Network configuration
- [[Service Routing|Routing]] - Service URLs - [[service-routing|Routing]] - Service URLs

View file

@ -1,5 +1,5 @@
--- ---
title: Indri title: indri
tags: tags:
- infrastructure - infrastructure
- host - host
@ -22,17 +22,17 @@ Primary BlumeOps server. Mac Mini M1 (2020).
## Services Hosted ## Services Hosted
**Native (via Ansible):** **Native (via Ansible):**
- [[Forgejo]] - Git forge - [[forgejo]] - Git forge
- [[Zot]] - Container registry - [[zot]] - Container registry
- [[Jellyfin]] - Media server - [[jellyfin]] - Media server
- [[Borgmatic]] - Backup system - [[borgmatic]] - Backup system
- [[Grafana Alloy|Alloy]] - Metrics/logs collector - [[grafana-alloy|Alloy]] - Metrics/logs collector
- Caddy - Reverse proxy for `*.ops.eblu.me` - Caddy - Reverse proxy for `*.ops.eblu.me`
**Kubernetes (via minikube):** **Kubernetes (via minikube):**
- [[ArgoCD Applications|All k8s applications]] - [[argocd-applications|All k8s applications]]
## Related ## Related
- [[Service Routing|Routing]] - Port mappings - [[service-routing|Routing]] - Port mappings
- [[Kubernetes Cluster|Cluster]] - Minikube details - [[kubernetes-cluster|Cluster]] - Minikube details

View file

@ -1,5 +1,5 @@
--- ---
title: Service Routing title: service-routing
tags: tags:
- infrastructure - infrastructure
- network - network
@ -25,20 +25,20 @@ DNS points to indri's Tailscale IP (100.98.163.89). TLS via Let's Encrypt (ACME
| Service | URL | Description | | Service | URL | Description |
|---------|-----|-------------| |---------|-----|-------------|
| Homepage | https://go.ops.eblu.me | Service dashboard | | Homepage | https://go.ops.eblu.me | Service dashboard |
| [[Forgejo]] | https://forge.ops.eblu.me | Git hosting (SSH: 2222) | | [[forgejo]] | https://forge.ops.eblu.me | Git hosting (SSH: 2222) |
| [[Zot]] | https://registry.ops.eblu.me | Container registry | | [[zot]] | https://registry.ops.eblu.me | Container registry |
| [[Grafana]] | https://grafana.ops.eblu.me | Dashboards | | [[grafana]] | https://grafana.ops.eblu.me | Dashboards |
| [[ArgoCD]] | https://argocd.ops.eblu.me | GitOps CD | | [[argocd]] | https://argocd.ops.eblu.me | GitOps CD |
| [[Prometheus]] | https://prometheus.ops.eblu.me | Metrics | | [[prometheus]] | https://prometheus.ops.eblu.me | Metrics |
| [[Loki]] | https://loki.ops.eblu.me | Logs | | [[loki]] | https://loki.ops.eblu.me | Logs |
| [[Miniflux]] | https://feed.ops.eblu.me | RSS reader | | [[miniflux]] | https://feed.ops.eblu.me | RSS reader |
| [[Kiwix]] | https://kiwix.ops.eblu.me | Offline Wikipedia | | [[kiwix]] | https://kiwix.ops.eblu.me | Offline Wikipedia |
| [[Transmission]] | https://torrent.ops.eblu.me | BitTorrent | | [[transmission]] | https://torrent.ops.eblu.me | BitTorrent |
| [[TeslaMate]] | https://tesla.ops.eblu.me | Tesla logger | | [[teslamate]] | https://tesla.ops.eblu.me | Tesla logger |
| [[Navidrome]] | https://dj.ops.eblu.me | Music streaming | | [[navidrome]] | https://dj.ops.eblu.me | Music streaming |
| [[Jellyfin]] | https://jellyfin.ops.eblu.me | Media server | | [[jellyfin]] | https://jellyfin.ops.eblu.me | Media server |
| [[PostgreSQL]] | pg.ops.eblu.me:5432 | Database | | [[postgresql]] | pg.ops.eblu.me:5432 | Database |
| [[Sifaka NAS|Sifaka]] | https://nas.ops.eblu.me | NAS dashboard | | [[sifaka-nas|Sifaka]] | https://nas.ops.eblu.me | NAS dashboard |
## Tailscale-Only Services ## Tailscale-Only Services
@ -61,5 +61,5 @@ DNS points to indri's Tailscale IP (100.98.163.89). TLS via Let's Encrypt (ACME
## Related ## Related
- [[Tailscale]] - ACL configuration - [[tailscale]] - ACL configuration
- [[Indri]] - Where services run - [[indri]] - Where services run

View file

@ -1,5 +1,5 @@
--- ---
title: Tailscale title: tailscale
tags: tags:
- infrastructure - infrastructure
- network - network
@ -17,7 +17,7 @@ ACLs managed via Pulumi in `pulumi/policy.hujson`.
| Group | Members | Purpose | | Group | Members | Purpose |
|-------|---------|---------| |-------|---------|---------|
| `group:allisonflix` | admin, member | [[Jellyfin]] media access | | `group:allisonflix` | admin, member | [[jellyfin]] media access |
## Device Tags ## Device Tags
@ -58,5 +58,5 @@ Pulumi uses OAuth client from 1Password (blumeops vault):
## Related ## Related
- [[Service Routing|Routing]] - Service URLs - [[service-routing|Routing]] - Service URLs
- [[Host Inventory|Hosts]] - Device inventory - [[host-inventory|Hosts]] - Device inventory

View file

@ -1,5 +1,5 @@
--- ---
title: ArgoCD Applications title: argocd-applications
tags: tags:
- kubernetes - kubernetes
- argocd - argocd
@ -7,33 +7,33 @@ tags:
# ArgoCD Applications # ArgoCD Applications
Registry of all applications deployed via [[ArgoCD]]. Registry of all applications deployed via [[argocd]].
## Application Registry ## Application Registry
| App | Namespace | Path/Source | Service | | App | Namespace | Path/Source | Service |
|-----|-----------|-------------|---------| |-----|-----------|-------------|---------|
| `apps` | argocd | `argocd/apps/` | App-of-apps root | | `apps` | argocd | `argocd/apps/` | App-of-apps root |
| `argocd` | argocd | `argocd/manifests/argocd/` | [[ArgoCD]] | | `argocd` | argocd | `argocd/manifests/argocd/` | [[argocd]] |
| `tailscale-operator` | tailscale | `argocd/manifests/tailscale-operator/` | Tailscale k8s operator | | `tailscale-operator` | tailscale | `argocd/manifests/tailscale-operator/` | Tailscale k8s operator |
| `1password-connect` | 1password | `argocd/manifests/1password-connect/` | [[1Password]] | | `1password-connect` | 1password | `argocd/manifests/1password-connect/` | [[1password]] |
| `external-secrets` | external-secrets | Helm chart | [[1Password]] | | `external-secrets` | external-secrets | Helm chart | [[1password]] |
| `external-secrets-config` | external-secrets | `argocd/manifests/external-secrets-config/` | [[1Password]] | | `external-secrets-config` | external-secrets | `argocd/manifests/external-secrets-config/` | [[1password]] |
| `cloudnative-pg` | cnpg-system | Helm chart (forge mirror) | PostgreSQL operator | | `cloudnative-pg` | cnpg-system | Helm chart (forge mirror) | PostgreSQL operator |
| `blumeops-pg` | databases | `argocd/manifests/databases/` | [[PostgreSQL]] | | `blumeops-pg` | databases | `argocd/manifests/databases/` | [[postgresql]] |
| `prometheus` | monitoring | `argocd/manifests/prometheus/` | [[Prometheus]] | | `prometheus` | monitoring | `argocd/manifests/prometheus/` | [[prometheus]] |
| `loki` | monitoring | `argocd/manifests/loki/` | [[Loki]] | | `loki` | monitoring | `argocd/manifests/loki/` | [[loki]] |
| `grafana` | monitoring | Helm chart (forge mirror) | [[Grafana]] | | `grafana` | monitoring | Helm chart (forge mirror) | [[grafana]] |
| `grafana-config` | monitoring | `argocd/manifests/grafana-config/` | [[Grafana]] | | `grafana-config` | monitoring | `argocd/manifests/grafana-config/` | [[grafana]] |
| `immich` | immich | Helm chart | [[Immich]] | | `immich` | immich | Helm chart | [[immich]] |
| `alloy-k8s` | alloy | `argocd/manifests/alloy-k8s/` | [[Grafana Alloy|Alloy]] | | `alloy-k8s` | alloy | `argocd/manifests/alloy-k8s/` | [[grafana-alloy|Alloy]] |
| `kube-state-metrics` | monitoring | `argocd/manifests/kube-state-metrics/` | K8s metrics | | `kube-state-metrics` | monitoring | `argocd/manifests/kube-state-metrics/` | K8s metrics |
| `miniflux` | miniflux | `argocd/manifests/miniflux/` | [[Miniflux]] | | `miniflux` | miniflux | `argocd/manifests/miniflux/` | [[miniflux]] |
| `kiwix` | kiwix | `argocd/manifests/kiwix/` | [[Kiwix]] | | `kiwix` | kiwix | `argocd/manifests/kiwix/` | [[kiwix]] |
| `torrent` | torrent | `argocd/manifests/torrent/` | [[Transmission]] | | `torrent` | torrent | `argocd/manifests/torrent/` | [[transmission]] |
| `navidrome` | navidrome | `argocd/manifests/navidrome/` | [[Navidrome]] | | `navidrome` | navidrome | `argocd/manifests/navidrome/` | [[navidrome]] |
| `teslamate` | teslamate | `argocd/manifests/teslamate/` | [[TeslaMate]] | | `teslamate` | teslamate | `argocd/manifests/teslamate/` | [[teslamate]] |
| `forgejo-runner` | forgejo-runner | `argocd/manifests/forgejo-runner/` | [[Forgejo]] CI | | `forgejo-runner` | forgejo-runner | `argocd/manifests/forgejo-runner/` | [[forgejo]] CI |
## Sync Policies ## Sync Policies
@ -44,5 +44,5 @@ Registry of all applications deployed via [[ArgoCD]].
## Related ## Related
- [[ArgoCD]] - GitOps platform details - [[argocd]] - GitOps platform details
- [[Kubernetes Cluster|Cluster]] - Kubernetes infrastructure - [[kubernetes-cluster|Cluster]] - Kubernetes infrastructure

View file

@ -1,12 +1,12 @@
--- ---
title: Kubernetes Cluster title: kubernetes-cluster
tags: tags:
- kubernetes - kubernetes
--- ---
# Kubernetes Cluster # Kubernetes Cluster
Single-node Minikube cluster running on [[Indri]]. Single-node Minikube cluster running on [[indri]].
## Cluster Specifications ## Cluster Specifications
@ -24,16 +24,16 @@ Single-node Minikube cluster running on [[Indri]].
## Volume Mounting ## Volume Mounting
Pods mount NFS directly from [[Sifaka NAS|Sifaka]]. Docker NATs outbound traffic through indri's LAN IP (192.168.1.50), allowing access to Sifaka's NFS exports. Pods mount NFS directly from [[sifaka-nas|Sifaka]]. Docker NATs outbound traffic through indri's LAN IP (192.168.1.50), allowing access to Sifaka's NFS exports.
## Registry Mirror ## Registry Mirror
Containerd uses [[Zot]] as a pull-through cache at `host.minikube.internal:5050`. Containerd uses [[zot]] as a pull-through cache at `host.minikube.internal:5050`.
Mirrors configured: `registry.ops.eblu.me`, `docker.io`, `ghcr.io`, `quay.io` Mirrors configured: `registry.ops.eblu.me`, `docker.io`, `ghcr.io`, `quay.io`
## Related ## Related
- [[ArgoCD Applications|Apps]] - ArgoCD applications - [[argocd-applications|Apps]] - ArgoCD applications
- [[ArgoCD]] - GitOps deployment - [[argocd]] - GitOps deployment
- [[Zot]] - Registry mirror - [[zot]] - Registry mirror

View file

@ -1,5 +1,5 @@
--- ---
title: External Secrets title: external-secrets
tags: tags:
- kubernetes - kubernetes
- secrets - secrets
@ -7,4 +7,4 @@ tags:
# External Secrets # External Secrets
See [[1Password]] in Services. See [[1password]] in Services.

View file

@ -1,5 +1,5 @@
--- ---
title: Backup title: backup
tags: tags:
- operations - operations
--- ---
@ -10,6 +10,6 @@ Daily automated backups of BlumeOps data.
## Components ## Components
- [[Borgmatic]] - Backup orchestration - [[borgmatic]] - Backup orchestration
- [[Sifaka NAS|Sifaka]] - Backup target (NAS) - [[sifaka-nas|Sifaka]] - Backup target (NAS)
- [[Backup Policy]] - What gets backed up and retention - [[backup-policy]] - What gets backed up and retention

View file

@ -1,5 +1,5 @@
--- ---
title: Disaster Recovery title: disaster-recovery
tags: tags:
- operations - operations
--- ---
@ -8,12 +8,12 @@ tags:
TBD. Current state: TBD. Current state:
- [[Borgmatic]] provides daily backups to [[Sifaka NAS|Sifaka]] - [[borgmatic]] provides daily backups to [[sifaka-nas|Sifaka]]
- Infrastructure can be rebootstrapped using the blumeops repo - Infrastructure can be rebootstrapped using the blumeops repo
- Detailed DR procedures not yet documented - Detailed DR procedures not yet documented
## Components ## Components
- [[Borgmatic]] - Backup restoration - [[borgmatic]] - Backup restoration
- [[1Password]] - Credential recovery - [[1password]] - Credential recovery
- [[Forgejo]] - Source of truth for infrastructure code - [[forgejo]] - Source of truth for infrastructure code

View file

@ -1,5 +1,5 @@
--- ---
title: Observability title: observability
tags: tags:
- operations - operations
--- ---
@ -10,7 +10,7 @@ Metrics, logs, and dashboards for BlumeOps infrastructure.
## Components ## Components
- [[Prometheus]] - Metrics storage and querying - [[prometheus]] - Metrics storage and querying
- [[Loki]] - Log aggregation - [[loki]] - Log aggregation
- [[Grafana Alloy|Alloy]] - Metrics and log collection - [[grafana-alloy|Alloy]] - Metrics and log collection
- [[Grafana]] - Dashboards and visualization - [[grafana]] - Dashboards and visualization

View file

@ -1,5 +1,5 @@
--- ---
title: 1Password title: 1password
tags: tags:
- service - service
- secrets - secrets
@ -36,5 +36,5 @@ Services reference 1Password items via `ExternalSecret` manifests.
## Related ## Related
- [[ArgoCD]] - Uses secrets for git access - [[argocd]] - Uses secrets for git access
- [[PostgreSQL]] - Database credentials - [[postgresql]] - Database credentials

View file

@ -1,5 +1,5 @@
--- ---
title: Grafana Alloy title: grafana-alloy
tags: tags:
- service - service
- observability - observability
@ -27,7 +27,7 @@ Unified observability collector for metrics and logs with two deployments:
- System metrics via `prometheus.exporter.unix` - System metrics via `prometheus.exporter.unix`
- Textfile collector: `minikube.prom`, `borgmatic.prom`, `zot.prom`, `jellyfin.prom` - Textfile collector: `minikube.prom`, `borgmatic.prom`, `zot.prom`, `jellyfin.prom`
- Zot registry metrics from `http://localhost:5050/metrics` - Zot registry metrics from `http://localhost:5050/metrics`
- Pushed to [[Prometheus]] via remote_write - Pushed to [[prometheus]] via remote_write
### From Kubernetes ### From Kubernetes
- All pod logs via `loki.source.kubernetes` - All pod logs via `loki.source.kubernetes`
@ -39,7 +39,7 @@ Unified observability collector for metrics and logs with two deployments:
**mcquack LaunchAgents:** alloy, borgmatic, zot, jellyfin **mcquack LaunchAgents:** alloy, borgmatic, zot, jellyfin
Logs pushed to [[Loki]] at `https://loki.tail8d86e.ts.net/loki/api/v1/push`. Logs pushed to [[loki]] at `https://loki.tail8d86e.ts.net/loki/api/v1/push`.
## Why Built from Source ## Why Built from Source
@ -49,6 +49,6 @@ The Homebrew bottle uses `CGO_ENABLED=0`, which breaks Tailscale MagicDNS. Build
## Related ## Related
- [[Prometheus]] - Metrics storage - [[prometheus]] - Metrics storage
- [[Loki]] - Log storage - [[loki]] - Log storage
- [[Grafana]] - Visualization - [[grafana]] - Visualization

View file

@ -1,5 +1,5 @@
--- ---
title: ArgoCD title: argocd
tags: tags:
- service - service
- gitops - gitops
@ -7,7 +7,7 @@ tags:
# ArgoCD # ArgoCD
GitOps continuous delivery platform for the [[Kubernetes Cluster|Kubernetes cluster]]. GitOps continuous delivery platform for the [[kubernetes-cluster|Kubernetes cluster]].
## Quick Reference ## Quick Reference
@ -33,5 +33,5 @@ GitOps continuous delivery platform for the [[Kubernetes Cluster|Kubernetes clus
## Related ## Related
- [[ArgoCD Applications|Apps]] - Full application registry - [[argocd-applications|Apps]] - Full application registry
- [[Forgejo]] - Git source - [[forgejo]] - Git source

View file

@ -1,5 +1,5 @@
--- ---
title: Borgmatic title: borgmatic
tags: tags:
- service - service
- backup - backup
@ -16,7 +16,7 @@ Daily backup system using Borg backup, running on indri.
| **Install** | mise (pipx) | | **Install** | mise (pipx) |
| **Config** | `~/.config/borgmatic/config.yaml` | | **Config** | `~/.config/borgmatic/config.yaml` |
| **Schedule** | Daily at 2:00 AM | | **Schedule** | Daily at 2:00 AM |
| **Repository** | `/Volumes/backups/borg/` on [[Sifaka NAS|Sifaka]] | | **Repository** | `/Volumes/backups/borg/` on [[sifaka-nas|Sifaka]] |
## What Gets Backed Up ## What Gets Backed Up
@ -28,8 +28,8 @@ Daily backup system using Borg backup, running on indri.
- `~/Pictures` - Photos - `~/Pictures` - Photos
**Databases:** **Databases:**
- `miniflux` on [[PostgreSQL]] - `miniflux` on [[postgresql]]
- `teslamate` on [[PostgreSQL]] - `teslamate` on [[postgresql]]
**Not backed up (by design):** **Not backed up (by design):**
- ZIM archives (re-downloadable) - ZIM archives (re-downloadable)
@ -46,15 +46,15 @@ Daily backup system using Borg backup, running on indri.
## Monitoring ## Monitoring
Metrics exposed via textfile collector to [[Prometheus]]: Metrics exposed via textfile collector to [[prometheus]]:
- `borgmatic_up` - Repository accessibility - `borgmatic_up` - Repository accessibility
- `borgmatic_last_archive_timestamp` - Last backup time - `borgmatic_last_archive_timestamp` - Last backup time
- `borgmatic_repo_deduplicated_size_bytes` - Disk usage - `borgmatic_repo_deduplicated_size_bytes` - Disk usage
Dashboard: "Borgmatic Backups" in [[Grafana]] Dashboard: "Borgmatic Backups" in [[grafana]]
## Related ## Related
- [[Backup Policy|Backups]] - Full backup policy - [[backup-policy|Backups]] - Full backup policy
- [[Sifaka NAS|Sifaka]] - Backup target - [[sifaka-nas|Sifaka]] - Backup target
- [[PostgreSQL]] - Database backups - [[postgresql]] - Database backups

View file

@ -1,5 +1,5 @@
--- ---
title: Forgejo title: forgejo
tags: tags:
- service - service
- git - git
@ -44,5 +44,5 @@ Managed via 1Password: `lfs-jwt-secret`, `internal-token`, `oauth2-jwt-secret`,
## Related ## Related
- [[ArgoCD]] - Uses Forgejo as git source - [[argocd]] - Uses Forgejo as git source
- [[Zot]] - Container registry for built images - [[zot]] - Container registry for built images

View file

@ -1,5 +1,5 @@
--- ---
title: Grafana title: grafana
tags: tags:
- service - service
- observability - observability
@ -45,6 +45,6 @@ Optional annotation: `grafana_folder: "FolderName"`
## Related ## Related
- [[Prometheus]] - Metrics datasource - [[prometheus]] - Metrics datasource
- [[Loki]] - Logs datasource - [[loki]] - Logs datasource
- [[Grafana Alloy|Alloy]] - Data collector - [[grafana-alloy|Alloy]] - Data collector

View file

@ -1,5 +1,5 @@
--- ---
title: Immich title: immich
tags: tags:
- service - service
- media - media
@ -16,11 +16,11 @@ Self-hosted photo and video management.
| **URL** | https://photos.ops.eblu.me | | **URL** | https://photos.ops.eblu.me |
| **Namespace** | `immich` | | **Namespace** | `immich` |
| **Deployment** | Helm chart (k8s) | | **Deployment** | Helm chart (k8s) |
| **Database** | [[PostgreSQL]] (CNPG) | | **Database** | [[postgresql]] (CNPG) |
| **Storage** | [[Sifaka NAS|Sifaka]] photos volume | | **Storage** | [[sifaka-nas|Sifaka]] photos volume |
## Related ## Related
- [[PostgreSQL]] - Database backend - [[postgresql]] - Database backend
- [[Sifaka NAS|Sifaka]] - Photo storage - [[sifaka-nas|Sifaka]] - Photo storage
- [[Jellyfin]] - Video streaming (separate service) - [[jellyfin]] - Video streaming (separate service)

View file

@ -1,5 +1,5 @@
--- ---
title: Jellyfin title: jellyfin
tags: tags:
- service - service
- media - media
@ -42,10 +42,10 @@ Dashboard > Playback:
## Observability ## Observability
- Metrics: `jellyfin_metrics` ansible role - Metrics: `jellyfin_metrics` ansible role
- Logs: Forwarded via [[Grafana Alloy|Alloy]] - Logs: Forwarded via [[grafana-alloy|Alloy]]
- Dashboard: "Jellyfin Media Server" in [[Grafana]] - Dashboard: "Jellyfin Media Server" in [[grafana]]
## Related ## Related
- [[Navidrome]] - Music streaming - [[navidrome]] - Music streaming
- [[Sifaka NAS|Sifaka]] - Media storage - [[sifaka-nas|Sifaka]] - Media storage

View file

@ -1,5 +1,5 @@
--- ---
title: Kiwix title: kiwix
tags: tags:
- service - service
- knowledge - knowledge
@ -17,14 +17,14 @@ Offline Wikipedia and ZIM archive server.
| **Tailscale URL** | https://kiwix.tail8d86e.ts.net | | **Tailscale URL** | https://kiwix.tail8d86e.ts.net |
| **Namespace** | `kiwix` | | **Namespace** | `kiwix` |
| **Image** | `ghcr.io/kiwix/kiwix-serve:3.8.1` | | **Image** | `ghcr.io/kiwix/kiwix-serve:3.8.1` |
| **Storage** | NFS from [[Sifaka NAS|Sifaka]] (`/volume1/torrents`) | | **Storage** | NFS from [[sifaka-nas|Sifaka]] (`/volume1/torrents`) |
## Architecture ## Architecture
| Component | Purpose | | Component | Purpose |
|-----------|---------| |-----------|---------|
| kiwix-serve | Serves ZIM files on port 80 | | kiwix-serve | Serves ZIM files on port 80 |
| torrent-sync | Sidecar syncing ZIM torrents to [[Transmission]] | | torrent-sync | Sidecar syncing ZIM torrents to [[transmission]] |
| zim-watcher | CronJob (hourly) to restart on new ZIMs | | zim-watcher | CronJob (hourly) to restart on new ZIMs |
## Configured Archives ## Configured Archives
@ -43,10 +43,10 @@ Full list: `argocd/manifests/kiwix/configmap-zim-torrents.yaml`
1. Edit `configmap-zim-torrents.yaml` 1. Edit `configmap-zim-torrents.yaml`
2. Add torrent URL from https://download.kiwix.org/zim/ 2. Add torrent URL from https://download.kiwix.org/zim/
3. Sync: `argocd app sync kiwix` 3. Sync: `argocd app sync kiwix`
4. Torrent-sync adds to [[Transmission]] 4. Torrent-sync adds to [[transmission]]
5. zim-watcher restarts kiwix when download completes 5. zim-watcher restarts kiwix when download completes
## Related ## Related
- [[Transmission]] - Downloads ZIM files - [[transmission]] - Downloads ZIM files
- [[Sifaka NAS|Sifaka]] - ZIM storage - [[sifaka-nas|Sifaka]] - ZIM storage

View file

@ -1,5 +1,5 @@
--- ---
title: Loki title: loki
tags: tags:
- service - service
- observability - observability
@ -24,8 +24,8 @@ Log aggregation system for BlumeOps infrastructure.
- Single-node deployment with filesystem storage - Single-node deployment with filesystem storage
- TSDB index with 24h period - TSDB index with 24h period
- Logs collected by [[Grafana Alloy|Alloy]] and pushed via Loki API - Logs collected by [[grafana-alloy|Alloy]] and pushed via Loki API
- Queried via [[Grafana]] - Queried via [[grafana]]
## Log Sources ## Log Sources
@ -46,6 +46,6 @@ Log aggregation system for BlumeOps infrastructure.
## Related ## Related
- [[Grafana Alloy|Alloy]] - Log collector - [[grafana-alloy|Alloy]] - Log collector
- [[Grafana]] - Log visualization - [[grafana]] - Log visualization
- [[Prometheus]] - Metrics counterpart - [[prometheus]] - Metrics counterpart

View file

@ -1,5 +1,5 @@
--- ---
title: Miniflux title: miniflux
tags: tags:
- service - service
- rss - rss
@ -17,7 +17,7 @@ Minimalist RSS/Atom feed reader.
| **Tailscale URL** | https://feed.tail8d86e.ts.net | | **Tailscale URL** | https://feed.tail8d86e.ts.net |
| **Namespace** | `miniflux` | | **Namespace** | `miniflux` |
| **Image** | `ghcr.io/miniflux/miniflux:latest` | | **Image** | `ghcr.io/miniflux/miniflux:latest` |
| **Database** | [[PostgreSQL]] | | **Database** | [[postgresql]] |
## Features ## Features
@ -33,9 +33,9 @@ Uses CloudNativePG cluster at `pg.ops.eblu.me`. Database user password stored in
## Backup ## Backup
Feed subscriptions and read state backed up via [[Borgmatic]] PostgreSQL hook. Feed subscriptions and read state backed up via [[borgmatic]] PostgreSQL hook.
## Related ## Related
- [[PostgreSQL]] - Database backend - [[postgresql]] - Database backend
- [[Borgmatic]] - Data backup - [[borgmatic]] - Data backup

View file

@ -1,5 +1,5 @@
--- ---
title: Navidrome title: navidrome
tags: tags:
- service - service
- media - media
@ -38,5 +38,5 @@ The `/data` directory contains SQLite database, configuration, and cache.
## Related ## Related
- [[Jellyfin]] - Video streaming - [[jellyfin]] - Video streaming
- [[Sifaka NAS|Sifaka]] - Music storage - [[sifaka-nas|Sifaka]] - Music storage

View file

@ -1,5 +1,5 @@
--- ---
title: PostgreSQL title: postgresql
tags: tags:
- service - service
- database - database
@ -23,8 +23,8 @@ Database cluster via CloudNativePG operator.
| Database | Owner | Purpose | | Database | Owner | Purpose |
|----------|-------|---------| |----------|-------|---------|
| miniflux | miniflux | [[Miniflux]] feed data | | miniflux | miniflux | [[miniflux]] feed data |
| teslamate | teslamate | [[TeslaMate]] vehicle data | | teslamate | teslamate | [[teslamate]] vehicle data |
## Users ## Users
@ -34,11 +34,11 @@ Database cluster via CloudNativePG operator.
| miniflux | app owner | Owns miniflux database | | miniflux | app owner | Owns miniflux database |
| teslamate | superuser | TeslaMate (needs extensions) | | teslamate | superuser | TeslaMate (needs extensions) |
| eblume | superuser | Admin access | | eblume | superuser | Admin access |
| borgmatic | pg_read_all_data | [[Borgmatic|Backup]] access | | borgmatic | pg_read_all_data | [[borgmatic|Backup]] access |
## Backup ## Backup
Backed up via [[Borgmatic]] `postgresql_databases` hook. Streams `pg_dump` directly to Borg (no intermediate files, no downtime). See [[Backup]] for overall backup policy. Backed up via [[borgmatic]] `postgresql_databases` hook. Streams `pg_dump` directly to Borg (no intermediate files, no downtime). See [[backup]] for overall backup policy.
## Credentials ## Credentials
@ -54,6 +54,6 @@ Backed up via [[Borgmatic]] `postgresql_databases` hook. Streams `pg_dump` direc
## Related ## Related
- [[Miniflux]] - Feed reader database - [[miniflux]] - Feed reader database
- [[TeslaMate]] - Vehicle data database - [[teslamate]] - Vehicle data database
- [[Borgmatic]] - Database backup - [[borgmatic]] - Database backup

View file

@ -1,5 +1,5 @@
--- ---
title: Prometheus title: prometheus
tags: tags:
- service - service
- observability - observability
@ -23,19 +23,19 @@ Metrics storage and querying for BlumeOps infrastructure.
## Data Sources ## Data Sources
### Remote Write (from Alloy) ### Remote Write (from Alloy)
- Indri system metrics via [[Grafana Alloy|Alloy]] remote_write - Indri system metrics via [[grafana-alloy|Alloy]] remote_write
- Textfile metrics: minikube, borgmatic, zot, jellyfin - Textfile metrics: minikube, borgmatic, zot, jellyfin
### Scrape Targets ### Scrape Targets
| Target | Metrics | | Target | Metrics |
|--------|---------| |--------|---------|
| `sifaka:9100` | [[Sifaka NAS|Sifaka]] NAS (node_exporter) | | `sifaka:9100` | [[sifaka-nas|Sifaka]] NAS (node_exporter) |
| `cnpg-metrics.tail8d86e.ts.net:9187` | [[PostgreSQL|CloudNativePG]] metrics | | `cnpg-metrics.tail8d86e.ts.net:9187` | [[postgresql|CloudNativePG]] metrics |
| `kube-state-metrics.monitoring.svc:8080` | Kubernetes resource metrics | | `kube-state-metrics.monitoring.svc:8080` | Kubernetes resource metrics |
## Related ## Related
- [[Grafana Alloy|Alloy]] - Metrics collector - [[grafana-alloy|Alloy]] - Metrics collector
- [[Grafana]] - Visualization - [[grafana]] - Visualization
- [[Loki]] - Logs counterpart - [[loki]] - Logs counterpart

View file

@ -1,5 +1,5 @@
--- ---
title: TeslaMate title: teslamate
tags: tags:
- service - service
- vehicle - vehicle
@ -17,7 +17,7 @@ Self-hosted Tesla data logger collecting vehicle telemetry from the Tesla Owner
| **Tailscale URL** | https://tesla.tail8d86e.ts.net | | **Tailscale URL** | https://tesla.tail8d86e.ts.net |
| **Namespace** | `teslamate` | | **Namespace** | `teslamate` |
| **Image** | `teslamate/teslamate:2.2.0` | | **Image** | `teslamate/teslamate:2.2.0` |
| **Database** | [[PostgreSQL]] | | **Database** | [[postgresql]] |
## Data Collected ## Data Collected
@ -53,6 +53,6 @@ Uses Tesla Owner API via OAuth:
## Related ## Related
- [[PostgreSQL]] - Data storage - [[postgresql]] - Data storage
- [[Grafana]] - Dashboards - [[grafana]] - Dashboards
- [[Borgmatic]] - Database backup - [[borgmatic]] - Database backup

View file

@ -1,5 +1,5 @@
--- ---
title: Transmission title: transmission
tags: tags:
- service - service
- torrent - torrent
@ -7,7 +7,7 @@ tags:
# Transmission # Transmission
BitTorrent daemon, primarily for downloading ZIM archives for [[Kiwix]]. BitTorrent daemon, primarily for downloading ZIM archives for [[kiwix]].
## Quick Reference ## Quick Reference
@ -17,7 +17,7 @@ BitTorrent daemon, primarily for downloading ZIM archives for [[Kiwix]].
| **Tailscale URL** | https://torrent.tail8d86e.ts.net | | **Tailscale URL** | https://torrent.tail8d86e.ts.net |
| **Namespace** | `torrent` | | **Namespace** | `torrent` |
| **Image** | `lscr.io/linuxserver/transmission:latest` | | **Image** | `lscr.io/linuxserver/transmission:latest` |
| **Storage** | NFS PVC from [[Sifaka NAS|Sifaka]] | | **Storage** | NFS PVC from [[sifaka-nas|Sifaka]] |
## Storage Layout ## Storage Layout
@ -30,7 +30,7 @@ NFS share on sifaka (`/volume1/torrents`):
| `/config/` | Transmission configuration | | `/config/` | Transmission configuration |
| `/watch/` | Watch directory for .torrent files | | `/watch/` | Watch directory for .torrent files |
[[Kiwix]] reads from `/downloads/complete/` to serve ZIM archives. [[kiwix]] reads from `/downloads/complete/` to serve ZIM archives.
## Integration with Kiwix ## Integration with Kiwix
@ -43,11 +43,11 @@ When downloads complete, the zim-watcher CronJob detects new ZIMs and restarts K
## Monitoring ## Monitoring
Basic uptime via blackbox probe in [[Grafana Alloy|Alloy]] k8s (Services Health dashboard). Basic uptime via blackbox probe in [[grafana-alloy|Alloy]] k8s (Services Health dashboard).
Web UI shows: active/seeding/paused counts, speeds, disk usage. Web UI shows: active/seeding/paused counts, speeds, disk usage.
## Related ## Related
- [[Kiwix]] - ZIM archive consumer - [[kiwix]] - ZIM archive consumer
- [[Sifaka NAS|Sifaka]] - Download storage - [[sifaka-nas|Sifaka]] - Download storage

View file

@ -1,5 +1,5 @@
--- ---
title: Zot title: zot
tags: tags:
- service - service
- registry - registry
@ -30,7 +30,7 @@ OCI-native container registry providing pull-through cache and private image sto
## Pull-Through Cache ## Pull-Through Cache
When [[Kubernetes Cluster|minikube]] pulls an image, containerd checks zot first. If cached, returns immediately. If not, zot fetches from upstream, caches it, then returns. When [[kubernetes-cluster|minikube]] pulls an image, containerd checks zot first. If cached, returns immediately. If not, zot fetches from upstream, caches it, then returns.
## Security Model ## Security Model
@ -38,5 +38,5 @@ Network access only (no authentication). Defense is the Tailscale ACL boundary.
## Related ## Related
- [[Forgejo]] - Container build CI - [[forgejo]] - Container build CI
- [[Kubernetes Cluster|Cluster]] - Registry consumer - [[kubernetes-cluster|Cluster]] - Registry consumer

View file

@ -1,5 +1,5 @@
--- ---
title: Backup Policy title: backup-policy
tags: tags:
- storage - storage
- backup - backup
@ -7,13 +7,13 @@ tags:
# Backup Policy # Backup Policy
Daily automated backups from [[Indri]] to [[Sifaka NAS|Sifaka]] NAS. Daily automated backups from [[indri]] to [[sifaka-nas|Sifaka]] NAS.
## Schedule ## Schedule
| Time | Frequency | System | | Time | Frequency | System |
|------|-----------|--------| |------|-----------|--------|
| 2:00 AM | Daily | [[Borgmatic]] | | 2:00 AM | Daily | [[borgmatic]] |
## What Gets Backed Up ## What Gets Backed Up
@ -31,8 +31,8 @@ Daily automated backups from [[Indri]] to [[Sifaka NAS|Sifaka]] NAS.
| Database | Host | Method | | Database | Host | Method |
|----------|------|--------| |----------|------|--------|
| miniflux | [[PostgreSQL|pg.ops.eblu.me]] | pg_dump stream | | miniflux | [[postgresql|pg.ops.eblu.me]] | pg_dump stream |
| teslamate | [[PostgreSQL|pg.ops.eblu.me]] | pg_dump stream | | teslamate | [[postgresql|pg.ops.eblu.me]] | pg_dump stream |
## What Is NOT Backed Up ## What Is NOT Backed Up
@ -53,19 +53,19 @@ Daily automated backups from [[Indri]] to [[Sifaka NAS|Sifaka]] NAS.
## Backup Target ## Backup Target
Repository: `/Volumes/backups/borg/` on [[Sifaka NAS|Sifaka]] Repository: `/Volumes/backups/borg/` on [[sifaka-nas|Sifaka]]
## Monitoring ## Monitoring
Metrics exposed to [[Prometheus]]: Metrics exposed to [[prometheus]]:
- `borgmatic_up` - Repository accessible - `borgmatic_up` - Repository accessible
- `borgmatic_last_archive_timestamp` - Last backup time - `borgmatic_last_archive_timestamp` - Last backup time
- `borgmatic_repo_deduplicated_size_bytes` - Disk usage - `borgmatic_repo_deduplicated_size_bytes` - Disk usage
Dashboard: "Borgmatic Backups" in [[Grafana]] Dashboard: "Borgmatic Backups" in [[grafana]]
## Related ## Related
- [[Borgmatic]] - Backup system details - [[borgmatic]] - Backup system details
- [[Sifaka NAS|Sifaka]] - Backup storage - [[sifaka-nas|Sifaka]] - Backup storage
- [[PostgreSQL]] - Database backups - [[postgresql]] - Database backups

View file

@ -1,5 +1,5 @@
--- ---
title: PostgreSQL Storage title: postgresql-storage
tags: tags:
- storage - storage
- database - database
@ -7,4 +7,4 @@ tags:
# PostgreSQL Storage # PostgreSQL Storage
See [[PostgreSQL]] in Services. See [[postgresql]] in Services.

View file

@ -1,5 +1,5 @@
--- ---
title: Sifaka NAS title: sifaka-nas
tags: tags:
- storage - storage
--- ---
@ -21,11 +21,11 @@ Synology NAS providing network storage and backup target.
| Share | Path | Purpose | Consumers | | Share | Path | Purpose | Consumers |
|-------|------|---------|-----------| |-------|------|---------|-----------|
| backups | `/volume1/backups` | Borg backup repository | [[Borgmatic]] | | backups | `/volume1/backups` | Borg backup repository | [[borgmatic]] |
| torrents | `/volume1/torrents` | ZIM downloads | [[Kiwix]], [[Transmission]] | | torrents | `/volume1/torrents` | ZIM downloads | [[kiwix]], [[transmission]] |
| music | `/volume1/music` | Music library | [[Navidrome]] | | music | `/volume1/music` | Music library | [[navidrome]] |
| allisonflix | `/volume1/allisonflix` | Video library | [[Jellyfin]] | | allisonflix | `/volume1/allisonflix` | Video library | [[jellyfin]] |
| photos | `/volume1/photos` | Photo library | [[Immich]] | | photos | `/volume1/photos` | Photo library | [[immich]] |
## NFS Exports ## NFS Exports
@ -37,7 +37,7 @@ Synology NAS providing network storage and backup target.
## Monitoring ## Monitoring
Node exporter running in Docker container, scraped by [[Prometheus]] at `sifaka:9100`. Node exporter running in Docker container, scraped by [[prometheus]] at `sifaka:9100`.
## Tailscale ## Tailscale
@ -46,14 +46,14 @@ Node exporter running in Docker container, scraped by [[Prometheus]] at `sifaka:
## Backup ## Backup
Sifaka is the **target** for [[Backup|backups]], not a backup source. [[Borgmatic]] sends backups TO sifaka, not OF sifaka. Sifaka is the **target** for [[backup|backups]], not a backup source. [[borgmatic]] sends backups TO sifaka, not OF sifaka.
Data protection for sifaka itself currently relies on the Synology RAID 5 configuration, which provides single-disk fault tolerance. Future plans include offsite duplication for additional resiliency. Data protection for sifaka itself currently relies on the Synology RAID 5 configuration, which provides single-disk fault tolerance. Future plans include offsite duplication for additional resiliency.
## Related ## Related
- [[Backup Policy|Backups]] - Backup policy - [[backup-policy|Backups]] - Backup policy
- [[Borgmatic]] - Backup system - [[borgmatic]] - Backup system
- [[Immich]] - Photo consumer - [[immich]] - Photo consumer
- [[Jellyfin]] - Media consumer - [[jellyfin]] - Media consumer
- [[Navidrome]] - Music consumer - [[navidrome]] - Music consumer

View file

@ -3,19 +3,22 @@
# requires-python = ">=3.12" # requires-python = ">=3.12"
# dependencies = ["pyyaml>=6.0", "rich>=13.0.0"] # dependencies = ["pyyaml>=6.0", "rich>=13.0.0"]
# /// # ///
#MISE description="List all doc card titles and detect duplicates" #MISE description="List all doc card titles and detect duplicates or invalid formats"
"""List all documentation card titles and detect duplicates. """List all documentation card titles and detect duplicates or invalid formats.
This script scans all markdown files in the docs/ directory (excluding This script scans all markdown files in the docs/ directory (excluding
changelog.d/ and zk/), extracts frontmatter titles, and reports any changelog.d/ and zk/), extracts frontmatter titles, and reports any
duplicates that could cause wiki-link resolution issues. duplicates or invalid formats that could cause wiki-link resolution issues.
With Quartz, wiki-links like [[Title]] resolve by frontmatter title, With Quartz, wiki-links like [[title]] resolve by frontmatter title,
so titles must be unique across the documentation. so titles must be:
- Unique across the documentation
- Lowercase with hyphens (no spaces or uppercase)
Usage: mise run doc-titles Usage: mise run doc-titles
""" """
import re
import sys import sys
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
@ -26,6 +29,9 @@ from rich.table import Table
DOCS_DIR = Path(__file__).parent.parent / "docs" DOCS_DIR = Path(__file__).parent.parent / "docs"
# Valid title pattern: lowercase letters, numbers, hyphens only
VALID_TITLE_PATTERN = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
def extract_frontmatter(file_path: Path) -> dict | None: def extract_frontmatter(file_path: Path) -> dict | None:
"""Extract YAML frontmatter from a markdown file.""" """Extract YAML frontmatter from a markdown file."""
@ -44,12 +50,18 @@ def extract_frontmatter(file_path: Path) -> dict | None:
return None return None
def is_valid_slug(title: str) -> bool:
"""Check if title is a valid slug (lowercase, hyphens, no spaces)."""
return bool(VALID_TITLE_PATTERN.match(title))
def main() -> int: def main() -> int:
console = Console() console = Console()
# Collect all titles and their source files # Collect all titles and their source files
# Key: title, Value: list of file paths # Key: title, Value: list of file paths
titles: dict[str, list[str]] = defaultdict(list) titles: dict[str, list[str]] = defaultdict(list)
invalid_titles: list[tuple[str, str]] = [] # (title, file_path)
# Scan all markdown files (excluding zk/ and changelog.d/) # Scan all markdown files (excluding zk/ and changelog.d/)
for md_file in sorted(DOCS_DIR.rglob("*.md")): for md_file in sorted(DOCS_DIR.rglob("*.md")):
@ -66,6 +78,8 @@ def main() -> int:
title = frontmatter.get("title") title = frontmatter.get("title")
if title: if title:
titles[title].append(rel_path) titles[title].append(rel_path)
if not is_valid_slug(title):
invalid_titles.append((title, rel_path))
# Find duplicates # Find duplicates
duplicates = {title: paths for title, paths in titles.items() if len(paths) > 1} duplicates = {title: paths for title, paths in titles.items() if len(paths) > 1}
@ -73,12 +87,30 @@ def main() -> int:
# Print results # Print results
console.print("[bold]Doc Card Title Inventory[/bold]") console.print("[bold]Doc Card Title Inventory[/bold]")
console.print() console.print()
console.print("With Quartz, wiki-links like [[Title]] resolve by frontmatter title,") console.print("Titles must be lowercase slugs (e.g., 'grafana-alloy', not 'Grafana Alloy')")
console.print("so titles must be unique across the documentation.") console.print("and unique across the documentation for wiki-link resolution.")
console.print() console.print()
has_errors = False
# Invalid format table (if any)
if invalid_titles:
has_errors = True
console.print("[bold red]Invalid Title Format[/bold red]")
console.print("Titles must be lowercase with hyphens only (no spaces or uppercase).")
inv_table = Table(show_header=True, header_style="bold")
inv_table.add_column("Title")
inv_table.add_column("File")
for title, file_path in invalid_titles:
inv_table.add_row(title, file_path)
console.print(inv_table)
console.print()
# Duplicates table (if any) # Duplicates table (if any)
if duplicates: if duplicates:
has_errors = True
console.print("[bold red]Duplicate Titles Found[/bold red]") console.print("[bold red]Duplicate Titles Found[/bold red]")
dup_table = Table(show_header=True, header_style="bold") dup_table = Table(show_header=True, header_style="bold")
dup_table.add_column("Title") dup_table.add_column("Title")
@ -101,7 +133,15 @@ def main() -> int:
for title in sorted(titles.keys()): for title in sorted(titles.keys()):
paths = titles[title] paths = titles[title]
is_dup = title in duplicates is_dup = title in duplicates
status = "[red]DUPLICATE[/red]" if is_dup else "[green]OK[/green]" is_invalid = not is_valid_slug(title)
if is_dup:
status = "[red]DUPLICATE[/red]"
elif is_invalid:
status = "[red]INVALID[/red]"
else:
status = "[green]OK[/green]"
all_table.add_row(title, paths[0], status) all_table.add_row(title, paths[0], status)
for extra_path in paths[1:]: for extra_path in paths[1:]:
all_table.add_row("", extra_path, "") all_table.add_row("", extra_path, "")
@ -113,10 +153,11 @@ def main() -> int:
console.print(f"Total files: {sum(len(p) for p in titles.values())}") console.print(f"Total files: {sum(len(p) for p in titles.values())}")
console.print(f"Unique titles: {len(titles)}") console.print(f"Unique titles: {len(titles)}")
console.print(f"Duplicate titles: {len(duplicates)}") console.print(f"Duplicate titles: {len(duplicates)}")
console.print(f"Invalid format: {len(invalid_titles)}")
if duplicates: if has_errors:
console.print() console.print()
console.print("[bold red]Action required:[/bold red] Rename titles to ensure unique wiki-link resolution.") console.print("[bold red]Action required:[/bold red] Fix title issues above.")
return 1 return 1
return 0 return 0