## Summary - Rename `date-modified` -> `modified` in all 80 docs and the `docs-check-frontmatter` task Quartz's `CreatedModifiedDate` plugin recognizes `modified`, `lastmod`, `updated`, and `last-modified` — but not `date-modified`. The wrong field name caused Quartz to ignore frontmatter dates entirely and fall through to filesystem timestamps (UTC inside Dagger), showing Feb 12 on pages built late on Feb 11 PST. ## Test plan - [x] `mise run docs-check-frontmatter` passes - [ ] Kick off docs release after merge — verify rendered dates match frontmatter values Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/158
3.2 KiB
| title | modified | tags | |||
|---|---|---|---|---|---|
| Caddy | 2026-02-08 |
|
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.mevia 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 (now publicly available at docs.eblu.me via flyio-proxy) |
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:
# Deploy caddy changes
mise run provision-indri -- --tags caddy
Key files:
ansible/roles/caddy/defaults/main.yml- Service definitionsansible/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.
Security Considerations
Caddy has no authentication layer — it is a plain reverse proxy. Access control relies entirely on Tailscale ACLs restricting which devices can reach indri on port 443. Currently tag:homelab and autogroup:admin can reach Caddy. The flyio-proxy no longer routes through Caddy — it pushes logs and metrics directly to loki and prometheus via their Tailscale Ingress endpoints.
Custom Build
Caddy is built from source with the Gandi DNS plugin:
# Build location
~/code/3rd/caddy/bin/caddy
The build includes the github.com/caddy-dns/gandi plugin for ACME DNS-01 challenges.
Related
- gandi - DNS hosting and ACME DNS-01 provider
- 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