feat(dns): Add self-hosted PowerDNS for 5 managed domains
- Deploy PowerDNS on k3s with SQLite backend - Add DNS ports 53 UDP/TCP to security group - Configure zones for superviber.com, muffinlabs.ai, letemcook.com, appbasecamp.com, thanksforborrowing.com - Add deploy-powerdns.sh standalone deployment script - Document in RFC 0003 Glue records updated at GoDaddy to point ns1/ns2 to 3.218.167.115. DNS verified working via Google DNS (8.8.8.8). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0d904fe130
commit
da40273177
3 changed files with 738 additions and 1 deletions
340
docs/rfcs/0003-powerdns-self-hosted.md
Normal file
340
docs/rfcs/0003-powerdns-self-hosted.md
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
# RFC 0003: PowerDNS Self-Hosted DNS
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Status** | Implemented |
|
||||||
|
| **Created** | 2026-01-24 |
|
||||||
|
| **Related** | [coherence-mcp RFC 0046](../../coherence-mcp/docs/rfcs/0046-domain-email-migration.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
We have 5 domains registered at GoDaddy that need DNS management:
|
||||||
|
- superviber.com
|
||||||
|
- muffinlabs.ai
|
||||||
|
- letemcook.com
|
||||||
|
- appbasecamp.com
|
||||||
|
- thanksforborrowing.com
|
||||||
|
|
||||||
|
Current state:
|
||||||
|
1. **Glue records exist** at GoDaddy pointing ns1/ns2.superviber.com to IPs 3.12.26.86 and 3.140.40.205
|
||||||
|
2. **No DNS server running** at those IPs (planned EKS infrastructure never deployed)
|
||||||
|
3. **Forgejo running** at 3.218.167.115 on k3s (hearth minimal), but DNS not configured
|
||||||
|
4. **Cloudflare partially configured** for superviber.com but nameservers not switched
|
||||||
|
|
||||||
|
We want self-hosted DNS for:
|
||||||
|
- Control over all records without third-party dashboard
|
||||||
|
- Integration with other self-hosted services (mail, git, auth)
|
||||||
|
- Single source of truth in infrastructure-as-code
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. **Deploy PowerDNS** on existing k3s infrastructure (hearth)
|
||||||
|
2. **Configure zones** for all 5 managed domains
|
||||||
|
3. **Expose DNS** via port 53 UDP/TCP
|
||||||
|
4. **Update glue records** at GoDaddy to point to working DNS servers
|
||||||
|
5. **Add git.beyondtheuniverse.superviber.com** record pointing to Forgejo
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- HA DNS with 3 separate IPs (future scope, current setup is single-user)
|
||||||
|
- DNSSEC (future scope)
|
||||||
|
- Secondary/slave DNS (future scope)
|
||||||
|
- DNS-over-HTTPS or DNS-over-TLS (future scope)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposal
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
Deploy PowerDNS on the existing hearth k3s instance (3.218.167.115) as a single authoritative DNS server.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ EC2: 3.218.167.115 │
|
||||||
|
│ (hearth-forgejo) │
|
||||||
|
│ │
|
||||||
|
┌─────────────┐ │ ┌─────────────────────────────┐ │
|
||||||
|
│ GoDaddy │ │ │ k3s │ │
|
||||||
|
│ Glue: │ │ │ │ │
|
||||||
|
│ ns1/ns2 ────┼────────►│ │ ┌─────────┐ ┌──────────┐ │ │
|
||||||
|
│ .superviber │ :53 │ │ │PowerDNS │ │ Forgejo │ │ │
|
||||||
|
│ .com │ │ │ │ :53 │ │ :3000 │ │ │
|
||||||
|
└─────────────┘ │ │ └─────────┘ └──────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Traefik (ingress) │ │
|
||||||
|
│ │ :80, :443, :22 │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why Single Instance
|
||||||
|
|
||||||
|
For a ~1 user personal infrastructure:
|
||||||
|
- Simpler ops (one machine to manage)
|
||||||
|
- Lower cost (~$7.50/month total)
|
||||||
|
- Acceptable availability (spot instance with persistent EIP)
|
||||||
|
- GoDaddy remains as backup (can revert NS records if needed)
|
||||||
|
|
||||||
|
### DNS Records
|
||||||
|
|
||||||
|
Initial zones will include:
|
||||||
|
|
||||||
|
**superviber.com:**
|
||||||
|
```
|
||||||
|
@ A 3.218.167.115
|
||||||
|
ns1 A 3.218.167.115
|
||||||
|
ns2 A 3.218.167.115
|
||||||
|
beyondtheuniverse A 3.218.167.115
|
||||||
|
git.beyondtheuniverse A 3.218.167.115
|
||||||
|
mail.beyondtheuniverse A 3.218.167.115
|
||||||
|
auth.beyondtheuniverse A 3.218.167.115
|
||||||
|
@ NS ns1.superviber.com.
|
||||||
|
@ NS ns2.superviber.com.
|
||||||
|
```
|
||||||
|
|
||||||
|
Other domains (muffinlabs.ai, etc.) will have minimal records pointing to the same infrastructure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Phase 1: Deploy PowerDNS on k3s
|
||||||
|
|
||||||
|
Add to hearth user-data.sh or apply directly:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: dns
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: powerdns-api-key
|
||||||
|
namespace: dns
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
api-key: "GENERATED_SECRET_KEY"
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: powerdns
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: powerdns
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: powerdns
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: powerdns
|
||||||
|
image: powerdns/pdns-auth-49:4.9.2
|
||||||
|
ports:
|
||||||
|
- name: dns-udp
|
||||||
|
containerPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- name: dns-tcp
|
||||||
|
containerPort: 53
|
||||||
|
protocol: TCP
|
||||||
|
- name: api
|
||||||
|
containerPort: 8081
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: PDNS_AUTH_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: powerdns-api-key
|
||||||
|
key: api-key
|
||||||
|
args:
|
||||||
|
- --api=yes
|
||||||
|
- --api-key=$(PDNS_AUTH_API_KEY)
|
||||||
|
- --webserver=yes
|
||||||
|
- --webserver-address=0.0.0.0
|
||||||
|
- --webserver-port=8081
|
||||||
|
- --launch=gsqlite3
|
||||||
|
- --gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/powerdns
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
hostPath:
|
||||||
|
path: /data/powerdns
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: powerdns
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
selector:
|
||||||
|
app: powerdns
|
||||||
|
ports:
|
||||||
|
- name: dns-udp
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- name: dns-tcp
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
protocol: TCP
|
||||||
|
- name: api
|
||||||
|
port: 8081
|
||||||
|
targetPort: 8081
|
||||||
|
protocol: TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Update Security Group
|
||||||
|
|
||||||
|
Add to hearth main.tf security group:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# DNS (UDP)
|
||||||
|
ingress {
|
||||||
|
description = "DNS UDP"
|
||||||
|
from_port = 53
|
||||||
|
to_port = 53
|
||||||
|
protocol = "udp"
|
||||||
|
cidr_blocks = ["0.0.0.0/0"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# DNS (TCP)
|
||||||
|
ingress {
|
||||||
|
description = "DNS TCP"
|
||||||
|
from_port = 53
|
||||||
|
to_port = 53
|
||||||
|
protocol = "tcp"
|
||||||
|
cidr_blocks = ["0.0.0.0/0"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Configure Zones
|
||||||
|
|
||||||
|
Use PowerDNS API to create zones:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create zone
|
||||||
|
curl -X POST http://localhost:8081/api/v1/servers/localhost/zones \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"name": "superviber.com.",
|
||||||
|
"kind": "Native",
|
||||||
|
"nameservers": ["ns1.superviber.com.", "ns2.superviber.com."]
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Add records
|
||||||
|
curl -X PATCH http://localhost:8081/api/v1/servers/localhost/zones/superviber.com. \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"rrsets": [
|
||||||
|
{
|
||||||
|
"name": "superviber.com.",
|
||||||
|
"type": "A",
|
||||||
|
"ttl": 300,
|
||||||
|
"changetype": "REPLACE",
|
||||||
|
"records": [{"content": "3.218.167.115", "disabled": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ns1.superviber.com.",
|
||||||
|
"type": "A",
|
||||||
|
"ttl": 3600,
|
||||||
|
"changetype": "REPLACE",
|
||||||
|
"records": [{"content": "3.218.167.115", "disabled": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ns2.superviber.com.",
|
||||||
|
"type": "A",
|
||||||
|
"ttl": 3600,
|
||||||
|
"changetype": "REPLACE",
|
||||||
|
"records": [{"content": "3.218.167.115", "disabled": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "git.beyondtheuniverse.superviber.com.",
|
||||||
|
"type": "A",
|
||||||
|
"ttl": 300,
|
||||||
|
"changetype": "REPLACE",
|
||||||
|
"records": [{"content": "3.218.167.115", "disabled": false}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Update GoDaddy Glue Records
|
||||||
|
|
||||||
|
At GoDaddy for superviber.com:
|
||||||
|
1. Update glue records to point ns1 and ns2 to 3.218.167.115
|
||||||
|
2. Set nameservers to ns1.superviber.com and ns2.superviber.com
|
||||||
|
|
||||||
|
### Phase 5: Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test directly against PowerDNS
|
||||||
|
dig @3.218.167.115 git.beyondtheuniverse.superviber.com A
|
||||||
|
|
||||||
|
# Test via public DNS (after propagation)
|
||||||
|
dig git.beyondtheuniverse.superviber.com A
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Changes
|
||||||
|
|
||||||
|
| File | Change | Status |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `hearth/terraform/minimal/main.tf` | Add DNS ports 53 UDP/TCP to security group | Done |
|
||||||
|
| `hearth/scripts/deploy-powerdns.sh` | Standalone PowerDNS deployment script | Done |
|
||||||
|
| `hearth/docs/rfcs/0003-powerdns-self-hosted.md` | This RFC | Done |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If PowerDNS fails:
|
||||||
|
1. Update GoDaddy to use its default nameservers (restore from backup)
|
||||||
|
2. Or: Use Cloudflare (already partially configured)
|
||||||
|
|
||||||
|
The Elastic IP remains stable, so only NS records need updating.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Plan
|
||||||
|
|
||||||
|
- [x] PowerDNS pod running in k3s
|
||||||
|
- [x] Port 53 UDP/TCP accessible from internet
|
||||||
|
- [x] `dig @3.218.167.115 superviber.com NS` returns ns1/ns2
|
||||||
|
- [x] `dig @3.218.167.115 git.beyondtheuniverse.superviber.com A` returns 3.218.167.115
|
||||||
|
- [x] GoDaddy glue records updated for all 5 domains (2026-01-24)
|
||||||
|
- [x] `dig @8.8.8.8 git.beyondtheuniverse.superviber.com A` resolves to 3.218.167.115
|
||||||
|
- [x] Forgejo accessible at https://git.beyondtheuniverse.superviber.com with valid TLS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Work
|
||||||
|
|
||||||
|
1. **HA DNS** - Deploy on separate instance with its own EIP for redundancy
|
||||||
|
2. **DNSSEC** - Sign zones for security
|
||||||
|
3. **Backup zones** - Export zone files to S3 with daily backups
|
||||||
|
4. **DNS UI** - PowerDNS-Admin web interface for zone management
|
||||||
|
5. **Integration with Blue** - Manage DNS records as code via Blue
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [PowerDNS Documentation](https://doc.powerdns.com/authoritative/)
|
||||||
|
- [coherence-mcp RFC 0046](../../coherence-mcp/docs/rfcs/0046-domain-email-migration.md)
|
||||||
|
- [coherence-mcp dns-zones-rfc0046.yaml](../../coherence-mcp/infra/kubernetes/powerdns/dns-zones-rfc0046.yaml)
|
||||||
352
scripts/deploy-powerdns.sh
Executable file
352
scripts/deploy-powerdns.sh
Executable file
|
|
@ -0,0 +1,352 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Deploy PowerDNS on k3s
|
||||||
|
# Usage: sudo ./deploy-powerdns.sh <PUBLIC_IP>
|
||||||
|
# Example: sudo ./deploy-powerdns.sh 3.218.167.115
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "Usage: $0 <PUBLIC_IP>"
|
||||||
|
echo "Example: $0 3.218.167.115"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PUBLIC_IP="$1"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Deploying PowerDNS with IP: $PUBLIC_IP"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Create data directory
|
||||||
|
echo "Creating data directory..."
|
||||||
|
mkdir -p /data/powerdns
|
||||||
|
chown 953:953 /data/powerdns
|
||||||
|
|
||||||
|
# Generate API key
|
||||||
|
PDNS_API_KEY=$(openssl rand -hex 32)
|
||||||
|
echo "$PDNS_API_KEY" > /root/.pdns-api-key
|
||||||
|
chmod 600 /root/.pdns-api-key
|
||||||
|
echo "API key saved to /root/.pdns-api-key"
|
||||||
|
|
||||||
|
# Deploy PowerDNS
|
||||||
|
echo "Deploying PowerDNS to k3s..."
|
||||||
|
cat <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: dns
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: powerdns-api-key
|
||||||
|
namespace: dns
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
api-key: "$PDNS_API_KEY"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: powerdns-data
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 1Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
hostPath:
|
||||||
|
path: /data/powerdns
|
||||||
|
storageClassName: local
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: powerdns-data
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: local
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: powerdns
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: powerdns
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: powerdns
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 953
|
||||||
|
initContainers:
|
||||||
|
- name: init-db
|
||||||
|
image: powerdns/pdns-auth-49:4.9.2
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
if [ ! -f /var/lib/powerdns/pdns.sqlite3 ]; then
|
||||||
|
sqlite3 /var/lib/powerdns/pdns.sqlite3 < /usr/local/share/doc/pdns/schema.sqlite3.sql
|
||||||
|
chown 953:953 /var/lib/powerdns/pdns.sqlite3
|
||||||
|
fi
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/powerdns
|
||||||
|
containers:
|
||||||
|
- name: powerdns
|
||||||
|
image: powerdns/pdns-auth-49:4.9.2
|
||||||
|
ports:
|
||||||
|
- name: dns-udp
|
||||||
|
containerPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- name: dns-tcp
|
||||||
|
containerPort: 53
|
||||||
|
protocol: TCP
|
||||||
|
- name: api
|
||||||
|
containerPort: 8081
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: PDNS_AUTH_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: powerdns-api-key
|
||||||
|
key: api-key
|
||||||
|
command:
|
||||||
|
- pdns_server
|
||||||
|
args:
|
||||||
|
- --api=yes
|
||||||
|
- --api-key=\$(PDNS_AUTH_API_KEY)
|
||||||
|
- --webserver=yes
|
||||||
|
- --webserver-address=0.0.0.0
|
||||||
|
- --webserver-port=8081
|
||||||
|
- --webserver-allow-from=0.0.0.0/0
|
||||||
|
- --launch=gsqlite3
|
||||||
|
- --gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
|
||||||
|
- --local-address=0.0.0.0
|
||||||
|
- --local-port=53
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/powerdns
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: powerdns-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: powerdns
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
selector:
|
||||||
|
app: powerdns
|
||||||
|
ports:
|
||||||
|
- name: dns-udp
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- name: dns-tcp
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: powerdns-api
|
||||||
|
namespace: dns
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: powerdns
|
||||||
|
ports:
|
||||||
|
- name: api
|
||||||
|
port: 8081
|
||||||
|
targetPort: 8081
|
||||||
|
protocol: TCP
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Waiting for PowerDNS pod to be ready..."
|
||||||
|
kubectl wait --for=condition=ready pod -l app=powerdns -n dns --timeout=120s
|
||||||
|
|
||||||
|
# Get PowerDNS API service IP
|
||||||
|
PDNS_HOST="http://$(kubectl get svc powerdns-api -n dns -o jsonpath='{.spec.clusterIP}'):8081"
|
||||||
|
|
||||||
|
echo "PowerDNS API: $PDNS_HOST"
|
||||||
|
|
||||||
|
# Wait for API to be ready
|
||||||
|
echo "Waiting for PowerDNS API..."
|
||||||
|
until curl -sf -H "X-API-Key: $PDNS_API_KEY" "$PDNS_HOST/api/v1/servers/localhost" > /dev/null 2>&1; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "PowerDNS API is ready!"
|
||||||
|
|
||||||
|
# Function to create zone
|
||||||
|
create_zone() {
|
||||||
|
local DOMAIN=$1
|
||||||
|
echo "Creating zone: $DOMAIN"
|
||||||
|
|
||||||
|
curl -sf -X POST "$PDNS_HOST/api/v1/servers/localhost/zones" \
|
||||||
|
-H "X-API-Key: $PDNS_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"name\": \"$DOMAIN.\",
|
||||||
|
\"kind\": \"Native\",
|
||||||
|
\"nameservers\": [\"ns1.$DOMAIN.\", \"ns2.$DOMAIN.\"]
|
||||||
|
}" 2>/dev/null || echo " (zone may already exist)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to setup records
|
||||||
|
setup_records() {
|
||||||
|
local DOMAIN=$1
|
||||||
|
echo "Setting up records for: $DOMAIN"
|
||||||
|
|
||||||
|
curl -sf -X PATCH "$PDNS_HOST/api/v1/servers/localhost/zones/$DOMAIN." \
|
||||||
|
-H "X-API-Key: $PDNS_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"rrsets\": [
|
||||||
|
{
|
||||||
|
\"name\": \"$DOMAIN.\",
|
||||||
|
\"type\": \"NS\",
|
||||||
|
\"ttl\": 86400,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [
|
||||||
|
{\"content\": \"ns1.$DOMAIN.\", \"disabled\": false},
|
||||||
|
{\"content\": \"ns2.$DOMAIN.\", \"disabled\": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"ns1.$DOMAIN.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 3600,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"ns2.$DOMAIN.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 3600,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"$DOMAIN.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"www.$DOMAIN.\",
|
||||||
|
\"type\": \"CNAME\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$DOMAIN.\", \"disabled\": false}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Creating DNS zones..."
|
||||||
|
for domain in superviber.com muffinlabs.ai letemcook.com appbasecamp.com thanksforborrowing.com alignment.coop; do
|
||||||
|
create_zone $domain
|
||||||
|
setup_records $domain
|
||||||
|
done
|
||||||
|
|
||||||
|
# Setup superviber.com beyondtheuniverse services
|
||||||
|
echo ""
|
||||||
|
echo "Setting up beyondtheuniverse.superviber.com services..."
|
||||||
|
curl -sf -X PATCH "$PDNS_HOST/api/v1/servers/localhost/zones/superviber.com." \
|
||||||
|
-H "X-API-Key: $PDNS_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"rrsets\": [
|
||||||
|
{
|
||||||
|
\"name\": \"beyondtheuniverse.superviber.com.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"git.beyondtheuniverse.superviber.com.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"mail.beyondtheuniverse.superviber.com.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"auth.beyondtheuniverse.superviber.com.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"vault.beyondtheuniverse.superviber.com.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\"name\": \"grafana.beyondtheuniverse.superviber.com.\",
|
||||||
|
\"type\": \"A\",
|
||||||
|
\"ttl\": 300,
|
||||||
|
\"changetype\": \"REPLACE\",
|
||||||
|
\"records\": [{\"content\": \"$PUBLIC_IP\", \"disabled\": false}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "PowerDNS deployment complete!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Verification:"
|
||||||
|
echo " dig @$PUBLIC_IP superviber.com NS"
|
||||||
|
echo " dig @$PUBLIC_IP git.beyondtheuniverse.superviber.com A"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Update GoDaddy glue records for each domain:"
|
||||||
|
echo " - ns1.<domain> -> $PUBLIC_IP"
|
||||||
|
echo " - ns2.<domain> -> $PUBLIC_IP"
|
||||||
|
echo ""
|
||||||
|
echo "2. Update nameservers at GoDaddy:"
|
||||||
|
echo " - ns1.<domain>"
|
||||||
|
echo " - ns2.<domain>"
|
||||||
|
echo ""
|
||||||
|
echo "3. Wait for DNS propagation (up to 48 hours)"
|
||||||
|
echo "=========================================="
|
||||||
|
|
@ -166,6 +166,24 @@ resource "aws_security_group" "forgejo" {
|
||||||
cidr_blocks = var.admin_cidr_blocks
|
cidr_blocks = var.admin_cidr_blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# DNS (UDP) - PowerDNS authoritative server
|
||||||
|
ingress {
|
||||||
|
description = "DNS UDP"
|
||||||
|
from_port = 53
|
||||||
|
to_port = 53
|
||||||
|
protocol = "udp"
|
||||||
|
cidr_blocks = ["0.0.0.0/0"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# DNS (TCP) - PowerDNS authoritative server
|
||||||
|
ingress {
|
||||||
|
description = "DNS TCP"
|
||||||
|
from_port = 53
|
||||||
|
to_port = 53
|
||||||
|
protocol = "tcp"
|
||||||
|
cidr_blocks = ["0.0.0.0/0"]
|
||||||
|
}
|
||||||
|
|
||||||
# All outbound
|
# All outbound
|
||||||
egress {
|
egress {
|
||||||
from_port = 0
|
from_port = 0
|
||||||
|
|
@ -340,7 +358,7 @@ resource "aws_instance" "forgejo" {
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycle {
|
lifecycle {
|
||||||
ignore_changes = [ami] # Don't replace on AMI updates
|
ignore_changes = [ami, user_data] # Don't replace on AMI or user-data changes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,3 +413,30 @@ output "dns_record" {
|
||||||
description = "DNS A record to create"
|
description = "DNS A record to create"
|
||||||
value = "${var.domain} → ${aws_eip.forgejo.public_ip}"
|
value = "${var.domain} → ${aws_eip.forgejo.public_ip}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "powerdns_deploy_command" {
|
||||||
|
description = "Command to deploy PowerDNS after instance is running"
|
||||||
|
value = <<-EOT
|
||||||
|
# Copy and run deployment script:
|
||||||
|
scp -P ${var.admin_ssh_port} scripts/deploy-powerdns.sh ec2-user@${aws_eip.forgejo.public_ip}:
|
||||||
|
ssh -p ${var.admin_ssh_port} ec2-user@${aws_eip.forgejo.public_ip} 'sudo bash deploy-powerdns.sh ${aws_eip.forgejo.public_ip}'
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
|
||||||
|
output "dns_glue_records" {
|
||||||
|
description = "Glue records to configure at GoDaddy after PowerDNS is running"
|
||||||
|
value = <<-EOT
|
||||||
|
For each managed domain, set nameservers and glue records:
|
||||||
|
|
||||||
|
Nameservers:
|
||||||
|
ns1.<domain>
|
||||||
|
ns2.<domain>
|
||||||
|
|
||||||
|
Glue Records:
|
||||||
|
ns1 → ${aws_eip.forgejo.public_ip}
|
||||||
|
ns2 → ${aws_eip.forgejo.public_ip}
|
||||||
|
|
||||||
|
Domains: superviber.com, muffinlabs.ai, letemcook.com,
|
||||||
|
appbasecamp.com, thanksforborrowing.com, alignment.coop
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue