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
|
||||
}
|
||||
|
||||
# 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
|
||||
egress {
|
||||
from_port = 0
|
||||
|
|
@ -340,7 +358,7 @@ resource "aws_instance" "forgejo" {
|
|||
}
|
||||
|
||||
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"
|
||||
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