496 lines
18 KiB
Markdown
496 lines
18 KiB
Markdown
# Company Name — Disaster Recovery Plan v3 (Consolidated)
|
|
|
|
**Author:** [Lead Sysadmin] / Network Services Team, incorporating third-party DR review
|
|
**Date:** July 9, 2026
|
|
**Status:** LIVE — replaces v1.0
|
|
**Supersedes:** server-dr-plans.md v1.0, server-provisioning-standard-v1.md, hermes-dr-plan-v2.md
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
1. Architecture & Shared Dependencies
|
|
2. Server Standards
|
|
3. Backup Strategy
|
|
4. Failover / Failback
|
|
5. Monitoring & Alerting
|
|
6. Restore Runbooks
|
|
7. Testing & Validation
|
|
8. Security
|
|
9. Immediate Action Items
|
|
10. Appendix: Third-Party Review Responses
|
|
|
|
---
|
|
|
|
## 1. Architecture & Shared Dependencies
|
|
|
|
```
|
|
Internet ──► CDN/DNS Provider ──► Caddy ──► Service (HTTP/HTTPS)
|
|
│
|
|
┌───────────┼───────────┐
|
|
│ │ │
|
|
SSH/WG Tailscale Management
|
|
(company-prefix- (Primary Host ↔ VPN (WireGuard
|
|
infra) Standby Host) to home router)
|
|
│ │ │
|
|
└───────────┼───────────┘
|
|
│
|
|
┌───────────┴───────────┐
|
|
│ │
|
|
S3 Primary S3 Secondary
|
|
(Object Storage Provider us-east-1) (Object Storage Provider us-west-2
|
|
versioning ON or Backblaze B2)
|
|
object lock ON object lock ON
|
|
```
|
|
|
|
### Shared Dependencies — Documented
|
|
|
|
| Dependency | Primary | Backup / Fallback | Credentials Location |
|
|
|---|---|---|---|
|
|
| SSH access | company-prefix-infra key (Secondary Provider vault) | Rescue mode / console | ~/.hermes/.env + Hudu |
|
|
| DNS | CDN/DNS Provider (API token in .env) | Manual via web UI | ~/.hermes/.env |
|
|
| S3 backup (primary) | Object Storage Provider us-east-1 | Object Storage Provider us-west-2 (to be created) | ~/.aws/credentials |
|
|
| S3 backup (secondary) | Object Storage Provider us-west-2 | Backblaze B2 (evaluate) | TBD |
|
|
| SMTP relay | [MAIL-SERVER-DOMAIN]:2525 | Direct Email Provider (port 465/587) | ~/.hermes/.env |
|
|
| Auth/SSO | CDN/DNS Provider Access | Local portal auth fallback | CDN/DNS Provider dashboard |
|
|
| Monitoring | Prometheus node_exporter | Uptime Kuma (docker box) | N/A (pull model) |
|
|
| VPN management | WireGuard (wg0, port 51821) | Tailscale direct tunnel | /etc/wireguard/ |
|
|
|
|
---
|
|
|
|
|
|
### 3.1 Backup Matrix
|
|
|
|
| Data Type | Frequency | Target | Retention | Status |
|
|
|---|---|---|---|---|
|
|
| Hermes state | Every 15 min | S3 primary (live/) | 48 hours | ✅ LIVE |
|
|
| Hermes full | Daily 5 AM | S3 primary (full/) | 90 days | ✅ LIVE |
|
|
| Memory snapshots | Every 10 min | S3 primary (memories/) | 48 hours | ✅ LIVE (new) |
|
|
| Memory history | Every 10 min | S3 primary (memories/history/) | 90 days | ✅ LIVE (new) |
|
|
| Router configs | Daily 6 AM | S3 primary (mikrotik/) | 90 days | ✅ LIVE |
|
|
| System configs | Daily | S3 primary (<server>/) | 90 days | ⚠️ NOT AUTOMATED |
|
|
| Docker volumes | Daily | S3 primary (<server>/) | 90 days | ❌ NOT DONE |
|
|
| Database dumps | Every 6 hours | S3 primary (<server>/) | 90 days | ❌ NOT DONE |
|
|
| Application data | Per-app | S3 primary (<server>/) | 90 days | ⚠️ PARTIAL |
|
|
| S3 secondary | Daily sync | Object Storage Provider us-west-2 | 90 days | ❌ NOT SET UP |
|
|
|
|
### 3.3 Backup Security
|
|
|
|
- **Versioning:** ON (all 3 buckets) ✅
|
|
- **Object Lock:** NOT enabled — needs implementation
|
|
- **Delete protection:** NOT configured
|
|
- **Backup credentials:** Write-only IAM user — NOT implemented (using admin credentials)
|
|
- **Encryption at rest:** Object Storage Provider server-side (default) — no client-side encryption
|
|
- **Credentials escrow:** Partial (in .env + Hudu)
|
|
|
|
**Fixes needed:**
|
|
1. Create IAM sub-user with `s3:PutObject` only (no delete, no list)
|
|
2. Enable Object Lock with 7-day minimum retention
|
|
3. Store write-only credentials separately from admin credentials
|
|
4. Client-side encrypt backup before upload (age/gpg) for the secondary copy
|
|
|
|
---
|
|
|
|
## 4. Failover / Failback
|
|
|
|
### 4.1 Warm Standby — Corrected Timing
|
|
|
|
The DR reviewer flagged this correctly: you cannot detect a 2-minute outage with a 5-minute check interval.
|
|
|
|
**Old:** Check every 5 min, failover after 2 min unreachable
|
|
**New:** Check every 30 seconds, failover after 4 failed checks (~2 min)
|
|
|
|
```python
|
|
# Pseudocode for corrected watchdog
|
|
CHECK_INTERVAL = 30 # seconds
|
|
FAILURE_THRESHOLD = 4 # consecutive failures
|
|
CONFIRMATION_WINDOW = 120 # seconds (2 min)
|
|
# Detection time: 4 * 30s = 120s = 2 minutes
|
|
# Sync + start time: ~45-90 seconds
|
|
# Effective RTO: ~3-3.5 minutes
|
|
# Stated RTO target: 5 minutes (ACHIEVABLE)
|
|
```
|
|
|
|
### 4.2 Health Check — Two Layers
|
|
|
|
**Layer 1 (Ping):** ICMP to Primary Host ([IP-REDACTED]) every 30s
|
|
**Layer 2 (HTTP):** `GET /[GENERIC-HEALTH-ENDPOINT]` on Tailscale IP — checks:
|
|
- Gateway heartbeat ≤ 5s old
|
|
- SQLite `PRAGMA quick_check` returns "ok"
|
|
- Local Ollama responding
|
|
|
|
**Decision Matrix:**
|
|
|
|
| Server | Hermes | Action |
|
|
|---|---|---|
|
|
| ✅ UP | ✅ UP | Stay dormant |
|
|
| ❌ DOWN | N/A | **Auto-takeover** — unambiguous |
|
|
| ✅ UP | ❌ DOWN | **ALERT + fence** — attempt SSH fence, only takeover if Primary Host Hermes confirmed stopped |
|
|
|
|
### 4.3 Fencing Protocol
|
|
|
|
```bash
|
|
# SSH Fence (preferred — server is up)
|
|
ssh [USER-REDACTED]@[IP-REDACTED] \
|
|
"sudo systemctl stop hermes && sudo systemctl mask hermes"
|
|
# Verify: returns "inactive"
|
|
ssh [USER-REDACTED]@[IP-REDACTED] "sudo systemctl is-active hermes"
|
|
|
|
# S3 Ownership Lock
|
|
# Object: s3://hermes-vps-backups/ACTIVE_OWNER
|
|
# Primary Host refreshes every 60s while healthy
|
|
# Standby writes itself as owner on takeover
|
|
# Primary Host reads on startup — if standby owns it, Primary Host stays dormant
|
|
```
|
|
|
|
### 4.4 Failback Protocol
|
|
|
|
**⚠️ Auto-failback is FORBIDDEN.** Human clearance required.
|
|
|
|
```bash
|
|
# FAILBACK STEPS (manual):
|
|
# 1. Confirm Primary Host is healthy: SSH + systemctl is-active hermes
|
|
# 2. Stop standby gateway: ssh Standby Host "hermes gateway stop"
|
|
# 3. Sync state from standby back to Primary Host (if needed):
|
|
# Rsync /root/.hermes/ from standby to Primary Host
|
|
# 4. Unmask Hermes on Primary Host:
|
|
# ssh Primary Host "sudo systemctl unmask hermes && sudo systemctl start hermes"
|
|
# 5. Verify Primary Host gateway is active (Telegram responds)
|
|
# 6. Standby returns to dormant state
|
|
# 7. Update S3 ownership lock to "core"
|
|
```
|
|
|
|
### 4.5 Provider/Account Outage Scenario
|
|
|
|
If the entire hosting provider (Primary Provider) is unavailable:
|
|
1. Standby Host (Secondary Provider) takes over as active Hermes host
|
|
2. DNS records updated to point to Standby Host IP ([IP-REDACTED])
|
|
3. Services migrate to Primary Provider replacement when available
|
|
4. No auto-failback — human decision
|
|
|
|
---
|
|
|
|
## 5. Monitoring & Alerting
|
|
|
|
### 5.1 Alert Thresholds
|
|
|
|
| Alert | Threshold | Action | Priority |
|
|
|---|---|---|---|
|
|
| Disk usage | > 80% | Notify, investigate | 🟡 P2 |
|
|
| Disk usage | > 90% | Notify, immediate cleanup | 🔴 P0 |
|
|
| Backup age | > 24h since last success | Notify | 🟡 P1 |
|
|
| Backup failure | Any failed job | Notify | 🔴 P0 |
|
|
| Hermes heartbeat | No heartbeat > 30s | Notify | 🔴 P0 |
|
|
| Hermes down + server up | Layer 2 fail, Layer 1 pass | IMMEDIATE alert | 🔴 P0 |
|
|
| TLS expiration | < 14 days | Notify | 🟡 P1 |
|
|
| TLS expiration | < 7 days | Notify + remind | 🔴 P0 |
|
|
| SSH fail spikes | > 10 failures/min | Investigate | 🟡 P1 |
|
|
| Service down | Caddy/nginx/Docker inactive | Notify, attempt restart | 🔴 P0 |
|
|
| S3 sync failure | Upload fails | Abort (memory consolidate) | 🔴 P0 |
|
|
| Standby stale | No sync > 30 min | Notify | 🟡 P1 |
|
|
|
|
### 5.2 Monitoring Stack
|
|
|
|
- **Per-server:** `prometheus-node-exporter` (all boxes)
|
|
- **Dashboard:** Prometheus → Grafana ()
|
|
- **Synthetic checks:** Uptime Kuma (on docker box, [IP-REDACTED])
|
|
- **Hermes watchdog:** `service-health-check.sh` (every 5 min, Primary Host)
|
|
- **Standby watcher:** `home-router-watchdog.sh` (every 5 min, Primary Host)
|
|
- **Apex mail:** `apex-mail-watchdog.sh` (every 5 min, Primary Host)
|
|
- **Alert delivery:** Telegram (via Hermes)
|
|
|
|
## 6. Restore Runbooks (Per Server)
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 5 minutes (via failover to Standby Host) |
|
|
| RPO target | 15 minutes (Hermes state), 24h (Docker data) |
|
|
| Actual RTO | ~3 minutes (corrected watchdog) |
|
|
| Actual RPO | Varies by data type (see below) |
|
|
|
|
**Restore steps:**
|
|
```bash
|
|
# Option A: Failover to standby (fastest — < 5 min)
|
|
# Standby Host detects Primary Host down → auto-takeover (Section 4.1)
|
|
|
|
# Option B: Full restore on replacement box
|
|
# Step 1 — Provision RS 2000 G12 on Primary Provider
|
|
# Step 2 — Apply base standard (Section 2.2)
|
|
# Step 3 — Install Hermes
|
|
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
|
|
# Step 4 — Restore Hermes state from latest S3:
|
|
aws s3 cp s3://hermes-vps-backups/live/state.db /root/.hermes/state.db \
|
|
--endpoint-url https://[S3-STORAGE-DOMAIN]
|
|
aws s3 cp s3://hermes-vps-backups/live/config.yaml /root/.hermes/config.yaml \
|
|
--endpoint-url https://[S3-STORAGE-DOMAIN]
|
|
# Step 5 — Restore .env from backup:
|
|
aws s3 cp s3://hermes-vps-backups/live/.env /root/.hermes/.env \
|
|
--endpoint-url https://[S3-STORAGE-DOMAIN]
|
|
chmod 600 /root/.hermes/.env
|
|
# Step 6 — Restore Docker stacks:
|
|
for dir in /root/docker/*/; do
|
|
(cd "dir" && docker compose up -d)
|
|
done
|
|
# Step 7 — Restore Caddy config:
|
|
aws s3 cp s3://hermes-vps-backups/live/config/Caddyfile /etc/caddy/Caddyfile
|
|
systemctl restart caddy
|
|
# Step 8 — Start Hermes gateway:
|
|
hermes gateway run --replace
|
|
# Step 9 — Verify:
|
|
curl -s https://[OPS-PORTAL-DOMAIN]/[GENERIC-HEALTH-ENDPOINT]
|
|
curl -s https://[DOCUMENT-SIGNING-DOMAIN]
|
|
# Check Telegram responds to /ping
|
|
```
|
|
|
|
### 6.2 [AI-SERVICE-DOMAIN] (Secondary Provider CPX41) — 🔴 Critical
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 2 hours |
|
|
| RPO target | 24 hours |
|
|
| Current disk | **🔴 92%** — must clean before migration |
|
|
|
|
**⚠️ Pre-reboot checklist:**
|
|
```bash
|
|
# Verify at least one alternative model provider works
|
|
curl https://[AI-ADMIN-DOMAIN]/[GENERIC-LLM-ENDPOINT] \
|
|
-H "Authorization: Bearer (cat ~/.hermes/.env | grep ADMIN_AI | cut -d= -f2)" \
|
|
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"ping"}]}'
|
|
# Ensure local Ollama on Primary Host is running
|
|
curl -s http://[IP-REDACTED]:11434/[GENERIC-OLLAMA-ENDPOINT] || systemctl start ollama
|
|
# Notify all Hermes users ([CEO], [Operations Manager], [Developer])
|
|
```
|
|
|
|
**Restore steps:**
|
|
```bash
|
|
# Step 1 — Provision replacement (CPX41 or Primary Provider equivalent)
|
|
# Step 2 — Apply base standard (Section 2.2)
|
|
# Step 3 — Deploy Docker stacks:
|
|
cd /opt/litellm && docker compose up -d
|
|
cd /opt/openwebui && docker compose up -d
|
|
cd /opt/qdrant && docker compose up -d
|
|
# Step 4 — Pull models:
|
|
ollama pull llama3.2:3b
|
|
# Step 5 — Configure DNS: [AI-ADMIN-DOMAIN] → new IP
|
|
# Step 6 — Update all Hermes config.yaml files with new base_url
|
|
# Step 7 — Verify model access for all profiles
|
|
```
|
|
|
|
### 6.3 Legacy Web Host (Secondary Provider CPX21, Server Management Panel) — 🟡 Important
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 4 hours |
|
|
| RPO target | 24 hours |
|
|
| Backup method | Server Management Panel daily backup + manual DB |
|
|
|
|
**Restore steps:**
|
|
```bash
|
|
# Step 1 — Provision CPX21 on Secondary Provider
|
|
# Step 2 — Connect to Server Management Panel panel, link server
|
|
# Step 3 — Server Management Panel provisions nginx, MariaDB, PHP
|
|
# Step 4 — Restore WordPress from Server Management Panel backup → verify apex site loads
|
|
# Step 5 — Deploy company-prefix-infra SSH key in Server Management Panel → SSH Access
|
|
# Step 6 — Whitelist Primary Host IP ([IP-REDACTED]) in Server Management Panel firewall
|
|
# Step 7 — Reconfigure Postfix relay:
|
|
postconf -e "relayhost = [MAIL-SERVER-DOMAIN]:2525"
|
|
postconf -e "smtp_sasl_auth_enable = yes"
|
|
echo "[MAIL-SERVER-DOMAIN]:2525 [EMAIL-REDACTED]:[CREDENTIAL-REDACTED]" > /etc/postfix/sasl_passwd
|
|
postmap /etc/postfix/sasl_passwd && systemctl restart postfix
|
|
# Step 8 — Flush postfix queue:
|
|
postqueue -f
|
|
```
|
|
|
|
### 6.4 UNMS (Secondary Provider CPX21) — 🟡 Important
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 4 hours |
|
|
| RPO target | 24 hours |
|
|
| Containers | 9 (Docker compose stack) |
|
|
|
|
**Restore steps:**
|
|
```bash
|
|
# Step 1 — Provision CPX21 → Docker
|
|
# Step 2 — Restore Postgres data volumes from S3
|
|
# Step 3 — Deploy docker-compose.yml
|
|
cd /opt/unms && docker compose up -d
|
|
# Step 4 — Verify UCRM + UNMS web login
|
|
# Step 5 — Re-register tower routers via UNMS
|
|
```
|
|
|
|
### 6.5 Hudu (Secondary Provider CPX21) — 🟡 Important
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 4 hours |
|
|
| RPO target | 24 hours |
|
|
| Containers | 5 (Docker compose) |
|
|
| Auto-backup | Hudu built-in S3 backup |
|
|
|
|
**Restore steps:**
|
|
```bash
|
|
# Step 1 — Provision CPX21 → Docker
|
|
# Step 2 — Deploy Hudu Docker stack
|
|
# Step 3 — Restore Postgres from last backup
|
|
# Step 4 — Verify web login + recent documents accessible
|
|
```
|
|
|
|
### 6.6 UniFi (Secondary Provider CPX21) — 🟡 Important
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 4 hours |
|
|
| RPO target | 24 hours |
|
|
| Type | Native install (not Docker) |
|
|
|
|
**Restore steps:**
|
|
```bash
|
|
# Step 1 — Provision CPX21
|
|
# Step 2 — Install UniFi Controller (native)
|
|
# Step 3 — Restore from UniFi backup file
|
|
# Step 4 — Adopt APs and switches
|
|
```
|
|
|
|
### 6.7 n8n (Secondary Provider CPX11) — 🟢 Standard
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 8 hours |
|
|
| Containers | 2 (n8n + postgres) |
|
|
|
|
**Restore:**
|
|
```bash
|
|
# Provision → Docker → restore postgres volume
|
|
cd /opt/n8n && docker compose up -d
|
|
```
|
|
|
|
### 6.8 docker box (Secondary Provider CPX11) — 🟢 Standard
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 8 hours |
|
|
| Containers | 8 (NPM, RustDesk, Uptime Kuma, Server Management Panel) |
|
|
|
|
**Restore:**
|
|
```bash
|
|
# Provision → Docker → restore compose stacks + volumes
|
|
```
|
|
|
|
### 6.9 fleettracker360 (Secondary Provider CPX11) — 🟢 Standard
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 8 hours |
|
|
| Type | Apache + MariaDB (LAMP) |
|
|
|
|
**Restore:**
|
|
```bash
|
|
# Provision → Apache + MariaDB → restore web root + DB
|
|
```
|
|
|
|
### 6.10 developer-vps (Secondary Provider CPX21) — 🟢 Standard
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | Coordinate with [Developer] |
|
|
| Type | Independent Hermes + local LLM |
|
|
|
|
**⚠️ SHADOW IT — needs documentation.**
|
|
|
|
**Current known config:**
|
|
- Hermes gateway with admin-ai model access
|
|
- Llama 3.2 1B local fallback (systemd: llama-server)
|
|
- Cron: hermes-heartbeat.sh every minute
|
|
- Minimal services, no Docker
|
|
|
|
**Actions needed:**
|
|
1. Document [Developer]'s full Hermes config.yaml and .env
|
|
2. Backup to S3 under s3://hermes-vps-backups/developer-vps/
|
|
3. Add [Developer]'s config to DR plan once documented
|
|
|
|
### 6.11 Standby Host (Secondary Provider CPX21) — 🔴 Standby
|
|
|
|
| Attribute | Detail |
|
|
|---|---|
|
|
| RTO target | 2 minutes |
|
|
| RPO target | 15 minutes |
|
|
| Type | Warm standby — gateway dormant |
|
|
|
|
**Activation:**
|
|
```bash
|
|
# Automatic: watchdog detects Primary Host down → S3 sync → start gateway
|
|
# Manual:
|
|
ssh [USER-REDACTED]@[IP-REDACTED]
|
|
sudo -i
|
|
hermes gateway run --replace
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Testing & Validation
|
|
|
|
### 7.1 Restore Testing Schedule
|
|
|
|
| Test | Frequency | Who |
|
|
|---|---|---|
|
|
| Verify backup job completion (all servers) | Daily | Automated alert |
|
|
| Check backup age per server | Daily | Automated |
|
|
| Restore one database dump to test environment | Monthly | Network Team |
|
|
| Restore one full application stack | Quarterly | Network Team |
|
|
| Full DR simulation — Primary Host failover to Standby Host | Quarterly | Network Team |
|
|
| S3 credential restore/access test | Quarterly | Network Team |
|
|
| Document restore test results (date, tester, duration, findings) | Per test | Network Team |
|
|
|
|
### 7.2 Validation Criteria
|
|
|
|
Each restore test must produce:
|
|
- Date + tester name
|
|
- Server/application tested
|
|
- Backup artifact used (path + timestamp)
|
|
- Restore duration (start → fully operational)
|
|
- Data validation: check a known record exists (specific row, file, or document)
|
|
- Issues encountered
|
|
- Remediation ticket (if needed)
|
|
|
|
### 7.3 Quarterly DR Simulation
|
|
|
|
Scenario: "Primary Host is completely gone. Primary Provider account inaccessible. Standby Host has been active for 4 hours. Now Primary Provider is back — fail back."
|
|
|
|
Steps to validate:
|
|
1. Simulate Primary Provider provider outage (block Primary Host IP)
|
|
2. Verify Standby Host takes over within 5 minutes
|
|
3. Send a Telegram message through standby
|
|
4. Verify all cron jobs run on standby
|
|
5. Simulate Primary Provider recovery
|
|
6. Execute manual failback per Section 4.4
|
|
7. Verify no data loss, no split-brain
|
|
|
|
---
|
|
|
|
## 8. Security
|
|
|
|
### 8.1 Current Security Posture
|
|
|
|
| Control | Status | Notes |
|
|
|---|---|---|
|
|
| SSH keys only | ✅ Done | Password auth disabled |
|
|
| Root SSH disabled | ⚠️ Partial | Some servers still allow root |
|
|
| UFW host firewall | ✅ All | Standard on all boxes |
|
|
| fail2ban | ✅ All | Standard on all boxes |
|
|
| S3 versioning | ✅ All buckets | Enabled |
|
|
| S3 Object Lock | ❌ Not done | Needs implementation |
|
|
| S3 write-only IAM | ❌ Not done | Currently using admin creds |
|
|
| Backup encryption | ❌ Not done | Server-side only; no client-side |
|
|
| Secrets escrow | 🟡 Partial | .env + Hudu — not all secrets documented |
|
|
| TLS everywhere | ✅ All public endpoints | Caddy auto-certs |
|
|
|
|
### 8.3 Break-Glass Access
|
|
|
|
If company-prefix-infra key is lost or revoked:
|
|
1. **Secondary Provider servers:** Use Secondary Provider console → Rescue mode → mount filesystem → redeploy key
|
|
2. **Primary Provider servers:** Use SCP web console → VNC → log in with root password → redeploy key
|
|
3. **Root passwords:** Stored in ~/.hermes/.env (chmod 600) and Hudu
|
|
|
|
---
|
|
|