Add Gandi DNS management via Pulumi (#54)
## Summary - Restructure Pulumi into separate projects: `pulumi/tailscale/` and `pulumi/gandi/` - Add Gandi LiveDNS management for `eblu.me` domain - Create wildcard DNS record `*.ops.eblu.me` → indri's Tailscale IP (100.98.163.89) - Add mise tasks: `dns-up`, `dns-preview` - Update `tailnet-up` to pass `--yes` by default - Document PAT cycling process (expires every 30 days) ## Background This enables using real DNS names (`*.ops.eblu.me`) that resolve to Tailscale IPs, which allows containers and other systems to resolve services without depending on MagicDNS. Since Tailscale IPs (100.x.x.x) are not publicly routable, services remain tailnet-only while using standard DNS. ## Deployment and Testing - [ ] Run `cd pulumi/gandi && uv sync` to install dependencies - [ ] Run `cd pulumi/gandi && pulumi stack init eblu-me` to create stack - [ ] Run `mise run dns-preview` to verify configuration - [ ] Run `mise run dns-up` to apply DNS records - [ ] Verify with `dig +short test.ops.eblu.me` returns `100.98.163.89` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.tail8d86e.ts.net/eblume/blumeops/pulls/54
This commit is contained in:
parent
af3536bc17
commit
b08faa50cc
17 changed files with 466 additions and 3 deletions
61
pulumi/gandi/__main__.py
Normal file
61
pulumi/gandi/__main__.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""Pulumi program to manage eblu.me DNS via Gandi LiveDNS.
|
||||
|
||||
This program manages DNS records for blumeops infrastructure:
|
||||
- Wildcard record for *.ops.eblu.me pointing to indri's Tailscale IP
|
||||
- indri hosts Caddy as the reverse proxy for all services
|
||||
- This allows services to be accessed via real DNS names while remaining
|
||||
tailnet-only (Tailscale IPs are not publicly routable)
|
||||
|
||||
Authentication:
|
||||
Set GANDI_PERSONAL_ACCESS_TOKEN environment variable.
|
||||
See README.md for PAT management instructions.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
|
||||
import pulumi
|
||||
import pulumiverse_gandi as gandi
|
||||
|
||||
# Get configuration
|
||||
config = pulumi.Config()
|
||||
domain = config.require("domain") # eblu.me
|
||||
subdomain = config.require("subdomain") # ops
|
||||
|
||||
# Resolve indri's Tailscale IP dynamically via MagicDNS
|
||||
# This script runs on the tailnet, so we can resolve the hostname directly.
|
||||
# indri hosts Caddy, which reverse-proxies all services.
|
||||
# Break-glass: set BLUMEOPS_REVERSE_PROXY_IP env var to override DNS resolution
|
||||
REVERSE_PROXY_HOST = "indri.tail8d86e.ts.net"
|
||||
tailscale_ip = os.environ.get("BLUMEOPS_REVERSE_PROXY_IP") or socket.gethostbyname(
|
||||
REVERSE_PROXY_HOST
|
||||
)
|
||||
|
||||
# Wildcard A record for *.ops.eblu.me
|
||||
# Points to indri's Tailscale IP, which is only routable within the tailnet.
|
||||
# This allows containers and other systems to resolve real DNS names
|
||||
# while keeping services private to the tailnet.
|
||||
wildcard_record = gandi.livedns.Record(
|
||||
"ops-wildcard",
|
||||
zone=domain,
|
||||
name=f"*.{subdomain}",
|
||||
type="A",
|
||||
ttl=300,
|
||||
values=[tailscale_ip],
|
||||
)
|
||||
|
||||
# Base subdomain record (ops.eblu.me) - same IP
|
||||
base_record = gandi.livedns.Record(
|
||||
"ops-base",
|
||||
zone=domain,
|
||||
name=subdomain,
|
||||
type="A",
|
||||
ttl=300,
|
||||
values=[tailscale_ip],
|
||||
)
|
||||
|
||||
# ============== Exports ==============
|
||||
pulumi.export("domain", domain)
|
||||
pulumi.export("wildcard_fqdn", f"*.{subdomain}.{domain}")
|
||||
pulumi.export("base_fqdn", f"{subdomain}.{domain}")
|
||||
pulumi.export("target_ip", tailscale_ip)
|
||||
Loading…
Add table
Add a link
Reference in a new issue