diff --git a/CLAUDE.md b/CLAUDE.md index 04f6330..033709d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,8 +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/` -and the `zareian-pg-*` / `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/`, +`~/zareian-pgbackup-216/`, `~/.ssh/zareian_pgbackup*`, and the `zareian-pg-*` / `zareian-pg216-*` / +`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 diff --git a/docs/operations.md b/docs/operations.md index a87a386..deffca7 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -389,21 +389,23 @@ 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`، fail2ban روی SSH | +| ورود | `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`) | +| ضدِ ربات | ۱) `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 ` | +| بکاپ | زنده روی ۲۴۷ (`~/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`) | | اپ | کاربرِ بی‌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 (فقط هشدارِ نبودنِ اسلاتِ بکاپ)، Qdrant از این سرور وصل، فرانت ۲۰۰، ریبوت و +سنجیده: `/api/health` با همه‌ی جزئیات ok و بی‌هشدار (`offsiteBackup` ok، عقب‌ماندگی ۰)، آزمونِ بازیابی از بکاپِ ۲۴۷ با همان شمارش‌ها، Qdrant از این سرور وصل، فرانت ۲۰۰، ریبوت و بالا آمدنِ خودکارِ همه‌ی سرویس‌ها، درخواستِ مستقیم به IP ۴۰۳ (nginx IPِ واقعی را می‌بیند). نسنجیده: هیچ درخواستی از مسیرِ ArvanCloud، پیامک، پرداخت و تیوتورِ زنده. **سوئیچ (ترتیب):** ۱) 443ِ ورودی باز شود. ۲) روی ۲۴۹ `zareian-mathapp` متوقف (پیامِ نگه‌داری در ArvanCloud)، `pg_dump -Fc`، بازیابی روی سرورِ جدید در دیتابیسِ خالی‌شده، مقایسه‌ی تعدادِ ردیف‌ها. ۳) originِ هر دو دامنه در ArvanCloud به `78.38.35.216`. -۴) health و یک ورود و یک آزمون از بیرون. ۵) بکاپِ زنده از ۲۴۷ (اسلات `zareian_247`، تونل؛ `AllowTcpForwarding` روی این سرور -بسته است و برای کاربرِ بکاپ باید با `Match User` باز شود) و `pull-app-logs.sh` روی ۲۴۷ به آدرسِ جدید. ۶) CLAUDE.md و این سند. +۴) health و یک ورود و یک آزمون از بیرون. ۵) روی ۲۴۷ یونیت‌های بکاپِ ۲۴۹ (`zareian-pg-*`) متوقف، `zareian-pg216-basebackup` +یک بار دستی (بعد از بازیابیِ دیتابیسِ نهایی)، و `pull-app-logs.sh` به آدرسِ جدید. ۶) CLAUDE.md و این سند. تا سوئیچ، `deploy.sh` روی هر دو سرور اجرا شود تا کدِ دو طرف یکی بماند. ### Qdrant diff --git a/ops/pgbackup/basebackup.sh b/ops/pgbackup/basebackup.sh index 58b460f..4d7207d 100755 --- a/ops/pgbackup/basebackup.sh +++ b/ops/pgbackup/basebackup.sh @@ -4,8 +4,10 @@ # --checkpoint=spread و --max-rate بار روی ۲۴۹ را کم نگه می‌دارند (آن‌جا سرورِ اصلی است). # نسخه تا pg_verifybackup قبولش نکند جای قبلی‌ها را نمی‌گیرد و چیزی پاک نمی‌شود. set -euo pipefail -ROOT=/home/administrator/zareian-pgbackup -BIN=/usr/lib/postgresql/14/bin +# ROOT/BIN/PORT از یونیت می‌آیند؛ پیش‌فرض‌ها همان بکاپِ ۲۴۹ است (یونیت‌های zareian-pg216-* برای سرورِ جدید). +ROOT="${PGB_ROOT:-/home/administrator/zareian-pgbackup}" +BIN="${PGB_BIN:-/usr/lib/postgresql/14/bin}" +PORT="${PGB_PORT:-15440}" KEEP=7 cd "$ROOT" @@ -14,7 +16,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 15440 -U zareian_replicator --no-password \ +"$BIN/pg_basebackup" -h 127.0.0.1 -p "$PORT" -U zareian_replicator --no-password \ -D "$tmp" -Fp -X stream --checkpoint=spread --max-rate=20M "$BIN/pg_verifybackup" "$tmp" mv "$tmp" "base/$ts" diff --git a/ops/pgbackup/export-payment-events.sh b/ops/pgbackup/export-payment-events.sh index a6162fa..545bfab 100755 --- a/ops/pgbackup/export-payment-events.sh +++ b/ops/pgbackup/export-payment-events.sh @@ -6,8 +6,9 @@ # خروجی: payment-log/payment-events-.csv، orders-…csv، transactions-…csv؛ ۱۴ فایلِ آخرِ هر نوع می‌ماند. # توجه: نسخه‌ی کامل تا چند دقیقه‌ی WAL عقب‌تر از ۲۴۹ می‌شود، ولی WALِ رسیده همه اعمال می‌شود. set -euo pipefail -ROOT=/home/administrator/zareian-pgbackup -BIN=/usr/lib/postgresql/14/bin +# ROOT/BIN از یونیت می‌آیند؛ پیش‌فرض‌ها همان بکاپِ ۲۴۹ است (یونیت‌های zareian-pg216-* برای سرورِ جدید). +ROOT="${PGB_ROOT:-/home/administrator/zareian-pgbackup}" +BIN="${PGB_BIN:-/usr/lib/postgresql/14/bin}" OUT="$ROOT/payment-log" mkdir -p "$OUT" diff --git a/ops/pgbackup/restore-check.sh b/ops/pgbackup/restore-check.sh index 2db122d..44756ff 100755 --- a/ops/pgbackup/restore-check.sh +++ b/ops/pgbackup/restore-check.sh @@ -6,8 +6,9 @@ # restore-check.sh '2026-10-10 16:30:00+03:30' # با KEEP=1 پوشه و Postgresِ موقت می‌مانند (برای بازیابیِ واقعی: از آن pg_dump بگیرید، بعد pg_ctl stop و rm). set -euo pipefail -ROOT=/home/administrator/zareian-pgbackup -BIN=/usr/lib/postgresql/14/bin +# ROOT/BIN از یونیت می‌آیند؛ پیش‌فرض‌ها همان بکاپِ ۲۴۹ است (یونیت‌های zareian-pg216-* برای سرورِ جدید). +ROOT="${PGB_ROOT:-/home/administrator/zareian-pgbackup}" +BIN="${PGB_BIN:-/usr/lib/postgresql/14/bin}" TARGET="${1:-}" latest=$(ls -1d "$ROOT"/base/2* | tail -1) diff --git a/ops/pgbackup/zareian-pg216-basebackup.service b/ops/pgbackup/zareian-pg216-basebackup.service new file mode 100644 index 0000000..3b1bff3 --- /dev/null +++ b/ops/pgbackup/zareian-pg216-basebackup.service @@ -0,0 +1,11 @@ +# روی سرورِ بکاپ (۲۴۷). نسخه‌ی کاملِ روزانه از ۲۱۶؛ همان 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 diff --git a/ops/pgbackup/zareian-pg216-basebackup.timer b/ops/pgbackup/zareian-pg216-basebackup.timer new file mode 100644 index 0000000..cb4ffe6 --- /dev/null +++ b/ops/pgbackup/zareian-pg216-basebackup.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Zareian MathApp: daily base backup from 216 + +[Timer] +OnCalendar=*-*-* 04:50:00 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/ops/pgbackup/zareian-pg216-export-events.service b/ops/pgbackup/zareian-pg216-export-events.service new file mode 100644 index 0000000..a6de586 --- /dev/null +++ b/ops/pgbackup/zareian-pg216-export-events.service @@ -0,0 +1,8 @@ +[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 diff --git a/ops/pgbackup/zareian-pg216-export-events.timer b/ops/pgbackup/zareian-pg216-export-events.timer new file mode 100644 index 0000000..019f12c --- /dev/null +++ b/ops/pgbackup/zareian-pg216-export-events.timer @@ -0,0 +1,9 @@ +[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 diff --git a/ops/pgbackup/zareian-pg216-tunnel.service b/ops/pgbackup/zareian-pg216-tunnel.service new file mode 100644 index 0000000..f260179 --- /dev/null +++ b/ops/pgbackup/zareian-pg216-tunnel.service @@ -0,0 +1,20 @@ +# روی سرورِ بکاپ (۲۴۷). تونلِ 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 diff --git a/ops/pgbackup/zareian-pg216-wal.service b/ops/pgbackup/zareian-pg216-wal.service new file mode 100644 index 0000000..a7ae8ca --- /dev/null +++ b/ops/pgbackup/zareian-pg216-wal.service @@ -0,0 +1,19 @@ +# روی سرورِ بکاپ (۲۴۷). هر تغییرِ دیتابیسِ ۲۱۶ را لحظه‌ای در ~/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