Publish Docs Site / build (push) Failing after 5s
- mkdocs.yml with dark slate theme, nav for 12 ITPP projects - build-docs.sh aggregates docs from all project repos - .gitea/workflows/docs-publish.yml for nightly rebuild+deploy - README and CHANGELOG for the itpp-docs repo itself - docs-source/ populated from all 12 repos - site/ ready for deployment to docs.itpropartner.com
159 lines
6.0 KiB
Markdown
159 lines
6.0 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 retention shown)
|
|
- Average snapshot size: 16MB files + 74KB database
|
|
- Total: ~1.4GB for full snapshot set
|
|
|
|
### 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. Access is controlled by Caddy routing — only requests through my.itpropartner.com reach the app. Internal network only.
|