Adds the Adelaide / Heidi / Addie baby shower app — a Django guest
splash, raffle picker, and prize-assignment console — on ringtail k3s.
Public landing at shower.eblu.me (via fly proxy), tailnet admin at
shower.ops.eblu.me. App source: forge.eblu.me/eblume/adelaide-baby-shower-app,
wheel-published to the Forgejo Packages PyPI index.
Manifests under argocd/manifests/shower/: NFS-backed PVC for /app/media,
local-path PVC for SQLite, ExternalSecret pulling DJANGO_SECRET_KEY from
1Password (item "Shower (blumeops)"), Tailscale ProxyGroup ingress.
Defense-in-depth for the public surface:
- /admin/ blocked at the fly edge except /admin/login/ and /admin/logout/
- shower_auth rate limit on the login path
- new fail2ban filter+jail with a per-service shower-deny.conf
(nginx-deny action generalized to accept nginx_deny_file)
- django-axes (5 / 1h) keyed on (username, ip_address)
Plus: Caddy route on indri, Pulumi gandi CNAME, Grafana APM dashboard
mirroring docs-apm.json, runbook at how-to/operations/shower-app.md,
and a service-versions entry. X-Clacks-Overhead set on the new server
block — GNU Terry Pratchett.
Build: containers/shower/default.nix uses dockerTools to ship a
nixpkgs Python plus a startup wrapper that installs the wheel into
/app/data/.venv on first boot and execs gunicorn. Lets the wheel come
from forge PyPI without pinning hashes for every transitive dep.
Prerequisites tracked in the runbook (not yet executed):
- NFS share sifaka:/volume1/shower (manual Synology step)
- 1Password item "Shower (blumeops)" with secret-key field
- container build via `mise run container-build-and-release shower`
- Pulumi dns-up after merge
- fly certs add shower.eblu.me
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
4 KiB
YAML
123 lines
4 KiB
YAML
---
|
|
# Caddy reverse proxy configuration
|
|
# Caddy is built from ~/code/3rd/caddy with Gandi DNS and Layer 4 plugins
|
|
|
|
caddy_repo_dir: /Users/erichblume/code/3rd/caddy
|
|
caddy_binary: "{{ caddy_repo_dir }}/bin/caddy"
|
|
caddy_config_dir: /Users/erichblume/.config/caddy
|
|
caddy_data_dir: /Users/erichblume/.local/share/caddy
|
|
caddy_log_dir: /Users/erichblume/Library/Logs
|
|
|
|
# Gandi API token file (written by ansible, chmod 0600)
|
|
# Caddy reads this file for ACME DNS-01 challenges
|
|
caddy_gandi_token_file: /Users/erichblume/.config/caddy/gandi-token
|
|
|
|
# Domain configuration
|
|
caddy_domain: ops.eblu.me
|
|
|
|
# HTTPS port (443 is standard)
|
|
caddy_https_port: 443
|
|
|
|
# Services to proxy
|
|
# Format: { name: "service", host: "hostname", backend: "url" }
|
|
caddy_services:
|
|
# Indri-local services
|
|
- name: forge
|
|
host: "forge.{{ caddy_domain }}"
|
|
backend: "http://localhost:3001"
|
|
- name: registry
|
|
host: "registry.{{ caddy_domain }}"
|
|
backend: "http://localhost:5050"
|
|
- name: jellyfin
|
|
host: "jellyfin.{{ caddy_domain }}"
|
|
backend: "http://localhost:8096"
|
|
|
|
# K8s services (via Tailscale Ingress)
|
|
# Caddy proxies to existing Tailscale endpoints - traffic stays local
|
|
- name: grafana
|
|
host: "grafana.{{ caddy_domain }}"
|
|
backend: "https://grafana.tail8d86e.ts.net"
|
|
- name: argocd
|
|
host: "argocd.{{ caddy_domain }}"
|
|
backend: "https://argocd.tail8d86e.ts.net"
|
|
- name: prometheus
|
|
host: "prometheus.{{ caddy_domain }}"
|
|
backend: "https://prometheus.tail8d86e.ts.net"
|
|
- name: loki
|
|
host: "loki.{{ caddy_domain }}"
|
|
backend: "https://loki.tail8d86e.ts.net"
|
|
- name: miniflux
|
|
host: "feed.{{ caddy_domain }}"
|
|
backend: "https://feed.tail8d86e.ts.net"
|
|
- name: devpi
|
|
host: "pypi.{{ caddy_domain }}"
|
|
backend: "http://localhost:3141"
|
|
- name: kiwix
|
|
host: "kiwix.{{ caddy_domain }}"
|
|
backend: "https://kiwix.tail8d86e.ts.net"
|
|
- name: torrent
|
|
host: "torrent.{{ caddy_domain }}"
|
|
backend: "https://torrent.tail8d86e.ts.net"
|
|
- name: teslamate
|
|
host: "tesla.{{ caddy_domain }}"
|
|
backend: "https://tesla.tail8d86e.ts.net"
|
|
- name: immich
|
|
host: "photos.{{ caddy_domain }}"
|
|
backend: "https://photos.tail8d86e.ts.net"
|
|
- name: navidrome
|
|
host: "dj.{{ caddy_domain }}"
|
|
backend: "https://dj.tail8d86e.ts.net"
|
|
- name: homepage
|
|
host: "go.{{ caddy_domain }}"
|
|
backend: "https://go.tail8d86e.ts.net"
|
|
- name: docs
|
|
host: "docs.{{ caddy_domain }}"
|
|
kind: static
|
|
root: "{{ docs_content_dir }}"
|
|
try_html: true # Quartz: path → path/ → path.html → 404.html
|
|
- name: cv
|
|
host: "cv.{{ caddy_domain }}"
|
|
kind: static
|
|
root: "{{ cv_content_dir }}"
|
|
download_paths:
|
|
- path: /resume.pdf
|
|
filename: erich-blume-resume.pdf
|
|
- name: nvr
|
|
host: "nvr.{{ caddy_domain }}"
|
|
backend: "https://nvr.tail8d86e.ts.net"
|
|
- name: authentik
|
|
host: "authentik.{{ caddy_domain }}"
|
|
backend: "https://authentik.tail8d86e.ts.net"
|
|
cache_policy: spa
|
|
- name: ntfy
|
|
host: "ntfy.{{ caddy_domain }}"
|
|
backend: "https://ntfy.tail8d86e.ts.net"
|
|
- name: ollama
|
|
host: "ollama.{{ caddy_domain }}"
|
|
backend: "https://ollama.tail8d86e.ts.net"
|
|
- name: mealie
|
|
host: "meals.{{ caddy_domain }}"
|
|
backend: "https://meals.tail8d86e.ts.net"
|
|
- name: paperless
|
|
host: "paperless.{{ caddy_domain }}"
|
|
backend: "https://paperless.tail8d86e.ts.net"
|
|
- name: shower
|
|
host: "shower.{{ caddy_domain }}"
|
|
backend: "https://shower.tail8d86e.ts.net"
|
|
- name: sifaka
|
|
host: "nas.{{ caddy_domain }}"
|
|
backend: "http://sifaka:5000"
|
|
|
|
# Layer 4 (TCP) services
|
|
# Format: { port: external_port, backend: "host:port" }
|
|
caddy_tcp_services:
|
|
- port: 2222
|
|
backend: "localhost:2200" # Forgejo SSH
|
|
- port: 5432
|
|
backend: "pg.tail8d86e.ts.net:5432" # PostgreSQL (blumeops-pg)
|
|
- port: 5433
|
|
backend: "immich-pg.tail8d86e.ts.net:5432" # PostgreSQL (immich-pg)
|
|
- port: "{{ sifaka_node_exporter_port }}"
|
|
backend: "sifaka:{{ sifaka_node_exporter_port }}" # Sifaka node_exporter
|
|
- port: "{{ sifaka_smartctl_exporter_port }}"
|
|
backend: "sifaka:{{ sifaka_smartctl_exporter_port }}" # Sifaka smartctl_exporter
|