216 backups encrypted on 216: pgBackRest (AES-256) + age-encrypted payment CSVs, pushed write-only to 247 with rrsync; health watches pg_stat_archiver and OFFSITE_SYNC_STATUS_FILE; 216 scripts/configs in ops/server216; plaintext pg216 pipeline removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,9 @@ DB_PORT=5432
|
||||
# اسلاتِ تکثیری که سرورِ بکاپ از آن کپیِ زنده میگیرد (ops/pgbackup/). روی سرور: zareian_247.
|
||||
# خالی روی Postgres = کپیِ بیرونی نداریم و /api/health هشدار میدهد. روی لپتاپ خالی.
|
||||
OFFSITE_WAL_SLOT=
|
||||
# سرورِ اختصاصی (۲۱۶): فایلی که tadrisino-offsite-sync زمانِ آخرین ارسالِ بکاپِ رمزشده به ۲۴۷ را در آن مینویسد.
|
||||
# روی ۲۱۶: /var/lib/tadrisino-backup/offsite-synced-at (بر OFFSITE_WAL_SLOT مقدم است). روی لپتاپ خالی.
|
||||
OFFSITE_SYNC_STATUS_FILE=
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# ۲) دامنهها — دو آدرس، بقیه از آنها ساخته میشوند
|
||||
|
||||
@@ -63,9 +63,9 @@ other people's home directories (`aria`, `artina`, `behdad`, `zahra`, `git`, `gi
|
||||
other nginx site. `metabase` and Artina's `celerybeat` crash-loop and eat ~3 of 8 cores; that is a
|
||||
request to the server admin, not something to fix.
|
||||
|
||||
**`78.38.35.247` (backup server)**, same account and password: ours is only `~/zareian-pgbackup/`,
|
||||
`~/zareian-pgbackup-216/`, `~/.ssh/zareian_pgbackup*`, and the `zareian-pg-*` / `zareian-pg216-*` /
|
||||
`zareian-applog-pull` units. It also runs other teams' services and its
|
||||
**`78.38.35.247` (backup server)**, same account and password: ours is only `~/zareian-pgbackup/` (249's
|
||||
live copy), `~/zareian-pgbackrest-216/` (216's backups, encrypted on 216; 247 never holds the key), our lines in
|
||||
`~/.ssh/authorized_keys` and `~/.pgpass`, `~/.ssh/zareian_*`, and the `zareian-pg-*` / `zareian-applog-pull` units. It also runs other teams' services and its
|
||||
own `postgresql@14-main`, which we never use.
|
||||
|
||||
**`78.38.35.216` (`tadrisino`, new dedicated VM, not yet serving users)**, account `zareian` (sudo): the whole
|
||||
|
||||
@@ -14,6 +14,7 @@ GET /api/health — «آیا این سرور واقعاً سالم است؟»
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
@@ -160,6 +161,8 @@ def _offsite_backup() -> dict:
|
||||
"""
|
||||
if connection.vendor != 'postgresql':
|
||||
return {'state': 'not_postgres'}
|
||||
if settings.OFFSITE_SYNC_STATUS_FILE:
|
||||
return _pushed_backup()
|
||||
slot = settings.OFFSITE_WAL_SLOT
|
||||
if not slot:
|
||||
return {'state': 'not_configured'}
|
||||
@@ -193,6 +196,45 @@ def _slot_state(row) -> dict:
|
||||
return {'state': state, 'lagBytes': lag}
|
||||
|
||||
|
||||
def _pushed_backup() -> dict:
|
||||
"""
|
||||
بکاپِ رمزشدهی ۲۱۶: pgBackRest هر WAL را با archive_command آرشیو میکند (pg_stat_archiver) و
|
||||
`tadrisino-offsite-sync` مخزن را هر دقیقه به ۲۴۷ میفرستد و زمانش را در OFFSITE_SYNC_STATUS_FILE مینویسد.
|
||||
کهنگیِ آخرین آرشیو هشدار نیست: دیتابیسِ بیتغییر WALِ تازهای ندارد که آرشیو شود.
|
||||
"""
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SELECT last_archived_time, last_failed_time FROM pg_stat_archiver')
|
||||
last_archived, last_failed = cursor.fetchone()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return {'state': 'error', 'error': type(exc).__name__}
|
||||
try:
|
||||
with open(settings.OFFSITE_SYNC_STATUS_FILE) as f:
|
||||
synced_at = int(f.read().strip())
|
||||
except FileNotFoundError:
|
||||
synced_at = None
|
||||
except (OSError, ValueError) as exc:
|
||||
return {'state': 'error', 'error': type(exc).__name__}
|
||||
return _pushed_state(last_archived, last_failed, synced_at, time.time())
|
||||
|
||||
|
||||
def _pushed_state(last_archived, last_failed, synced_at, now) -> dict:
|
||||
age = None if synced_at is None else int(now - synced_at)
|
||||
if last_failed is not None and (last_archived is None or last_failed > last_archived):
|
||||
state = 'archive_failing'
|
||||
elif age is None:
|
||||
state = 'never_synced'
|
||||
elif age > settings.OFFSITE_SYNC_MAX_AGE_SECONDS:
|
||||
state = 'sync_stale'
|
||||
else:
|
||||
state = 'ok'
|
||||
return {
|
||||
'state': state,
|
||||
'syncAgeSeconds': age,
|
||||
'lastArchivedAt': last_archived.isoformat() if last_archived else None,
|
||||
}
|
||||
|
||||
|
||||
# اتصالهایی بیرون از poolِ workerها: pg_receivewal و pg_basebackupِ سرورِ بکاپ (۲)، pg_dump و
|
||||
# migrateِ deploy.sh (۲)، فرمانِ مدیریتی یا psqlِ دستی (۲)، و workerی که gunicorn با max_requests
|
||||
# عوض میکند و تا بسته شدنِ قبلی همزمان بالاست (یک pool).
|
||||
@@ -267,6 +309,9 @@ _OFFSITE_MESSAGES = {
|
||||
'lagging': 'سرورِ بکاپ از دیتابیس عقب است.',
|
||||
'lost': 'سرورِ بکاپ آنقدر قطع بود که زنجیرهی WAL برید؛ روی آن سرور اسلات و base backupِ تازه لازم است.',
|
||||
'error': 'وضعیتِ کپیِ زندهی دیتابیس خوانده نشد.',
|
||||
'archive_failing': 'آرشیوِ WAL (pgBackRest) شکست میخورد و تغییراتِ تازه بکاپ نمیشوند: sudo -u postgres pgbackrest --stanza=zareian check',
|
||||
'never_synced': 'بکاپِ رمزشده هنوز هیچوقت به سرورِ بیرونی (۲۴۷) نرسیده (فایلِ OFFSITE_SYNC_STATUS_FILE نیست).',
|
||||
'sync_stale': 'ارسالِ بکاپِ رمزشده به ۲۴۷ عقب افتاده: journalctl -u tadrisino-offsite-sync',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1078,6 +1078,12 @@ LOG_DIR = os.environ.get('LOG_DIR', '')
|
||||
OFFSITE_WAL_SLOT = os.environ.get('OFFSITE_WAL_SLOT', '')
|
||||
# بیش از این عقبماندن هشدار است. ۶۴ مگ ≈ چهار فایلِ WAL؛ در ترافیکِ عادی عقبماندگی نزدیکِ صفر است.
|
||||
OFFSITE_WAL_MAX_LAG_BYTES = 64 * 2**20
|
||||
# سرورِ اختصاصی (۲۱۶): بکاپِ رمزشدهی pgBackRest؛ `tadrisino-offsite-sync` هر دقیقه آن را به ۲۴۷ میفرستد و زمانِ
|
||||
# آخرین ارسالِ موفق (epoch) را در این فایل مینویسد (docs/operations.md «سرورِ اختصاصیِ جدید»). ست باشد، بر
|
||||
# OFFSITE_WAL_SLOT مقدم است. OFFSITE_WAL_SLOT را فقط ۲۴۹ دارد و با سوئیچ به ۲۱۶ همراهِ _slot_state حذف میشود.
|
||||
OFFSITE_SYNC_STATUS_FILE = os.environ.get('OFFSITE_SYNC_STATUS_FILE', '')
|
||||
# ارسال هر دقیقه است؛ ۱۰ دقیقه بیارسال یعنی چند اجرای پشتِ سرِ هم شکست خورده.
|
||||
OFFSITE_SYNC_MAX_AGE_SECONDS = 600
|
||||
|
||||
# چرخشِ فایلِ لاگ: 'size' (پیشفرض، Docker و محلی) = ۶ فایلِ ۱۰ مگی و قدیمیها پاک میشوند؛
|
||||
# 'external' (سرور) = برنامه هرگز پاک نمیکند، `ops/applogs/archive-app-log.sh` هر شب app.log را جابهجا و
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
پرسشِ واقعی از pg_replication_slots اینجا اجرا نمیشود (تستها روی SQLiteاند)؛ روی سرور با اسلاتِ
|
||||
واقعی دیده شد (docs/operations.md «بکاپ»).
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
|
||||
from config import health
|
||||
@@ -27,13 +29,45 @@ class SlotStateTests(SimpleTestCase):
|
||||
self.assertEqual(health._slot_state((True, 'reserved', 100))['state'], 'ok')
|
||||
|
||||
|
||||
@override_settings(OFFSITE_SYNC_MAX_AGE_SECONDS=600)
|
||||
class PushedStateTests(SimpleTestCase):
|
||||
"""بکاپِ رمزشدهی ۲۱۶ (_pushed_state). پرسشِ pg_stat_archiver و خواندنِ فایل روی سرور دیده شد، نه اینجا."""
|
||||
|
||||
T0 = datetime(2026, 10, 17, 16, 0, tzinfo=timezone.utc)
|
||||
|
||||
def test_recent_sync_is_ok(self):
|
||||
r = health._pushed_state(self.T0, None, 1000, now=1060)
|
||||
self.assertEqual((r['state'], r['syncAgeSeconds']), ('ok', 60))
|
||||
self.assertEqual(r['lastArchivedAt'], self.T0.isoformat())
|
||||
|
||||
def test_old_sync_is_stale(self):
|
||||
self.assertEqual(health._pushed_state(self.T0, None, 1000, now=1601)['state'], 'sync_stale')
|
||||
self.assertEqual(health._pushed_state(self.T0, None, 1000, now=1600)['state'], 'ok')
|
||||
|
||||
def test_no_status_file_is_never_synced(self):
|
||||
self.assertEqual(health._pushed_state(self.T0, None, None, now=1000)['state'], 'never_synced')
|
||||
|
||||
def test_failure_after_last_success_wins(self):
|
||||
later = self.T0 + timedelta(minutes=1)
|
||||
self.assertEqual(health._pushed_state(self.T0, later, 1000, now=1001)['state'], 'archive_failing')
|
||||
self.assertEqual(health._pushed_state(None, later, 1000, now=1001)['state'], 'archive_failing')
|
||||
|
||||
def test_old_failure_then_success_is_ok(self):
|
||||
earlier = self.T0 - timedelta(minutes=1)
|
||||
self.assertEqual(health._pushed_state(self.T0, earlier, 1000, now=1001)['state'], 'ok')
|
||||
|
||||
def test_idle_database_with_no_archive_yet_is_ok(self):
|
||||
self.assertEqual(health._pushed_state(None, None, 1000, now=1001)['state'], 'ok')
|
||||
|
||||
|
||||
class OffsiteWarningTests(SimpleTestCase):
|
||||
def test_ok_and_dev_are_silent(self):
|
||||
self.assertEqual(health._offsite_warnings({'state': 'ok', 'lagBytes': 0}), [])
|
||||
self.assertEqual(health._offsite_warnings({'state': 'not_postgres'}), [])
|
||||
|
||||
def test_every_bad_state_warns(self):
|
||||
for state in ('not_configured', 'slot_missing', 'disconnected', 'lagging', 'lost', 'error'):
|
||||
for state in ('not_configured', 'slot_missing', 'disconnected', 'lagging', 'lost', 'error',
|
||||
'archive_failing', 'never_synced', 'sync_stale'):
|
||||
self.assertEqual(len(health._offsite_warnings({'state': state})), 1, state)
|
||||
|
||||
def test_sqlite_reports_not_postgres(self):
|
||||
|
||||
@@ -389,23 +389,32 @@ the servers»):
|
||||
| | |
|
||||
|---|---|
|
||||
| ماشین | VMware، Ubuntu 24.04، ۸ هسته، ۱۲ گیگ رم، دیسکِ ۹۷ گیگ؛ hostname `tadrisino`، IPِ داخلی `192.168.200.163` (۲۴۹ = `.164`)، IPِ خروجی `78.38.35.228` |
|
||||
| ورود | `ssh zareian@78.38.35.216` (sudo). `temp` حسابِ موقتِ ادمینِ شبکه است و حذف میشود. ورودِ root بسته، `AllowUsers zareian temp pgbackup@192.168.200.167`، forwarding بسته (`/etc/ssh/sshd_config.d/10-tadrisino.conf`) |
|
||||
| ورود | `ssh zareian@78.38.35.216` (sudo). `temp` حسابِ موقتِ ادمینِ شبکه است و حذف میشود. ورودِ root بسته، `AllowUsers zareian temp`، forwarding بسته (`10-tadrisino.conf`) |
|
||||
| فایلها | نسخهی اصلیِ همهی اسکریپتها و تنظیمهای این سرور در `ops/server216/` (بیکلید). تغییر: فایلِ ریپو، بعد کپی روی سرور |
|
||||
| ضدِ ربات | ۱) `tadrisino-blocklist.timer`: هر روز FireHOL level1 (Spamhaus، DShield، …) در ipsetِ `tadrisino-block`، همهی پورتها؛ شبکهی داخلی، `78.38.35.0/24` و رنجهای آروان هرگز در آن نیستند. ۲) `ufw limit 22`: بیش از ۶ اتصال در ۳۰ ثانیه رد. ۳) fail2ban پلهای: ۶ اشتباه در ۱۰ دقیقه → ۱۰ دقیقه، هر تکرار دو برابر تا یک هفته؛ `recidive`: ۵ بار در روز → یک هفته همهی پورتها؛ `192.168.200.0/24` هرگز. آزاد کردن: `sudo fail2ban-client set sshd unbanip <IP>` |
|
||||
| بکاپ | زنده روی ۲۴۷ (`~/zareian-pgbackup-216/`، یونیتهای `zareian-pg216-*`، ابزارِ Postgres 16 بازشده در همان پوشه). ۲۴۷ با کاربرِ `pgbackup` (فقط کلید، فقط از `.167`، فقط `permitopen=127.0.0.1:5440`) تونل میزند؛ رمزِ `zareian_replicator` فقط در `~/.pgpass`ِ ۲۴۷، روی ۲۱۶ فقط هشِ SCRAM. اسلات `zareian_247`، `max_slot_wal_keep_size=5GB`. آزمونِ بازیابی: `PGB_ROOT=… PGB_BIN=… LD_LIBRARY_PATH=… restore-check.sh` (مقادیر در یونیتِ `zareian-pg216-basebackup`) |
|
||||
| بکاپ | **رمزنگاری روی همین سرور، ۲۴۷ فقط فایلِ رمزشده میبیند** (۲۴۷ مشترک است و هر کس `administrator` را دارد root است). pgBackRest با `repo1-cipher-type=aes-256-cbc`: آرشیوِ پیوستهی WAL (`archive_timeout=60`، یعنی حداکثر یک دقیقه تغییرِ آرشیونشده)، `tadrisino-pgbackrest-full` یکشنبهها، `-diff` هر روز، دو نسخهی کامل نگه داشته میشود. CSVهای پرداخت هر ۶ ساعت با age رمز میشوند (`tadrisino-export-payment-events`). `tadrisino-offsite-sync` هر دقیقه `/var/lib/pgbackrest` و `payment-log` را به `~/zareian-pgbackrest-216/` روی ۲۴۷ میفرستد؛ کلیدِ postgres در `authorized_keys`ِ ۲۴۷ با `from=192.168.200.163,restrict,command="rrsync -wo …"` فقط میتواند در همان پوشه بنویسد (نه بخواند، نه شِل). `/api/health` → `offsiteBackup` از `pg_stat_archiver` و `OFFSITE_SYNC_STATUS_FILE` |
|
||||
| کلیدهای بازیابی | `/etc/tadrisino-backup/RECOVERY-KEYS` (root، 600): کلیدِ pgBackRest و کلیدِ age. **بیآنها بکاپِ ۲۴۷ خواندنی نیست**؛ یک نسخه بیرون از سرورها نزدِ صاحبِ محصول لازم است: `ssh -t zareian@78.38.35.216 sudo cat /etc/tadrisino-backup/RECOVERY-KEYS` در ترمینالِ خودش (نه در چت) |
|
||||
| آزمونِ بازیابی | `sudo -u postgres tadrisino-restore-check` (ماهی یک بار): بازیابی در پوشهی موقت و مقایسهی شمارشها با دیتابیسِ زنده. بازیابیِ واقعی از ۲۴۷ (اگر ۲۱۶ از دست رفت): پوشهی `repo/` را از ۲۴۷ کپی، `pgbackrest.conf` با کلید از RECOVERY-KEYS، `pgbackrest --stanza=zareian restore` |
|
||||
| اپ | کاربرِ بیsudoِ `mathapp`؛ `/home/mathapp/mathapp-v2` و `/home/mathapp/mathapp_frontend_new` |
|
||||
| سرویسها | `zareian-mathapp` (= `gunicorn-mathapp`، با `RuntimeDirectory`)، `zareian-redis` (6390)، `postgresql@16-zareian` (5440)، `nginx`، `zareian-applog-archive.timer` |
|
||||
| رمزها | رمزِ Postgres و Redis روی این سرور تازه ساخته شده (`.pg_password`، `.redis_password` کنارِ `.env`)، نه کپیِ ۲۴۹ |
|
||||
| فایروال | ufw فقط 22/80/443. فایروالِ شبکهی شرکت 443ِ ورودی را هنوز میبندد (80 باز است) |
|
||||
| بستهها | apt از `mirror.arvancloud.ir`، pip از `mirror-pypi.runflare.com` (`~mathapp/.config/pip/pip.conf`)، npm از `mirror-npm.runflare.com`؛ `files.pythonhosted.org` و `registry.npmjs.org` از این سرور بستهاند |
|
||||
|
||||
سنجیده: `/api/health` با همهی جزئیات ok و بیهشدار (`offsiteBackup` ok، عقبماندگی ۰)، آزمونِ بازیابی از بکاپِ ۲۴۷ با همان شمارشها، Qdrant از این سرور وصل، فرانت ۲۰۰، ریبوت و
|
||||
سنجیده: `/api/health` با همهی جزئیات ok، آزمونِ بازیابی از بکاپِ رمزشده با همان شمارشها، فایلهای ۲۴۷ رمزشده (`Salted__`،
|
||||
`age-encryption.org/v1`)، کلیدِ ۲۱۶ روی ۲۴۷ نه میخواند نه شِل میگیرد، کپیِ ۲۴۷ با `rsync --checksum` عینِ مخزنِ محلی، Qdrant از این سرور وصل، فرانت ۲۰۰، ریبوت و
|
||||
بالا آمدنِ خودکارِ همهی سرویسها، درخواستِ مستقیم به IP ۴۰۳ (nginx IPِ واقعی را میبیند). نسنجیده: هیچ درخواستی از مسیرِ ArvanCloud،
|
||||
پیامک، پرداخت و تیوتورِ زنده.
|
||||
|
||||
**سوئیچ (ترتیب):** ۱) 443ِ ورودی باز شود. ۲) روی ۲۴۹ `zareian-mathapp` متوقف (پیامِ نگهداری در ArvanCloud)، `pg_dump -Fc`،
|
||||
بازیابی روی سرورِ جدید در دیتابیسِ خالیشده، مقایسهی تعدادِ ردیفها. ۳) originِ هر دو دامنه در ArvanCloud به `78.38.35.216`.
|
||||
۴) health و یک ورود و یک آزمون از بیرون. ۵) روی ۲۴۷ یونیتهای بکاپِ ۲۴۹ (`zareian-pg-*`) متوقف، `zareian-pg216-basebackup`
|
||||
یک بار دستی (بعد از بازیابیِ دیتابیسِ نهایی)، و `pull-app-logs.sh` به آدرسِ جدید. ۶) CLAUDE.md و این سند.
|
||||
۴) health و یک ورود و یک آزمون از بیرون. ۵) `tadrisino-pgbackrest-full` یک بار دستی (بعد از بازیابیِ دیتابیسِ نهایی)؛ روی ۲۴۷
|
||||
یونیتهای بکاپِ ۲۴۹ (`zareian-pg-*`) متوقف و، با تأییدِ صاحبِ محصول، بکاپهای رمزنگارینشدهی ۲۴۹ (`~/zareian-pgbackup`) پاک؛
|
||||
`pull-app-logs.sh` به آدرسِ جدید؛ در کدِ health، `_slot_state` و `OFFSITE_WAL_SLOT` حذف. ۶) CLAUDE.md و این سند.
|
||||
|
||||
**باز، بعد از سوئیچ:** ورود با کلیدِ SSH برای `zareian` (صاحبِ محصول روی لپتاپ `ssh-keygen -t ed25519` و `ssh-copy-id`)، بعد
|
||||
`PasswordAuthentication no` با `Match Address 192.168.200.0/24` برای ورودِ با رمز از شبکهی داخلی؛ private کردنِ مخزن در Gitea
|
||||
(الان بیرمز از اینترنت خواندنی است) و توکنِ فقطخواندنی برای `deploy.sh`.
|
||||
تا سوئیچ، `deploy.sh` روی هر دو سرور اجرا شود تا کدِ دو طرف یکی بماند.
|
||||
|
||||
### Qdrant
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
# --checkpoint=spread و --max-rate بار روی ۲۴۹ را کم نگه میدارند (آنجا سرورِ اصلی است).
|
||||
# نسخه تا pg_verifybackup قبولش نکند جای قبلیها را نمیگیرد و چیزی پاک نمیشود.
|
||||
set -euo pipefail
|
||||
# ROOT/BIN/PORT از یونیت میآیند؛ پیشفرضها همان بکاپِ ۲۴۹ است (یونیتهای zareian-pg216-* برای سرورِ جدید).
|
||||
ROOT="${PGB_ROOT:-/home/administrator/zareian-pgbackup}"
|
||||
BIN="${PGB_BIN:-/usr/lib/postgresql/14/bin}"
|
||||
PORT="${PGB_PORT:-15440}"
|
||||
ROOT=/home/administrator/zareian-pgbackup
|
||||
BIN=/usr/lib/postgresql/14/bin
|
||||
KEEP=7
|
||||
|
||||
cd "$ROOT"
|
||||
@@ -16,7 +14,7 @@ ts=$(date +%Y%m%d-%H%M%S)
|
||||
rm -rf base/.inprogress-*
|
||||
tmp="base/.inprogress-$ts"
|
||||
|
||||
"$BIN/pg_basebackup" -h 127.0.0.1 -p "$PORT" -U zareian_replicator --no-password \
|
||||
"$BIN/pg_basebackup" -h 127.0.0.1 -p 15440 -U zareian_replicator --no-password \
|
||||
-D "$tmp" -Fp -X stream --checkpoint=spread --max-rate=20M
|
||||
"$BIN/pg_verifybackup" "$tmp"
|
||||
mv "$tmp" "base/$ts"
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
# خروجی: payment-log/payment-events-<UTC>.csv، orders-…csv، transactions-…csv؛ ۱۴ فایلِ آخرِ هر نوع میماند.
|
||||
# توجه: نسخهی کامل تا چند دقیقهی WAL عقبتر از ۲۴۹ میشود، ولی WALِ رسیده همه اعمال میشود.
|
||||
set -euo pipefail
|
||||
# ROOT/BIN از یونیت میآیند؛ پیشفرضها همان بکاپِ ۲۴۹ است (یونیتهای zareian-pg216-* برای سرورِ جدید).
|
||||
ROOT="${PGB_ROOT:-/home/administrator/zareian-pgbackup}"
|
||||
BIN="${PGB_BIN:-/usr/lib/postgresql/14/bin}"
|
||||
ROOT=/home/administrator/zareian-pgbackup
|
||||
BIN=/usr/lib/postgresql/14/bin
|
||||
OUT="$ROOT/payment-log"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
# restore-check.sh '2026-10-10 16:30:00+03:30'
|
||||
# با KEEP=1 پوشه و Postgresِ موقت میمانند (برای بازیابیِ واقعی: از آن pg_dump بگیرید، بعد pg_ctl stop و rm).
|
||||
set -euo pipefail
|
||||
# ROOT/BIN از یونیت میآیند؛ پیشفرضها همان بکاپِ ۲۴۹ است (یونیتهای zareian-pg216-* برای سرورِ جدید).
|
||||
ROOT="${PGB_ROOT:-/home/administrator/zareian-pgbackup}"
|
||||
BIN="${PGB_BIN:-/usr/lib/postgresql/14/bin}"
|
||||
ROOT=/home/administrator/zareian-pgbackup
|
||||
BIN=/usr/lib/postgresql/14/bin
|
||||
TARGET="${1:-}"
|
||||
|
||||
latest=$(ls -1d "$ROOT"/base/2* | tail -1)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# روی سرورِ بکاپ (۲۴۷). نسخهی کاملِ روزانه از ۲۱۶؛ همان basebackup.sh با مسیر و پورتِ ۲۱۶.
|
||||
[Unit]
|
||||
Description=Zareian MathApp: daily base backup from 216
|
||||
Requires=zareian-pg216-tunnel.service
|
||||
After=zareian-pg216-tunnel.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=administrator
|
||||
Environment=PGB_ROOT=/home/administrator/zareian-pgbackup-216 PGB_BIN=/home/administrator/zareian-pgbackup-216/pg16/root/usr/lib/postgresql/16/bin PGB_PORT=15441 LD_LIBRARY_PATH=/home/administrator/zareian-pgbackup-216/pg16/root/usr/lib/x86_64-linux-gnu
|
||||
ExecStart=/home/administrator/zareian-pgbackup-216/bin/basebackup.sh
|
||||
@@ -1,9 +0,0 @@
|
||||
[Unit]
|
||||
Description=Zareian MathApp: daily base backup from 216
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 04:50:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -1,8 +0,0 @@
|
||||
[Unit]
|
||||
Description=Zareian MathApp: export payment events from the 216 backup copy to CSV
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=administrator
|
||||
Environment=PGB_ROOT=/home/administrator/zareian-pgbackup-216 PGB_BIN=/home/administrator/zareian-pgbackup-216/pg16/root/usr/lib/postgresql/16/bin LD_LIBRARY_PATH=/home/administrator/zareian-pgbackup-216/pg16/root/usr/lib/x86_64-linux-gnu
|
||||
ExecStart=/home/administrator/zareian-pgbackup-216/bin/export-payment-events.sh
|
||||
@@ -1,9 +0,0 @@
|
||||
[Unit]
|
||||
Description=Zareian MathApp: payment events CSV from 216 every 6 hours
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 01,07,13,19:35:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -1,20 +0,0 @@
|
||||
# روی سرورِ بکاپ (۲۴۷). تونلِ SSH از 127.0.0.1:15441ِ اینجا به Postgresِ سرورِ اختصاصی (۲۱۶، 127.0.0.1:5440).
|
||||
# طرفِ ۲۱۶ کاربرِ سیستمیِ pgbackup است: فقط همین کلید، فقط از 192.168.200.167، بیشِل، و در authorized_keys
|
||||
# permitopen="127.0.0.1:5440" (هیچ پورتِ دیگری). رمزِ zareian_replicator فقط در ~/.pgpassِ ۲۴۷ است؛ ۲۱۶ فقط هشِ SCRAM آن را دارد.
|
||||
# (forwardingِ سوکت ممکن نیست: AllowTcpForwarding no در sshd آن را هم میبندد.)
|
||||
[Unit]
|
||||
Description=Zareian MathApp: SSH tunnel to the app Postgres on 216
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=administrator
|
||||
ExecStart=/usr/bin/ssh -N -i /home/administrator/.ssh/zareian_pgbackup_216 \
|
||||
-o BatchMode=yes -o ExitOnForwardFailure=yes \
|
||||
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
|
||||
-L 127.0.0.1:15441:127.0.0.1:5440 pgbackup@192.168.200.163
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,19 +0,0 @@
|
||||
# روی سرورِ بکاپ (۲۴۷). هر تغییرِ دیتابیسِ ۲۱۶ را لحظهای در ~/zareian-pgbackup-216/wal مینویسد.
|
||||
# ابزارهای Postgres 16 از مخزنِ رسمی در ~/zareian-pgbackup-216/pg16 باز شدهاند (نصبِ سیستمی نیست؛ ۲۴۷ فقط 14 دارد).
|
||||
# اسلاتِ zareian_247 روی ۲۱۶، سقف max_slot_wal_keep_size=5GB. اپ منتظرِ اینجا نمیماند.
|
||||
[Unit]
|
||||
Description=Zareian MathApp: live WAL stream from 216
|
||||
After=zareian-pg216-tunnel.service
|
||||
BindsTo=zareian-pg216-tunnel.service
|
||||
|
||||
[Service]
|
||||
User=administrator
|
||||
Environment=LD_LIBRARY_PATH=/home/administrator/zareian-pgbackup-216/pg16/root/usr/lib/x86_64-linux-gnu
|
||||
ExecStart=/home/administrator/zareian-pgbackup-216/pg16/root/usr/lib/postgresql/16/bin/pg_receivewal -h 127.0.0.1 -p 15441 -U zareian_replicator \
|
||||
--no-password --slot=zareian_247 --synchronous --no-loop \
|
||||
-D /home/administrator/zareian-pgbackup-216/wal
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
ops/server216/10-tadrisino.conf
Normal file
13
ops/server216/10-tadrisino.conf
Normal file
@@ -0,0 +1,13 @@
|
||||
# تدریسینو: سختسازیِ SSH. فقط کاربرهای نامبرده وارد میشوند؛ temp تا حذفش به دستِ ادمینِ شبکه.
|
||||
# ورود با رمز هنوز باز است (تا رفتن به کلید SSH)؛ fail2ban جلوی حدس زدن را میگیرد.
|
||||
PermitRootLogin no
|
||||
AllowUsers zareian temp
|
||||
MaxAuthTries 4
|
||||
MaxSessions 10
|
||||
LoginGraceTime 30
|
||||
X11Forwarding no
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
PermitTunnel no
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
18
ops/server216/80-tadrisino-hardening.conf
Normal file
18
ops/server216/80-tadrisino-hardening.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
# تدریسینو: تنظیماتِ شبکهی استاندارد (CIS). سرور مسیریاب نیست.
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv6.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
fs.protected_hardlinks = 1
|
||||
fs.protected_symlinks = 1
|
||||
4
ops/server216/backup.conf
Normal file
4
ops/server216/backup.conf
Normal file
@@ -0,0 +1,4 @@
|
||||
# تدریسینو: آرشیوِ پیوستهی WAL با pgBackRest (رمزنگاریشده). archive_timeout یعنی حداکثر ۶۰ ثانیه تغییرِ آرشیونشده.
|
||||
archive_mode = on
|
||||
archive_command = 'pgbackrest --stanza=zareian archive-push %p'
|
||||
archive_timeout = 60
|
||||
20
ops/server216/gunicorn-mathapp.service
Normal file
20
ops/server216/gunicorn-mathapp.service
Normal file
@@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=Gunicorn MathApp (tadrisino API)
|
||||
After=network.target postgresql@16-zareian.service zareian-redis.service
|
||||
Wants=postgresql@16-zareian.service zareian-redis.service
|
||||
|
||||
[Service]
|
||||
User=mathapp
|
||||
Group=www-data
|
||||
WorkingDirectory=/home/mathapp/mathapp-v2
|
||||
EnvironmentFile=/home/mathapp/mathapp-v2/.env
|
||||
# /run/gunicorn بعد از هر ریبوت ساخته میشود (روی ۲۴۹ دستی ساخته شده بود و بعد از ریبوت نبود).
|
||||
RuntimeDirectory=gunicorn
|
||||
RuntimeDirectoryMode=0750
|
||||
ExecStart=/home/mathapp/mathapp-v2/venv/bin/gunicorn --bind unix:/run/gunicorn/mathapp.sock config.wsgi:application
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=zareian-mathapp.service
|
||||
17
ops/server216/pgbackrest.conf
Normal file
17
ops/server216/pgbackrest.conf
Normal file
@@ -0,0 +1,17 @@
|
||||
# روی ۲۱۶: /etc/pgbackrest.conf (root:postgres 640). کلیدِ واقعی فقط روی سرور و در /etc/tadrisino-backup/RECOVERY-KEYS است.
|
||||
# تدریسینو: بکاپِ رمزنگاریشده. مخزنِ محلی /var/lib/pgbackrest؛ نسخهی بیرونی با tadrisino-offsite-sync به ۲۴۷ (فقط فایلِ رمزشده).
|
||||
[global]
|
||||
repo1-path=/var/lib/pgbackrest
|
||||
repo1-cipher-type=aes-256-cbc
|
||||
repo1-cipher-pass=<RECOVERY-KEYS، خطِ کلیدِ ۱>
|
||||
repo1-retention-full=2
|
||||
compress-type=zst
|
||||
process-max=2
|
||||
start-fast=y
|
||||
log-level-console=info
|
||||
log-level-file=detail
|
||||
|
||||
[zareian]
|
||||
pg1-path=/var/lib/postgresql/16/zareian
|
||||
pg1-port=5440
|
||||
pg1-socket-path=/var/run/postgresql
|
||||
44
ops/server216/tadrisino-blocklist
Executable file
44
ops/server216/tadrisino-blocklist
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# تدریسینو: IPهای شناختهشدهی مخرب را پیش از اولین تلاش روی همهی پورتها میبندد.
|
||||
# فهرست: FireHOL level1 (Spamhaus DROP/EDROP، DShield، Feodo، bogons)، روزی یک بار (tadrisino-blocklist.timer).
|
||||
# شبکههای داخلی، رنجِ IPِ شرکت و رنجهای ArvanCloud (از snippetِ nginx) هرگز بسته نمیشوند.
|
||||
# اگر دانلود نشد، آخرین فهرستِ سالم (/var/lib/tadrisino-blocklist/list) دوباره بار میشود.
|
||||
set -euo pipefail
|
||||
URL=https://iplists.firehol.org/files/firehol_level1.netset
|
||||
DIR=/var/lib/tadrisino-blocklist; SET=tadrisino-block
|
||||
mkdir -p "$DIR"
|
||||
tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT
|
||||
if curl -fsS -m 90 "$URL" -o "$tmp" && [ "$(grep -c '^[0-9]' "$tmp")" -gt 1000 ]; then
|
||||
python3 - "$tmp" "$DIR/list" <<'PY'
|
||||
import ipaddress, re, sys
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
keep = ['127.0.0.0/8','10.0.0.0/8','172.16.0.0/12','192.168.0.0/16','100.64.0.0/10','169.254.0.0/16','78.38.35.0/24']
|
||||
try:
|
||||
keep += re.findall(r'^allow\s+([0-9./]+);', open('/etc/nginx/snippets/zareian-arvan-only.conf').read(), re.M)
|
||||
except OSError:
|
||||
pass
|
||||
keep = [ipaddress.ip_network(k) for k in keep if ':' not in k]
|
||||
out, dropped = [], 0
|
||||
for line in open(src):
|
||||
line = line.strip()
|
||||
if not line or line[0] == '#':
|
||||
continue
|
||||
n = ipaddress.ip_network(line, strict=False)
|
||||
if any(n.overlaps(k) for k in keep):
|
||||
dropped += 1
|
||||
continue
|
||||
out.append(str(n))
|
||||
open(dst, 'w').write('\n'.join(out) + '\n')
|
||||
print(f'{len(out)} networks, {dropped} skipped (internal/company/Arvan)')
|
||||
PY
|
||||
else
|
||||
echo "download failed; reloading last good list" >&2
|
||||
fi
|
||||
[ -s "$DIR/list" ] || { echo "no list available" >&2; exit 1; }
|
||||
ipset create "$SET" hash:net maxelem 262144 -exist
|
||||
ipset create "$SET-new" hash:net maxelem 262144 -exist
|
||||
ipset flush "$SET-new"
|
||||
sed "s/^/add $SET-new /" "$DIR/list" | ipset restore -exist
|
||||
ipset swap "$SET-new" "$SET"; ipset destroy "$SET-new"
|
||||
iptables -C INPUT -m set --match-set "$SET" src -j DROP 2>/dev/null || iptables -I INPUT 1 -m set --match-set "$SET" src -j DROP
|
||||
echo "loaded $(ipset list "$SET" -t | sed -n 's/^Number of entries: //p') entries into $SET"
|
||||
11
ops/server216/tadrisino-blocklist.service
Normal file
11
ops/server216/tadrisino-blocklist.service
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: block known-malicious IPs (FireHOL level1)
|
||||
After=network-online.target ufw.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/tadrisino-blocklist
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
10
ops/server216/tadrisino-blocklist.timer
Normal file
10
ops/server216/tadrisino-blocklist.timer
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: refresh malicious-IP blocklist daily
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 05:20:00
|
||||
RandomizedDelaySec=30m
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
19
ops/server216/tadrisino-export-payment-events
Executable file
19
ops/server216/tadrisino-export-payment-events
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# تدریسینو: دفترِ خرید (payment_paymentevent)، سفارشها و تراکنشها به CSV، هر ۶ ساعت. هر فایل پیش از نوشتهشدن روی دیسک
|
||||
# با age برای کلیدِ /etc/tadrisino-backup/age-recipient.txt رمز میشود؛ متنِ خوانا هیچجا ذخیره نمیشود.
|
||||
# بازکردن: age -d -i age-identity.txt payment-events-….csv.age (کلید در RECOVERY-KEYS). ۱۴ فایلِ آخرِ هر نوع میماند.
|
||||
set -euo pipefail
|
||||
OUT=/var/lib/tadrisino-backup/payment-log
|
||||
REC=/etc/tadrisino-backup/age-recipient.txt
|
||||
stamp=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
q() {
|
||||
psql -p 5440 -d zareian_mathapp -v ON_ERROR_STOP=1 -Atqc "\\copy ($1) to stdout csv header" | age -R "$REC" -o "$OUT/$2-$stamp.csv.age"
|
||||
}
|
||||
q "select id, created_at, order_id, user_id, provider, event, detail from payment_paymentevent order by id" payment-events
|
||||
q "select id, created_at, user_id, plan_id, plan_title, amount, status, provider, gateway_authority, subscription_id from payment_order order by id" orders
|
||||
q "select id, created_at, order_id, gateway, gateway_ref_id, status, paid_at from payment_transaction order by id" transactions
|
||||
chmod 640 "$OUT"/*-"$stamp".csv.age
|
||||
for kind in payment-events orders transactions; do
|
||||
ls -1t "$OUT/$kind"-*.csv.age | tail -n +15 | xargs -r rm -f --
|
||||
done
|
||||
echo "wrote $OUT/*-$stamp.csv.age"
|
||||
8
ops/server216/tadrisino-export-payment-events.service
Normal file
8
ops/server216/tadrisino-export-payment-events.service
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: encrypted payment CSVs every 6 hours
|
||||
After=postgresql@16-zareian.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=postgres
|
||||
ExecStart=/usr/local/sbin/tadrisino-export-payment-events
|
||||
9
ops/server216/tadrisino-export-payment-events.timer
Normal file
9
ops/server216/tadrisino-export-payment-events.timer
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: encrypted payment CSVs every 6 hours
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 01,07,13,19:15:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
11
ops/server216/tadrisino-offsite-sync
Executable file
11
ops/server216/tadrisino-offsite-sync
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# تدریسینو: نسخهی بیرونیِ بکاپ. فقط فایلهای رمزشده (مخزنِ pgBackRest با AES-256 و CSVهای age) به ۲۴۷ میروند.
|
||||
# کلیدِ postgres روی ۲۴۷ با rrsync -wo محدود است: فقط نوشتن در ~/zareian-pgbackrest-216، نه خواندن، نه شِل.
|
||||
# پس از موفقیتِ هر دو، زمانش در offsite-synced-at ثبت میشود و /api/health از روی آن عقبماندگی را میسنجد.
|
||||
set -euo pipefail
|
||||
R=administrator@192.168.200.167
|
||||
SSH="ssh -i /var/lib/postgresql/.ssh/id_offsite -o BatchMode=yes -o ConnectTimeout=20"
|
||||
rsync -a --delete -e "$SSH" /var/lib/pgbackrest/ "$R:repo/"
|
||||
rsync -a --delete -e "$SSH" /var/lib/tadrisino-backup/payment-log/ "$R:payment-log/"
|
||||
date -u +%s > /var/lib/tadrisino-backup/offsite-synced-at.tmp
|
||||
mv /var/lib/tadrisino-backup/offsite-synced-at.tmp /var/lib/tadrisino-backup/offsite-synced-at
|
||||
8
ops/server216/tadrisino-offsite-sync.service
Normal file
8
ops/server216/tadrisino-offsite-sync.service
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: copy encrypted backups to 247
|
||||
After=postgresql@16-zareian.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=postgres
|
||||
ExecStart=/usr/local/sbin/tadrisino-offsite-sync
|
||||
9
ops/server216/tadrisino-offsite-sync.timer
Normal file
9
ops/server216/tadrisino-offsite-sync.timer
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: copy encrypted backups to 247
|
||||
|
||||
[Timer]
|
||||
OnCalendar=minutely
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
8
ops/server216/tadrisino-pgbackrest-diff.service
Normal file
8
ops/server216/tadrisino-pgbackrest-diff.service
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: daily differential encrypted backup
|
||||
After=postgresql@16-zareian.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=postgres
|
||||
ExecStart=/usr/bin/pgbackrest --stanza=zareian --type=diff backup
|
||||
9
ops/server216/tadrisino-pgbackrest-diff.timer
Normal file
9
ops/server216/tadrisino-pgbackrest-diff.timer
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: daily differential encrypted backup
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:40:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
8
ops/server216/tadrisino-pgbackrest-full.service
Normal file
8
ops/server216/tadrisino-pgbackrest-full.service
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: weekly full encrypted backup
|
||||
After=postgresql@16-zareian.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=postgres
|
||||
ExecStart=/usr/bin/pgbackrest --stanza=zareian --type=full backup
|
||||
9
ops/server216/tadrisino-pgbackrest-full.timer
Normal file
9
ops/server216/tadrisino-pgbackrest-full.timer
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Tadrisino: weekly full encrypted backup
|
||||
|
||||
[Timer]
|
||||
OnCalendar=Sun *-*-* 03:00:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
26
ops/server216/tadrisino-restore-check
Executable file
26
ops/server216/tadrisino-restore-check
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# تدریسینو: آزمونِ بازیابی از بکاپِ رمزشده (ماهی یک بار، دستی: sudo -u postgres tadrisino-restore-check).
|
||||
# آخرین بکاپ + همهی WALِ آرشیوشده را در پوشهی موقت بازیابی میکند، Postgresِ موقت را روی سوکتِ همان پوشه (بیپورتِ شبکه،
|
||||
# archive_mode=off تا چیزی به مخزن ننویسد) بالا میآورد، شمارشها را کنارِ دیتابیسِ اصلی چاپ میکند و همه را پاک میکند.
|
||||
set -euo pipefail
|
||||
BIN=/usr/lib/postgresql/16/bin
|
||||
work=$(mktemp -d /var/lib/postgresql/restore-check.XXXXXX)
|
||||
trap '"$BIN/pg_ctl" -D "$work/data" -m immediate stop >/dev/null 2>&1 || true; rm -rf "$work"' EXIT
|
||||
pgbackrest --stanza=zareian --pg1-path="$work/data" --type=default --archive-mode=off restore --log-level-console=warn
|
||||
cat > "$work/data/postgresql.conf" <<CONF
|
||||
listen_addresses = ''
|
||||
port = 5499
|
||||
unix_socket_directories = '$work'
|
||||
archive_mode = off
|
||||
shared_buffers = 128MB
|
||||
CONF
|
||||
echo 'local all all peer' > "$work/data/pg_hba.conf"
|
||||
"$BIN/pg_ctl" -D "$work/data" -l "$work/log" -w -t 600 start >/dev/null
|
||||
for _ in $(seq 120); do
|
||||
[ "$(psql -h "$work" -p 5499 -d zareian_mathapp -Atc 'select pg_is_in_recovery()')" = f ] && break; sleep 1
|
||||
done
|
||||
Q="select 'users', count(*) from accounts_user union all select 'questions', count(*) from content_question
|
||||
union all select 'orders', count(*) from payment_order union all select 'payment_events', count(*) from payment_paymentevent
|
||||
union all select 'login_events', count(*) from accounts_loginevent"
|
||||
echo "restored | live"
|
||||
paste -d'|' <(psql -h "$work" -p 5499 -d zareian_mathapp -Atc "$Q") <(psql -p 5440 -d zareian_mathapp -Atc "$Q" | cut -d'|' -f2)
|
||||
25
ops/server216/tadrisino.local
Normal file
25
ops/server216/tadrisino.local
Normal file
@@ -0,0 +1,25 @@
|
||||
# تدریسینو: SSH در برابرِ رباتهای حدسِ رمز، پلهای تا آدمِ واقعی (اشتباهِ تایپی، کیبوردِ فارسی) گیر نکند.
|
||||
# ۶ اشتباه در ۱۰ دقیقه → ۱۰ دقیقه بسته؛ هر بارِ تکرار دو برابر (۲۰، ۴۰، ...، حداکثر یک هفته).
|
||||
# شبکهی داخلیِ شرکت (ادمینها، ۲۴۷، ۲۴۹) هرگز بسته نمیشود.
|
||||
[DEFAULT]
|
||||
ignoreip = 127.0.0.1/8 ::1 192.168.200.0/24
|
||||
bantime.increment = true
|
||||
bantime.factor = 2
|
||||
bantime.maxtime = 1w
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
backend = systemd
|
||||
mode = aggressive
|
||||
maxretry = 6
|
||||
findtime = 10m
|
||||
bantime = 10m
|
||||
|
||||
# فقط رباتِ سمج: پنج بار بستهشدن در یک روز → یک هفته روی همهی پورتها.
|
||||
[recidive]
|
||||
enabled = true
|
||||
logpath = /var/log/fail2ban.log
|
||||
banaction = iptables-allports
|
||||
findtime = 1d
|
||||
maxretry = 5
|
||||
bantime = 1w
|
||||
13
ops/server216/zareian-redis.service
Normal file
13
ops/server216/zareian-redis.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Redis for zareian-mathapp only
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=redis
|
||||
Group=redis
|
||||
ExecStart=/usr/bin/redis-server /etc/zareian-redis.conf
|
||||
Restart=on-failure
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
5
ops/server216/zareian.conf
Normal file
5
ops/server216/zareian.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
# تدریسینو، سرور اختصاصی: فقط localhost. max_connections با workers × DB_POOL_MAX_SIZE + 6 در /api/health سنجیده میشود.
|
||||
listen_addresses = '127.0.0.1'
|
||||
max_connections = 60
|
||||
shared_buffers = 1GB
|
||||
effective_cache_size = 4GB
|
||||
Reference in New Issue
Block a user