blumeops/pulumi/gandi
Erich Blume 6e37abda5d C1: deploy adelaide-baby-shower-app to ringtail k3s
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>
2026-05-11 08:14:12 -07:00
..
.gitignore Add Gandi DNS management via Pulumi (#54) 2026-01-25 08:15:46 -08:00
__main__.py C1: deploy adelaide-baby-shower-app to ringtail k3s 2026-05-11 08:14:12 -07:00
Pulumi.eblu-me.yaml Add Gandi DNS management via Pulumi (#54) 2026-01-25 08:15:46 -08:00
Pulumi.yaml Add Gandi DNS management via Pulumi (#54) 2026-01-25 08:15:46 -08:00
pyproject.toml Add Gandi DNS management via Pulumi (#54) 2026-01-25 08:15:46 -08:00
README.md C0: split gandi-operations docs; add dns-acme-cleanup mise task 2026-04-27 09:48:46 -07:00
uv.lock Add Fly.io public reverse proxy for docs.eblu.me (#120) 2026-02-08 02:36:19 -08:00

Gandi DNS Management

This Pulumi project manages DNS records for eblu.me via Gandi LiveDNS.

What It Does

Creates DNS records that point *.ops.eblu.me to indri's Tailscale IP.

Why indri? indri hosts Caddy, the reverse proxy for all blumeops services. All *.ops.eblu.me requests route through Caddy, which proxies to the appropriate backend service (either on indri itself or in the k8s cluster).

Since Tailscale IPs (100.x.x.x) are not routable on the public internet, these DNS records effectively make services accessible only from within the tailnet, while still using real, resolvable DNS names.

The target IP is resolved dynamically from indri.tail8d86e.ts.net at deploy time, so if indri's Tailscale IP changes, just re-run the deployment.

Setup

cd pulumi/gandi
uv sync
pulumi stack select eblu-me  # or: pulumi stack init eblu-me

Authentication

This project uses a Gandi Personal Access Token (PAT) shared with Caddy. See the Gandi reference card and Rotate the Gandi PAT.

The mise tasks handle fetching the PAT from 1Password:

mise run dns-preview   # Preview only
mise run dns-up        # Preview and apply

Or manually:

export GANDI_PERSONAL_ACCESS_TOKEN=$(op read "op://blumeops/gandi - blumeops/pat")
pulumi up

DNS Records Created

Record Type Value Purpose
*.ops.eblu.me A (indri's Tailscale IP) Wildcard for all services
ops.eblu.me A (indri's Tailscale IP) Base subdomain

Service Hostnames

Once Caddy is configured on indri, services will be accessible at:

  • forge.ops.eblu.me - Forgejo git server
  • registry.ops.eblu.me - Zot container registry
  • grafana.ops.eblu.me - Grafana dashboards
  • argocd.ops.eblu.me - ArgoCD
  • feed.ops.eblu.me - Miniflux RSS reader
  • pypi.ops.eblu.me - DevPI Python index
  • kiwix.ops.eblu.me - Kiwix offline content
  • tesla.ops.eblu.me - TeslaMate
  • torrent.ops.eblu.me - Transmission
  • prometheus.ops.eblu.me - Prometheus metrics
  • loki.ops.eblu.me - Loki logs