Live off-site Postgres copy on 247 replaces the nightly pg_dump; backup retention in deploy.sh
- ops/pgbackup: SSH tunnel + pg_receivewal (slot zareian_247) + daily verified pg_basebackup (keep 7) and a restore check, all run on 78.38.35.247 as systemd units - /api/health reports offsiteBackup (slot active/lag/lost) and warns; OFFSITE_WAL_SLOT setting - removed statuslog backup_db/backup_verify and their health warnings - deploy.sh keeps 3 pre-deploy dumps, 3 local patches, drops manual backups after 30 days - docs: operations backup section, security item 14 closed, CLAUDE.md status Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,25 +72,73 @@ curl -i https://mathapi.tadrisino2.ir/api/health
|
||||
|
||||
## بکاپ و بازیابی
|
||||
|
||||
دیتابیسِ سرور Postgres است (cluster جدای `zareian`، پورت ۵۴۴۰، فقط localhost؛ راهنمای کامل: `گزارش-۲۹/راهنمای-Postgres-و-Redis.html`). سه لایه:
|
||||
دیتابیسِ سرور Postgres است (cluster جدای `zareian`، پورت ۵۴۴۰، فقط localhost؛ راهنمای کامل: `گزارش-۲۹/راهنمای-Postgres-و-Redis.html`).
|
||||
|
||||
| لایه | چه زمانی | نگهداری | کجا |
|
||||
| لایه | چه چیزی | از دست رفتنِ داده در بدترین حالت | کجا |
|
||||
|---|---|---|---|
|
||||
| پیش از migrate | هر `./deploy.sh` (`pg_dump -Fc`؛ اگر نشد migrate اجرا نمیشود) | ۲ تای آخر | `.deploy-backups/db-before-deploy-*.pgdump` |
|
||||
| روزانه | cron: `python manage.py backup_db` (`statuslog/backup.py`) | ۷ روزِ آخر + ۴ هفتهی آخر | `.deploy-backups/scheduled/` + `status.json` |
|
||||
| بیرونِ ماشین | همان فرمان، اگر `BACKUP_OFFSITE_CMD` در `.env` باشد (فرمانی با `{file}`، مثلاً rsync/scp/rclone) | دستِ مقصد | مقصد را ادمین تعیین میکند؛ تا تعیین نشده `/api/health` هشدار میدهد |
|
||||
| کپیِ زنده (اصلی) | هر تغییرِ دیتابیس لحظهای به سرورِ دیگر میرود + یک نسخهی کامل در روز؛ بازیابی تا هر لحظه از ۷ روزِ اخیر | چند ثانیهی آخر | `administrator@78.38.35.247:~/zareian-pgbackup/` |
|
||||
| پیش از migrate | `./deploy.sh` (`pg_dump -Fc`؛ اگر نشد migrate اجرا نمیشود)؛ برای برگشتن از migrationِ خراب | — | `~/mathapp-v2/.deploy-backups/db-before-deploy-*.pgdump` (۲ تای آخر) |
|
||||
| ایمیجِ کلِ ماشین | کارِ ادمینِ سرور، روزانه؛ ما محتوایش را نسنجیدهایم | — | دستِ ادمین |
|
||||
|
||||
نصبِ بکاپِ شبانه (یک بار، با `crontab -e` حسابِ `administrator`؛ بکاپِ crontab را پیش از ویرایش بگیرید):
|
||||
بکاپِ شبانهی جداگانه (`backup_db` با cron) حذف شد: کپیِ زنده همان کار را بهتر میکند و ایمیجِ روزانه را ادمین دارد.
|
||||
|
||||
### کپیِ زنده چطور کار میکند
|
||||
|
||||
Postgres هر تغییر را اول در یک دفترِ پیوسته (WAL) مینویسد. سرورِ ۲۴۷ این دفتر را با ابزارِ خودِ Postgres
|
||||
(`pg_receivewal`) لحظهای میگیرد، نه کپیِ کلِ دیتابیس: فقط تغییرات میروند. روزی یک بار هم یک نسخهی کامل
|
||||
(`pg_basebackup`) گرفته میشود؛ ۷ نسخهی آخر میمانند و WALِ قدیمیتر از قدیمیترینشان پاک میشود، پس حجم
|
||||
محدود است. بازیابی = آخرین نسخهی کاملِ پیش از لحظهی دلخواه + بازپخشِ WAL تا آن لحظه.
|
||||
|
||||
```
|
||||
15 2 * * * cd /home/administrator/mathapp-v2 && venv/bin/python manage.py backup_db >> .deploy-backups/scheduled/cron.log 2>&1
|
||||
۲۴۹ (اپ) ۲۴۷ (بکاپ)
|
||||
Postgres 5440 (localhost) ◄── تونلِ SSH ─── zareian-pg-tunnel (127.0.0.1:15440)
|
||||
اسلاتِ zareian_247 zareian-pg-wal → wal/ (لحظهای)
|
||||
نقشِ zareian_replicator zareian-pg-basebackup.timer → base/ (هر روز ۰۴:۳۰، ۷ تا)
|
||||
```
|
||||
|
||||
**آزمونِ بازیابی (ماهی یک بار، و پس از هر تغییرِ بکاپ):** `venv/bin/python manage.py backup_verify` آخرین بکاپ را روی دیتابیسِ آزمایشیِ `<DB_NAME>_restorecheck` برمیگرداند، شمارشِ کاربر/ورود/سفارش/تراکنش/لاگِ وضعیت را با لحظهی بکاپ میسنجد و آن دیتابیس را میاندازد؛ به دیتابیسِ اصلی دست نمیزند. نقشِ برنامه باید `CREATEDB` داشته باشد (`ALTER ROLE zareian_app CREATEDB;` با ادمینِ Postgres). فایلِ دستخورده را با هش رد میکند.
|
||||
- **۲۴۹ را نمیخواباند:** اپ منتظرِ ۲۴۷ نمیماند (تکثیرِ ناهمزمان). اگر ۲۴۷ قطع شود، اسلات تغییرات را نگه میدارد تا
|
||||
وصل شود، ولی حداکثر ۱ گیگ (`max_slot_wal_keep_size`)؛ بیش از آن اسلات «lost» میشود و دیسکِ ۲۴۹ امن میماند.
|
||||
نسخهی کامل با `--checkpoint=spread --max-rate=20M` گرفته میشود.
|
||||
- **امنیت:** کلیدِ SSHِ ۲۴۷ در `authorized_keys`ِ ۲۴۹ با `restrict,port-forwarding,permitopen="127.0.0.1:5440"`
|
||||
محدود است (نه شِل، نه پورتِ دیگر). رمزِ نقشِ `zareian_replicator` فقط در `~/.pgpass`ِ ۲۴۷ است. این نقش
|
||||
همهی داده را میخواند (طبیعتِ بکاپ)، پس `~/zareian-pgbackup` با مجوز ۷۰۰ است. ترافیک در شبکهی داخلی است
|
||||
(۲۴۹ = `192.168.200.164`، ۲۴۷ = `192.168.200.167`).
|
||||
- **Postgresِ ۲۴۷ را به کار نمیگیریم:** سرویسِ `postgresql@14-main` آنجا مالِ دیگران است. ما فقط ابزارهای
|
||||
Postgres 14 را اجرا میکنیم؛ آزمونِ بازیابی یک Postgresِ موقت روی سوکتِ داخلِ پوشهی خودمان بالا میآورد.
|
||||
- **پایش:** `/api/health` (با `X-Internal-Key`) زیرِ `offsiteBackup` وضعیت و عقبماندگی را میدهد و اگر ۲۴۷
|
||||
وصل نباشد، عقب باشد یا اسلات lost شده باشد هشدار میدهد (`OFFSITE_WAL_SLOT=zareian_247` در `.env`ِ ۲۴۹).
|
||||
شکستِ نسخهی کاملِ روزانه از ۲۴۹ دیده نمیشود: روی ۲۴۷ `systemctl status zareian-pg-basebackup` و
|
||||
`journalctl -u zareian-pg-basebackup`. شکستش داده را از دست نمیدهد (WAL از آخرین نسخهی سالم نگه داشته
|
||||
میشود)، فقط بازیابی را کندتر میکند.
|
||||
|
||||
**بازیابیِ واقعی** (دیتابیسِ اصلی خراب شد): `sudo systemctl stop zareian-mathapp` ← از دیتابیسِ فعلی یک `pg_dump` دیگر بگیرید ← `dropdb`/`createdb` ← `pg_restore --no-owner -d zareian_mathapp <فایل>` ← `start` ← `/api/health`. سفارشهای بعد از زمانِ بکاپ را با پنلِ زرینپال/بازار تطبیق دهید.
|
||||
فایلها در `ops/pgbackup/` ریپو. روی ۲۴۷: یونیتها در `/etc/systemd/system/zareian-pg-*`، اسکریپتها در
|
||||
`~/zareian-pgbackup/bin/`. هر دو با دست کپی میشوند (`deploy.sh` فقط ۲۴۹ را میشناسد)؛ بعد از تغییرِ
|
||||
یکی از آنها: کپی، `sudo systemctl daemon-reload`، و `restart` همان یونیت.
|
||||
|
||||
آزمودهشده: بکاپ، نگهداری، بازیابی و ردشدنِ فایلِ خراب روی Postgresِ لپتاپ. آزمودهنشده: زیر cron روی سرور، دیسکِ نزدیک به پُر، `BACKUP_OFFSITE_CMD` واقعی.
|
||||
### آزمونِ بازیابی (ماهی یک بار)
|
||||
|
||||
روی ۲۴۷: `~/zareian-pgbackup/bin/restore-check.sh` (یا با یک لحظه: `… '2026-10-10 16:30:00+03:30'`).
|
||||
آخرین نسخهی کامل + همهی WAL را در پوشهی موقت بالا میآورد، شمارش و آخرین زمانِ کاربر/ورود/سفارش/تراکنش/
|
||||
تغییرِ وضعیت را چاپ میکند و پاک میکند. همان پرسش را روی ۲۴۹ بزنید؛ باید یکی باشند (جز تغییرِ همان چند ثانیه).
|
||||
|
||||
### اگر ۲۴۷ مدتی قطع بود
|
||||
|
||||
`/api/health` میگوید `disconnected`. با برگشتنِ ۲۴۷ سرویسها خودشان وصل میشوند و عقبماندگی را میگیرند. اگر
|
||||
`lost` شد (بیش از ۱ گیگ تغییر در قطعی): روی ۲۴۷
|
||||
`systemctl stop zareian-pg-wal`، روی ۲۴۹ اسلات را بیندازید
|
||||
(`SELECT pg_drop_replication_slot('zareian_247')`)، روی ۲۴۷ بسازید
|
||||
(`pg_receivewal -h 127.0.0.1 -p 15440 -U zareian_replicator --create-slot --slot=zareian_247`)،
|
||||
`systemctl start zareian-pg-wal` و `systemctl start zareian-pg-basebackup` (نسخهی کاملِ تازه؛ WALِ پیش از آن
|
||||
بریده است).
|
||||
|
||||
### بازیابیِ واقعی
|
||||
|
||||
- **دیتابیسِ ۲۴۹ خراب شد یا دادهای اشتباهی پاک شد:** لحظهی درست را پیدا کنید، روی ۲۴۷ با `restore-check.sh`
|
||||
تا آن لحظه آزمایش کنید؛ وقتی درست بود، با `KEEP=1` اجرا کنید تا Postgresِ موقت بماند، از آن با
|
||||
`pg_dump -Fc -h <پوشه> -U postgres zareian_mathapp` خروجی بگیرید و روی ۲۴۹ با `pg_restore --clean --no-owner -d zareian_mathapp` برگردد (اپ را پیش از آن stop کنید و از
|
||||
وضعیتِ فعلی هم یک `pg_dump` بگیرید). این مسیر روی سرور آزموده نشده؛ با کمک بروید.
|
||||
- **کلِ ۲۴۹ رفت:** همان بازیابی تا انتهای WAL روی ماشینِ تازه، و سفارشهای چند ثانیهی آخر را با پنلِ زرینپال/بازار
|
||||
تطبیق دهید.
|
||||
|
||||
بکاپِ دستیِ SQLite (توسعهی محلی):
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
| ۹ | کشِ مشترک بینِ workerها نیست (Redisِ اختصاصی)؛ سقفهای مبتنی بر کش ≈ ۳ برابرند | متوسط | تصمیم + ساختِ Redis |
|
||||
| ۱۱ | کدِ OTP بیهش در دیتابیس و در فهرستِ پنل | کم | کد |
|
||||
| ۱۲ | هزینهی دستیارِ AI با ساختِ شمارهی تازه (هر شماره ۲ سوالِ رایگان) قابلِ بالا بردن است | کم (مالی) | کد |
|
||||
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر؛ بکاپِ پیش از migrate خودکار است ولی بیرون از ماشین نیست | کم (داده) | زیرساخت |
|
||||
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||||
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||||
| ۲۲ | سایتِ فرانت هیچ هدرِ امنیتی ندارد (CSP، frame-ancestors، HSTS) | کم | nginx/فرانت |
|
||||
@@ -116,19 +115,6 @@
|
||||
با سقفِ بسیار کمتر برای مهمان؛ هشدار وقتی `aiProvider.state` در `/api/health` برابرِ
|
||||
`quota_exhausted` شد.
|
||||
|
||||
## ۱۴. دیتابیس و بکاپ
|
||||
|
||||
- **چیست:** SQLite در `~/mathapp-v2/db.sqlite3`، روی دیسکی که بینِ چند سرویس مشترک است و
|
||||
~۸۳٪ پر است. `transaction_mode: IMMEDIATE` همزمانی را درست ولی سریالی میکند. `deploy.sh`
|
||||
پیش از `migrate` بکاپ میگیرد (`.deploy-backups/db-before-deploy-*`، ۲ تای آخر؛ اگر بکاپ
|
||||
نشد، migrate اجرا نمیشود)، ولی همه در همان پوشه و روی همان دیسکاند. روی لپتاپ و در دیپلوی ۲۰۲۶-۱۰-۰۷ روی سرور آزموده شد
|
||||
(ساخت و `integrity_check`؛ حذفِ قدیمیها فقط روی لپتاپ). دیتابیسِ لپتاپ ۱۲۶ مگابایت
|
||||
است و دو بکاپ ~۲۵۰ مگابایت میگیرند؛ پیش از اولین دیپلو `df -h /` را ببینید.
|
||||
- **خطر:** از دست رفتنِ کلِ داده با یک خطای دیسک یا یک دستورِ اشتباه؛ پُر شدنِ دیسک
|
||||
سرویسهای دیگر (Gitea، ایمیل) را هم میخواباند.
|
||||
- **رفع:** مهاجرت به Postgres (روی همان ماشین نصب است؛ بعدش بکاپِ `deploy.sh` باید `pg_dump` شود،
|
||||
الان فقط هشدار میدهد) و نگهداریِ یک نسخه بیرون از ماشین.
|
||||
|
||||
## ۱۵. دسترسیِ سرور
|
||||
|
||||
- **چیست:** ورود با رمز به حسابِ مشترکِ `administrator` با `sudo (ALL : ALL) ALL` و عضوِ
|
||||
@@ -235,6 +221,7 @@
|
||||
| قلبِ صفر جوابِ سوال را میبندد؛ چکش داخلِ تراکنش است (بیجوابماندن و جزیرهی قفل جداگانه در ردیفِ بعدی بسته شدهاند) | `content/tests_hearts_gate.py` |
|
||||
| `select_for_update` مؤثر با `transaction_mode: IMMEDIATE`؛ مقایسهی زمانثابتِ `X-Internal-Key` | `payment/tests_audit_fixes.py` |
|
||||
| `deploy.sh` پیش از `migrate` بکاپِ `db-before-deploy-*` میگیرد، `integrity_check` میکند و اگر نشد migrate را اجرا نمیکند | مرحلهی «بکاپِ دیتابیس»: ۷ بار روی لپتاپ و یک بار روی سرور (۶ مگابایت، `integrity_check: ok`) |
|
||||
| دیتابیس Postgresِ اختصاصی است و هر تغییرش لحظهای روی سرورِ دیگری (۲۴۷) کپی میشود، با نسخهی کاملِ روزانه و بازیابی تا هر لحظه از ۷ روزِ اخیر؛ قطعیِ ۲۴۷ اپ را نمیخواباند و بیش از ۱ گیگ روی دیسکِ ۲۴۹ جمع نمیشود (`docs/operations.md` «بکاپ و بازیابی») | `config/tests_offsite_backup.py` برای هشدارِ `/api/health`؛ روی سرور: بازیابی روی ۲۴۷ با شمارشهای برابرِ ۲۴۹ و یک ردیفِ آزمایشی که ۲ ثانیه بعد آنجا بود |
|
||||
| دیتابیس و بکاپها فقط برای صاحبِ سرویس خواندنیاند (مرحلهی «مجوزِ فایلهای داده» در `deploy.sh`) | با `sudo -u nobody head -c 15 ~/mathapp-v2/db.sqlite3` روی سرور باید خطای دسترسی بدهد |
|
||||
| پانزده مسیرِ خواندنی و نوشتنیِ کمخطر سقفِ نرخ دارند (بانکِ سوال و intro بیورود، `progress/nodes`، `user/hearts`، اعلان، تیکت، پلنها، وضعیتِ اشتراک، سفارش، پیشنهادها، affiliate، تاریخچه و فیدبکِ دستیار، خروج). با نرخِ ۳ در دقیقه درخواستِ چهارم ۴۲۹ میگیرد | `content/tests_read_throttles.py` |
|
||||
| ساختنِ دستیِ `OTPCode` و `Session` در پنل فقط برای ابرکاربر (کارمندِ عادی ۴۰۳) | `accounts/tests_security_review.py` (`StaffCannotMintLoginsTests`) |
|
||||
|
||||
Reference in New Issue
Block a user