Add Gandi DNS docs and rewrite homepage intro (#115)

## Summary
- New reference card (`docs/reference/infrastructure/gandi.md`) covering DNS records, Pulumi config, TLS integration
- New how-to guide (`docs/how-to/gandi-operations.md`) for DNS deployment and PAT cycling with `pbpaste` shortcut
- Rewritten homepage intro for wider audience ahead of public docs.eblu.me
- Cross-linked from reference index, routing, caddy, and how-to index
- Fixed PAT expiration inaccuracy in `pulumi/gandi/README.md` (max is 90 days, not 30)

## Test plan
- [ ] Verify wiki-links resolve in Quartz build
- [ ] Review gandi reference card for accuracy
- [ ] Review gandi-operations how-to for accuracy
- [ ] Check homepage reads well for external visitors

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/115
This commit is contained in:
Erich Blume 2026-02-07 21:02:10 -08:00
commit 8e4afe77e0
9 changed files with 190 additions and 2 deletions

View file

@ -0,0 +1 @@
Add Gandi DNS reference card and operations how-to, rewrite homepage intro for wider audience.

View file

@ -0,0 +1,88 @@
---
title: gandi-operations
tags:
- how-to
- dns
- pulumi
---
# Gandi Operations
How to manage DNS records and cycle the Gandi API token.
## Prerequisites
- Pulumi CLI installed (`brew install pulumi`)
- Access to 1Password blumeops vault (for PAT)
- On the tailnet (Pulumi resolves indri's IP via MagicDNS)
## Preview and Apply DNS Changes
```bash
# Preview changes (always do this first)
mise run dns-preview
# Apply changes
mise run dns-up
```
Both tasks fetch the Gandi PAT from 1Password automatically.
To run Pulumi directly:
```bash
export GANDI_PERSONAL_ACCESS_TOKEN=$(op item get mco6ka3dc3rmw7zkg2dhia5d2m --field pat --reveal --vault vg6xf6vvfmoh5hqjjhlhbeoaie)
cd pulumi/gandi
pulumi preview
pulumi up --yes
```
## Cycle the Gandi PAT
The Gandi Personal Access Token has a maximum lifetime of 90 days. Currently set to 30 days as a security compromise, though shorter may be appropriate given infrequent use.
### 1. Create a new PAT
Go to the [Gandi admin console](https://admin.gandi.net/organizations/1db8d76a-f729-11ed-b8d1-00163e94b645/account/pat) and create a new token:
- **Name:** `blumeops-pulumi` (or similar)
- **Expiration:** 30 days (max 90; shorter is fine if you run this rarely)
- **Required permission:** Manage domain name technical configurations
- **Also enable:** See and renew domain names
Copy the new PAT to your clipboard.
### 2. Update 1Password
With the new PAT on your clipboard:
```bash
op item edit mco6ka3dc3rmw7zkg2dhia5d2m pat="$(pbpaste)" --vault vg6xf6vvfmoh5hqjjhlhbeoaie
```
### 3. Delete the old PAT
Return to the Gandi admin console and delete the previous token.
### 4. Verify
```bash
mise run dns-preview
```
A successful preview confirms the new PAT is working.
## Break-Glass Override
If MagicDNS is unavailable and Pulumi can't resolve indri's IP, set the target IP manually:
```bash
export BLUMEOPS_REVERSE_PROXY_IP=100.98.163.89
mise run dns-up
```
## Related
- [[gandi]] - DNS configuration reference
- [[caddy]] - Reverse proxy (also uses a Gandi token for TLS)
- [[update-tailscale-acls]] - Similar Pulumi workflow for Tailscale

View file

@ -20,6 +20,7 @@ Task-oriented instructions for common BlumeOps operations. These guides assume y
| Guide | Description | | Guide | Description |
|-------|-------------| |-------|-------------|
| [[update-tailscale-acls]] | Update Tailscale access control policies | | [[update-tailscale-acls]] | Update Tailscale access control policies |
| [[gandi-operations]] | Manage DNS records and cycle the Gandi API token |
| [[use-pypi-proxy]] | Configure pip and publish packages to devpi | | [[use-pypi-proxy]] | Configure pip and publish packages to devpi |
## Documentation ## Documentation

View file

@ -1,11 +1,39 @@
--- ---
title: blumeops-documentation title: blumeops-documentation
aliases: []
id: index
tags: []
--- ---
Welcome to the BlumeOps documentation. Welcome to the BlumeOps (aka "Blue Mops") documentation. Here you will find
hopefully everything you'll need to understand and operate my personal digital
infrastructure.
**New here?** Start with [[exploring-the-docs]] to find your way around. **New here?** Start with [[exploring-the-docs]] to find your way around.
## What is BlumeOps?
BlumeOps is my personal homelab infrastructure managed entirely through code.
Everything lives in a single git repository, from service configs to deployment
automation. Even the [[forgejo]] instance that hosts this repo is defined
within it, making BlumeOps fully self-hosting. It's a digital life raft I built
for myself as I went, and you can see it all from within your editor of choice.
(I recommend vim.)
These services run on my home [[hosts|infrastructure]], primarily an m1 mac
mini named [[indri]] and a Synology NAS called [[sifaka]]. The infrastructure
is networked via [[tailscale]], with the domain `eblu.me` hosted via [[gandi]]
with [[caddy]] providing a reverse proxy to resolve tailnet devices.
The goal of BlumeOps is threefold:
1. To provide a rich array of useful personal services in order to manage my
own digital life.
2. To exercise my skills as a software engineer specializing in
Platforms/DevOps/SRE.
3. To act as a portfolio piece for talking about building hosted software
platforms.
## Sections ## Sections
- [[tutorials|Tutorials]] - Learning-oriented guides for getting started - [[tutorials|Tutorials]] - Learning-oriented guides for getting started

View file

@ -0,0 +1,67 @@
---
title: gandi
tags:
- infrastructure
- networking
- dns
---
# Gandi
DNS hosting provider for the `eblu.me` domain, managed via Pulumi IaC.
## Quick Reference
| Property | Value |
|----------|-------|
| **Domain** | `eblu.me` |
| **Provider** | Gandi LiveDNS |
| **IaC** | `pulumi/gandi/` |
| **Stack** | `eblu-me` |
## What It Does
Gandi hosts the DNS records that make `*.ops.eblu.me` resolve to [[indri]]'s Tailscale IP (100.98.163.89). Since Tailscale IPs are not publicly routable, this gives services real DNS names while keeping them private to the tailnet.
The target IP is resolved dynamically from `indri.tail8d86e.ts.net` at deploy time, so if indri's Tailscale IP changes, re-running the deployment is sufficient.
## DNS Records
| Record | Type | Value | TTL |
|--------|------|-------|-----|
| `*.ops.eblu.me` | A | indri's Tailscale IP | 300s |
| `ops.eblu.me` | A | indri's Tailscale IP | 300s |
Both records point to [[indri]], which runs [[caddy]] as the reverse proxy for all services. See [[routing]] for the full service URL map.
## Pulumi Configuration
The Pulumi program lives in `pulumi/gandi/`:
- `__main__.py` - Creates the two A records via `pulumiverse_gandi`
- `Pulumi.eblu-me.yaml` - Stack config (domain, subdomain)
Stack config values:
| Key | Value |
|-----|-------|
| `blumeops-dns:domain` | `eblu.me` |
| `blumeops-dns:subdomain` | `ops` |
A break-glass override is available via the `BLUMEOPS_REVERSE_PROXY_IP` environment variable, which bypasses dynamic IP resolution.
## TLS Integration
[[caddy]] uses Gandi's API separately (via `GANDI_BEARER_TOKEN`) for ACME DNS-01 challenges to obtain a wildcard Let's Encrypt certificate for `*.ops.eblu.me`. This is a different credential from the Pulumi PAT.
## Authentication
Gandi requires a Personal Access Token (PAT) for API access. PATs have a maximum lifetime of 90 days (currently set to 30). See [[gandi-operations]] for deployment and PAT cycling instructions.
## Related
- [[gandi-operations]] - PAT cycling and deployment how-to
- [[routing]] - Service URLs and routing architecture
- [[caddy]] - Reverse proxy using Gandi for TLS
- [[tailscale]] - Tailnet networking
- [[indri]] - Server hosting Caddy (DNS target)

View file

@ -61,5 +61,6 @@ DNS points to indri's Tailscale IP (100.98.163.89). TLS via Let's Encrypt (ACME
## Related ## Related
- [[gandi]] - DNS hosting for `eblu.me`
- [[tailscale]] - ACL configuration - [[tailscale]] - ACL configuration
- [[indri]] - Where services run - [[indri]] - Where services run

View file

@ -44,6 +44,7 @@ Host inventory and network configuration.
- [[indri]] - Primary server - [[indri]] - Primary server
- [[gilbert]] - Development workstation - [[gilbert]] - Development workstation
- [[tailscale]] - ACLs, groups, tags - [[tailscale]] - ACLs, groups, tags
- [[gandi]] - DNS hosting for `eblu.me`
- [[routing|Routing]] - DNS domains, port mappings - [[routing|Routing]] - DNS domains, port mappings
## Kubernetes ## Kubernetes

View file

@ -92,6 +92,7 @@ The build includes the `github.com/caddy-dns/gandi` plugin for ACME DNS-01 chall
## Related ## Related
- [[gandi]] - DNS hosting and ACME DNS-01 provider
- [[routing]] - Service routing architecture - [[routing]] - Service routing architecture
- [[forgejo]] - Git forge (proxied by Caddy) - [[forgejo]] - Git forge (proxied by Caddy)
- [[zot]] - Container registry (proxied by Caddy) - [[zot]] - Container registry (proxied by Caddy)

View file

@ -37,7 +37,7 @@ This project requires a Gandi Personal Access Token (PAT) with LiveDNS permissio
2. Create a new PAT: 2. Create a new PAT:
- Name: `blumeops-pulumi` (or similar) - Name: `blumeops-pulumi` (or similar)
- Expiration: 30 days (maximum) - Expiration: 30 days (maximum is 90; shorter is fine if used rarely)
- Permissions required: - Permissions required:
- **Manage domain name technical configurations** (required for DNS records) - **Manage domain name technical configurations** (required for DNS records)
- See and renew domain names - See and renew domain names