mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 09:53:08 +02:00
- surface backup/cleanup cron logs + backup freshness status - email xamxam@erg.be when SQLite backups go stale (backup watchdog) - sync SQLite snapshots to Nextcloud WebDAV + remote-freshness watchdog - precise retention pruning, manual sync in check recipe, and Nextcloud-sync docs
134 lines
5.7 KiB
Markdown
134 lines
5.7 KiB
Markdown
# Nextcloud off-site backup sync
|
|
|
|
XAMXAM pushes its latest SQLite snapshot to a Nextcloud folder so that a
|
|
server/disk failure doesn't take the backup history with it. This is the
|
|
implementation of *Phase 5 — Remote Sync* from [backup-plan.md](backup-plan.md).
|
|
|
|
## What it does
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| **Destination** | `https://cloud.erg.school/remote.php/dav/files/xamxam%40erg.be/XAMXAM-BCK` |
|
|
| **Protocol** | WebDAV (HTTP `PUT` / `PROPFIND` / `DELETE`) |
|
|
| **Credentials** | Reused from `smtp_settings` (`xamxam@erg.be`), decrypted via `Crypto` |
|
|
| **Transport** | PHP `curl` extension — no `rclone`, no shell `curl` |
|
|
| **What is uploaded** | The newest `db-*.db.gz` snapshot from `/var/backups/xamxam` |
|
|
| **Remote filename** | `xamxam-db-<timestamp>.db.gz` (timestamped, not overwritten) |
|
|
| **Remote retention** | Last **7** snapshots (configurable via `REMOTE_KEEP`) |
|
|
| **Cadence** | Daily, 20 min after the 02:00 snapshot |
|
|
|
|
The uploads are **SQLite snapshots only** — the CSV and files-ZIP exports
|
|
(see [export.md](export.md)) are *not* pushed. This keeps Nextcloud usage
|
|
minimal; the full export set remains available on-demand from the admin panel.
|
|
|
|
### Sizing
|
|
|
|
As of writing, the live DB is ~2.5 MB and compresses to ~475 KB. Seven daily
|
|
snapshots are therefore ~3.3 MB — a negligible fraction of the 10 GB Nextcloud
|
|
quota for `xamxam@erg.be`. If more history is desired, raise `REMOTE_KEEP` in
|
|
the cron line; there is ample headroom.
|
|
|
|
## Script: `scripts/nextcloud-sync.php`
|
|
|
|
CLI script (deployed to `/usr/local/bin/nextcloud-sync.php`), run by cron.
|
|
|
|
Flow:
|
|
1. Resolve `APP_ROOT` (`/var/www/xamxam` in prod, `app/` in dev) and load the
|
|
composer autoloader.
|
|
2. No-op `DatabaseMigrations` (read-only — the sync must never mutate the DB).
|
|
3. Load SMTP credentials via `SmtpRelay::getSettings()` (decrypts the password).
|
|
4. Find the newest `db-*.db.gz` in `/var/backups/xamxam`.
|
|
5. `PUT` it to the WebDAV path (streaming upload, memory-safe).
|
|
6. `PROPFIND` the uploaded file and verify its size matches the local file
|
|
(integrity check — a failed sync is detected, not assumed).
|
|
7. `PROPFIND` the folder, list `xamxam-db-*.db.gz`, and `DELETE` the oldest
|
|
beyond `REMOTE_KEEP` (`7` by default).
|
|
|
|
Exit code `0` on success, `1` on any failure (logged to
|
|
`/var/log/xamxam-backup-YYYY-MM-DD.log`). A sync failure never blocks the
|
|
local backup — the two jobs are separate cron entries.
|
|
|
|
### Environment variables
|
|
|
|
| Variable | Default | Meaning |
|
|
|----------|---------|---------|
|
|
| `REMOTE_KEEP` | `7` | Number of remote snapshots to retain |
|
|
|
|
## Cron wiring
|
|
|
|
In `/etc/cron.d/xamxam-backup` (deployed from `deploy/xamxam-backup.cron`):
|
|
|
|
```
|
|
# Daily snapshot at 2am — kept 90 days
|
|
0 2 * * * www-data RETENTION_DAYS=90 /usr/local/bin/backup-sqlite.sh >> /var/log/xamxam-backup-$(date +\%Y-\%m-\%d).log 2>&1
|
|
|
|
# Push the latest snapshot to Nextcloud WebDAV (daily, after the 2am snapshot)
|
|
20 2 * * * www-data php /usr/local/bin/nextcloud-sync.php >> /var/log/xamxam-backup-$(date +\%Y-\%m-\%d).log 2>&1
|
|
```
|
|
|
|
The sync runs as `www-data`, so it shares the file permissions of the
|
|
backup script (`/var/backups/xamxam` is `www-data:www-data`). The `.env`
|
|
holding the crypto key is `640 www-data:xamxam` — readable by `www-data`, so
|
|
`Crypto::decrypt` works from the CLI.
|
|
|
|
## Monitoring
|
|
|
|
The backup watchdog ([`scripts/backup-watchdog.php`](../scripts/backup-watchdog.php))
|
|
now checks **both** the local snapshots and the Nextcloud copy:
|
|
|
|
- **Local** — newest `db-*.db.gz` older than 2 h → alert.
|
|
- **Remote** — newest `xamxam-db-*.db.gz` older than 48 h (or none found) → alert.
|
|
|
|
Both feed the same anti-flood email to `xamxam@erg.be` (or the configured
|
|
`notify_email`). A single alert can report either or both issues.
|
|
|
|
## Manual verification
|
|
|
|
```bash
|
|
# Read the backup folder (expect 207 Multi-Status)
|
|
curl -sS -u 'xamxam@erg.be:<password>' -X PROPFIND --head \
|
|
'https://cloud.erg.school/remote.php/dav/files/xamxam%40erg.be/XAMXAM-BCK' \
|
|
-o /dev/null -w '%{http_code}\n'
|
|
|
|
# Upload a test file (expect 201), then delete it (expect 204)
|
|
curl -sS -u 'xamxam@erg.be:<password>' -X PUT --data-binary test \
|
|
'https://cloud.erg.school/remote.php/dav/files/xamxam%40erg.be/XAMXAM-BCK/_write_test.txt' \
|
|
-o /dev/null -w '%{http_code}\n'
|
|
curl -sS -u 'xamxam@erg.be:<password>' -X DELETE \
|
|
'https://cloud.erg.school/remote.php/dav/files/xamxam%40erg.be/XAMXAM-BCK/_write_test.txt' \
|
|
-o /dev/null -w '%{http_code}\n'
|
|
|
|
# Run the sync manually (server only)
|
|
sudo -u www-data php /usr/local/bin/nextcloud-sync.php
|
|
```
|
|
|
|
## Restoring from Nextcloud
|
|
|
|
The remote snapshot is the same `.db.gz` the local backup produces, so
|
|
restoring follows the standard procedure in [deployment.md](deployment.md):
|
|
|
|
```bash
|
|
# Download the snapshot
|
|
curl -sS -u 'xamxam@erg.be:<password>' -O \
|
|
'https://cloud.erg.school/remote.php/dav/files/xamxam%40erg.be/XAMXAM-BCK/xamxam-db-<timestamp>.db.gz'
|
|
|
|
# Restore
|
|
sudo systemctl stop nginx
|
|
gunzip -c xamxam-db-<timestamp>.db.gz > /var/www/xamxam/storage/xamxam.db
|
|
chown www-data:www-data /var/www/xamxam/storage/xamxam.db
|
|
chmod 660 /var/www/xamxam/storage/xamxam.db
|
|
sudo systemctl start nginx
|
|
```
|
|
|
|
## Security notes
|
|
|
|
- Credentials are read from the DB (`smtp_settings`) and **never** logged or
|
|
echoed; only the resulting success/failure messages are written to the log.
|
|
- The Nextcloud password is the same LDAP/SSO-backed `xamxam@erg.be`
|
|
credential used for SMTP — the WebDAV Basic-auth endpoint accepts it directly
|
|
(verified live: `PUT` → 201, `PROPFIND` → 207, `DELETE` → 204).
|
|
- `CURLOPT_SSL_VERIFYPEER` / `CURLOPT_SSL_VERIFYHOST` are enabled (certificate
|
|
chain is validated).
|
|
- The sync is one-way and additive-then-pruned: it never deletes a snapshot
|
|
until a newer one has been confirmed uploaded.
|