Research-Stack/6-Documentation/DISASTER_RECOVERY.md
Brandon Schneider 7a6df586b2 docs: runbook, FPGA programming guide, disaster recovery, API docs
- RUNBOOK.md: k3s/FPGA/Tailscale/GPU/DNS ops procedures
- FPGA_PROGRAMMING_GUIDE.md: SUBLEQ format, memory map, 3 examples
- DISASTER_RECOVERY.md: backup/restore for all components
- API_DOCS.md: dashboard, credential, registry, jobs, blobs APIs
2026-05-29 01:26:41 -05:00

343 lines
6.9 KiB
Markdown

# Disaster Recovery — Research Stack
**Last updated:** 2026-05-29
Procedures for backing up and restoring all Research Stack components.
---
## k3s Cluster
### Backup etcd
```bash
# Snapshot etcd (on control plane node — nixos)
sudo k3s etcd-snapshot save \
--name researchstack-backup \
--dir /var/lib/rancher/k3s/server/db/snapshots
# List snapshots
sudo ls -la /var/lib/rancher/k3s/server/db/snapshots/
# Copy snapshot off-node
scp /var/lib/rancher/k3s/server/db/snapshots/researchstack-backup-* \
user@backup-host:/backups/k3s/
```
### Restore from Snapshot
```bash
# On control plane (nixos):
sudo systemctl stop k3s
# Restore
sudo k3s server \
--cluster-reset \
--cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/researchstack-backup-<timestamp>
# Restart
sudo systemctl start k3s
# Verify
export KUBECONFIG=/tmp/researchstack-kubeconfig.yaml
kubectl get nodes
kubectl get pods -A
```
### Backup Manifests
All Kubernetes manifests are in the git repo. The source of truth is:
```bash
cd ~/Research\ Stack
git push origin main
```
### Automated Backup Schedule
Create a cron job on nixos:
```bash
# /etc/cron.d/k3s-backup
0 3 * * * root k3s etcd-snapshot save --name researchstack-backup --dir /var/lib/rancher/k3s/server/db/snapshots && \
find /var/lib/rancher/k3s/server/db/snapshots/ -mtime +7 -delete
```
---
## FPGA — Tang Nano 9K
### Backup Bitstream
The bitstream source and compiled output are in the git repo:
```bash
cd ~/Research\ Stack
# Source
ls 4-Infrastructure/hardware/*.v
# Compiled bitstream
ls 4-Infrastructure/hardware/research_stack_top.fs
```
### Re-flash from Git
```bash
cd ~/Research\ Stack
# Rebuild if needed
cd 4-Infrastructure/hardware && bash build_research_stack.sh
# Flash
openFPGALoader -b tangnano9k research_stack_top.fs
# Verify
openFPGALoader -b tangnano9k --verify research_stack_top.fs
```
### Backup SRAM Contents
SRAM contents are volatile (lost on power cycle). To preserve runtime state:
```python
# Dump memory via UART before power-off
import serial, struct
ser = serial.Serial('/dev/ttyUSB0', 115384, timeout=10)
# Trigger memory dump (send HALT with dump flag)
# ... protocol-specific ...
# Read and save
with open('fpga_memory_dump.bin', 'wb') as f:
data = ser.read(8192) # 4K words = 8K bytes
f.write(data)
ser.close()
```
---
## Tailscale
### Re-authenticate Node
```bash
# On the node that lost auth:
sudo systemctl restart tailscaled
tailscale up --authkey=<TS_AUTH_KEY>
# Or interactive
tailscale up
```
### Re-join Tailnet
```bash
# Check current status
tailscale status
# If node is missing from tailnet:
# 1. Generate new auth key at https://login.tailscale.com/admin/settings/keys
# 2. On the node:
sudo tailscale up --authkey=tskey-auth-<key>
# Verify connectivity
tailscale ping nixos-laptop
tailscale ping qfox-1
```
### Backup Tailscale State
Tailscale state is stored at `/var/lib/tailscale/`:
```bash
# Backup state directory
sudo tar czf /backups/tailscale-state-$(date +%Y%m%d).tar.gz \
/var/lib/tailscale/
# Restore
sudo systemctl stop tailscaled
sudo tar xzf /backups/tailscale-state-*.tar.gz -C /
sudo systemctl start tailscaled
```
---
## Git Repository
### Backup
The canonical backup is the remote origin. Ensure all changes are pushed:
```bash
cd ~/Research\ Stack
# Check for unpushed commits
git log --oneline origin/main..HEAD
# Push everything
git push origin main --tags
# Verify clean state
git status --branch --short --untracked-files=all
```
### Restore from Remote
```bash
# Clone fresh
cd ~
git clone <remote-url> "Research Stack"
# Or reset to remote state
cd ~/Research\ Stack
git fetch origin
git reset --hard origin/main
```
### Backup Remotes
```bash
# List remotes
cd ~/Research\ Stack
git remote -v
# Add backup remote
git remote add backup <backup-url>
git push backup main --tags
```
---
## Secrets (sops-nix / age)
### Backup age Keys
```bash
# age key location (NixOS)
cat /etc/age/keys.txt
# Backup
cp /etc/age/keys.txt /backups/age-keys-$(date +%Y%m%d).txt
chmod 600 /backups/age-keys-*.txt
# Or from home directory
cat ~/.config/sops/age/keys.txt
```
### Restore age Keys
```bash
# Restore key file
cp /backups/age-keys-*.txt /etc/age/keys.txt
chmod 600 /etc/age/keys.txt
# Verify sops can decrypt
sops -d secrets.enc.yaml
```
### Backup sops Configuration
```bash
# The .sops.yaml config is in the repo
cat ~/Research\ Stack/.sops.yaml
# Encrypted secrets are also in the repo
find ~/Research\ Stack -name '*.enc.yaml' -o -name '*.enc.json'
```
### Regenerate age Key (Last Resort)
```bash
# Generate new key pair
age-keygen -o /etc/age/keys.txt
# Get public key
age-keygen -y /etc/age/keys.txt
# Re-encrypt all secrets with new key
# (requires old key or plaintext backup)
sops updatekeys secrets.enc.yaml
```
---
## DNS — Porkbun
### Backup API Key
```bash
# From k8s secret
kubectl get secret porkbun-credentials -n services \
-o jsonpath='{.data.api-key}' | base64 -d
# Back up securely
echo "<api-key>" | age -r <backup-age-pubkey> > /backups/porkbun-api.age
# Or store in password manager
```
### Recover API Key
1. Log in to [Porkbun](https://porkbun.com)
2. Navigate to **Account → API Access**
3. Regenerate or copy existing API key
4. Update k8s secret:
```bash
kubectl create secret generic porkbun-credentials \
-n services \
--from-literal=api-key=<new-key> \
--dry-run=client -o yaml | kubectl apply -f -
# Restart Caddy to pick up new key
kubectl rollout restart deployment/caddy -n services
```
### Verify DNS Records
```bash
dig researchstack.info +short
dig auth.researchstack.info +short
dig registry.researchstack.info +short
# Test Porkbun API
curl -X POST https://api.porkbun.com/api/json/v3/ping \
-H "Content-Type: application/json" \
-d '{"apikey": "<key>", "secretapikey": "<secret>"}'
```
---
## Recovery Priority Order
When restoring from a total failure, follow this order:
| Step | Component | Depends On |
|------|-----------|------------|
| 1 | **Tailscale** | Auth key (from password manager or backup) |
| 2 | **Git repo** | Network connectivity (Tailscale or direct) |
| 3 | **age keys** | Backup location |
| 4 | **k3s cluster** | etcd snapshot + age keys for secrets |
| 5 | **DNS/Porkbun** | API key (from backup or Porkbun account) |
| 6 | **FPGA** | Git repo (source + bitstream) |
| 7 | **Services** | k3s cluster + secrets + DNS |
---
## Full System Checklist
After recovery, verify each component:
- [ ] Tailscale: `tailscale status` shows all 5 nodes
- [ ] k3s: `kubectl get nodes` shows all nodes Ready
- [ ] Pods: `kubectl get pods -A` — all Running/Succeeded
- [ ] DNS: `dig researchstack.info` resolves correctly
- [ ] TLS: cert valid (`openssl s_client`)
- [ ] Auth: `auth.researchstack.info` loads Authentik
- [ ] Funnel: `361395-1.tail4e7094.ts.net` responds
- [ ] Ollama: `curl http://100.88.57.96:31434/api/tags` returns models
- [ ] FPGA: `openFPGALoader --detect` sees Tang Nano 9K
- [ ] Dashboard: `https://researchstack.info` shows Homer