Files
itpp-infrastructure/docs/backup-restore/ARCHITECTURE.md
T

161 lines
6.4 KiB
Markdown

# Backup-Restore — Architecture
## Topology
```
INTERNET
|
[Caddy on Core]
my.itpropartner.com
|
+---------------+---------------+
| | |
/backups/* /api/restore /api/backup
/api/log /api/download /api/delete
| | |
+-------+-------+-------+-------+
|
app3 (152.53.241.111)
netcup RS 4000
|
[Flask :8090]
/opt/backup-restore/
|
+-------------------+-------------------+
| | |
snapshot.sh app.py (UI+API) snapshots/
(cron 1AM,1PM) Jinja templates /opt/backup-restore/
| | snapshots/<domain>/
v v |
[tar files] [render HTML] +------+------+
[mysqldump] [REST API] | | |
| | .tar.gz .sql note.txt
v v
/opt/backup-restore/ [Browser]
snapshots/<domain>/
<timestamp>/
```
## Data Flow — Manual Backup
```
Browser (user clicks "Backup Now")
|
|-- POST /api/backup {"domain":"x.com","note":"pre-deploy"}
| |
| v
| Caddy → app3:8090
| |
| v
| Flask api_backup()
| |
| |-- Parse nginx config → find htdocs path
| |-- tar -czf files.tar.gz (timeout 300s)
| |-- Parse wp-config.php → find DB_NAME
| |-- mysqldump → database.sql (timeout 300s)
| |-- Save note.txt, size.txt
| |-- Return {"ok":true, "snapshot":"<timestamp>"}
| |
| v
| snapshots/x.com/2026-07-20_163208/
| files.tar.gz (16MB)
| database.sql (74KB)
| note.txt ("pre-deploy")
| size.txt
|
v
Browser reloads → new snapshot in list
```
## Data Flow — Restore
```
Browser (user clicks Restore on a snapshot)
|
|-- POST /api/restore {"domain":"x.com","snapshot":"2026-07-20_130001"}
| |
| v
| Caddy → app3:8090 (flush_interval -1, 300s timeouts)
| |
| v
| Flask api_restore()
| |
| |-- Find snapshot path
| |-- tar -xzf files.tar.gz → htdocs (timeout 300s)
| |-- mysql < database.sql → WordPress DB (timeout 300s)
| |-- chown -R site-user:site-user
| |-- Log to restore.log: "TS|x.com|snap_id|OK"
| |-- Return {"ok":true, "msg":"x.com restored to <snap>"}
| |
| v
| Site is restored
|
v
Browser shows success toast → Restore History updates
```
## Components
### 1. Flask App (`/opt/backup-restore/app/app.py`)
- Single-file Flask application, port 8090
- Jinja2 templating for backup dashboard (render_template_string)
- 6 API endpoints (backup, restore, delete, download, log, index)
- All HTML/CSS/JS inline in a single Python triple-quoted string
- No auth — accessible via Caddy-only routing
- Systemd: `backup-restore.service`
### 2. Snapshot Engine (`/opt/backup-restore/snapshot.sh`)
- Bash script, runs at 1 AM and 1 PM via cron
- Iterates all WordPress sites in `/etc/nginx/sites-enabled/`
- Creates: files.tar.gz (document root), database.sql (MySQL dump)
- Auto-cleanup: deletes snapshots older than 30 days
- Log: `/opt/backup-restore/logs/snapshots.log`
### 3. Snapshot Storage (`/opt/backup-restore/snapshots/`)
- Structure: `/<domain>/<YYYY-MM-DD_HHMMSS>/`
- 9 WordPress domains, 10 snapshots each (10 days shown in UI)
- Retention: **30 days**`snapshot.sh` auto-deletes snapshots older than 30 days via cron
- Average snapshot size: 16MB files + 74KB database
- Total: ~1.4GB for full snapshot set
- **⚠ Local only** — not synced to S3. If app3 fails, all local snapshots are lost. Daily 3 AM S3 backup (`app3-backup.sh`) provides coarser off-site coverage.
### 4. Restore Log (`/opt/backup-restore/logs/restore.log`)
- Pipe-delimited format: `timestamp|domain|snapshot_id|status`
- Written by api_restore() on every restore attempt
- Read by /api/log → displayed in Restore History table
- Last 50 entries retained
### 5. Caddy Proxy (on Core)
- `handle /api/backup` → app3:8090
- `handle /api/restore` → app3:8090 (flush_interval -1, 300s read/write timeouts)
- `handle /api/download/*` → app3:8090
- `handle /api/log` → app3:8090
- `handle_path /backups/*` → app3:8090 (300s timeouts for long restores)
- Domain: my.itpropartner.com
## 9 Hosted WordPress Sites
All served by CloudPanel on app3, backed up by this system:
| Domain | htdocs Path | DB Pattern |
|---|---|---|
| apextrackexperience.com | /home/apx/htdocs/apextrackexperience.com | wp-config DB_NAME |
| boxpilotlogistics.com | /home/boxpilotlogistics/htdocs/boxpilotlogistics.com | wp-config DB_NAME |
| debtrecoveryexperts.com | /home/debtrecoveryexperts/... | wp-config DB_NAME |
| iamgmb.com | /home/iamgmb/... | wp-config DB_NAME |
| katiewattdesign.com | /home/katiewattdesign/htdocs/katiewattdesign.com | wp-config DB_NAME |
| katiewattsdesign.com | /home/katiewattsdesign/... | wp-config DB_NAME |
| mainwp.itpropartner.com | /home/mainwp/... | wp-config DB_NAME |
| vigilanttac.com | /home/vigilanttac/... | wp-config DB_NAME |
| voipsimplicity.com | /home/voipsimplicity/... | wp-config DB_NAME |
## Key Design Decisions
1. **Single-file Flask app:** No package structure needed — the app has 6 endpoints and one HTML template. Keeping it in one file makes deployment trivial (scp + systemctl restart).
2. **Caddy on Core as single entry point:** app3 isn't exposed to the internet directly. All access goes through Core's Caddy with proper timeouts. The restore operation takes 30-45s and Caddy's default proxy timeout was killing connections mid-operation.
3. **Tar + mysqldump over rsync:** Snapshots are point-in-time archives, not incremental backups. Each snapshot is self-contained (files.tar.gz + database.sql). Restore is a single operation with no dependency chain.
4. **No auth on backup API:** The endpoints have no authentication. The UI at `my.itpropartner.com/backups/` is publicly accessible through Core's Caddy. Access control relies on obscurity (the domain is not widely known) and Caddy's TLS termination. For production use, consider adding IP whitelisting or Caddy basic auth.