---
title: "النسخ الاحتياطي والاستعادة إلى نقطة زمنية وتمرين الاستعادة"
description: "أنشئ نسخة احتياطية من Quire واستعدها إلى نقطة زمنية وتحقق منها بتمرين الاستعادة."
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/ar/llms.txt
> Use this file to discover all available pages before exploring further.

# النسخ الاحتياطي والاستعادة إلى نقطة زمنية وتمرين الاستعادة

<span id="backup-point-in-time-recovery-and-the-restore-drill"></span>

يوضح التصميم `docs/architecture/23-ops.md` القسم 8. وهذا دليل تشغيل منتج Docker Compose. كُتب ليتبعه شخص لم يكتبه؛ فإذا لم تكن خطوة واضحة، فهذا عيب في الوثيقة.

## ما الذي نحميه وكيف <!--quire:what-is-protected-and-how-->

| الأصل | الطريقة | المكان |
| --- | --- | --- |
| قاعدة البيانات | أرشفة WAL باستمرار، بفواصل لا تتجاوز 60 ثانية، منذ أول تشغيل | وحدة `pgwal` |
| قاعدة البيانات | نسخ أساسية باستخدام `pg_basebackup` يوميًا افتراضيًا (`backup-scheduler`) | وحدة `pgbackup` |
| قاعدة البيانات | نسخ مشفرة من النسخ الأساسية وWAL كل خمس دقائق (`backup-offsite`) | مخزن منفصل تحدده |
| الملفات | وحدة `files`. انسخها بأداة النسخ لدى المضيف أو استخدم تخزينًا كائنيًا بإصدارات | وحدة `files` |
| الأسرار | `docker/.env`، ولا سيما `QUIRE_MASTER_KEY` (وأي `QUIRE_MASTER_KEY_RETIRED` لا يزال مستخدمًا) و`QUIRE_BACKUP_ENCRYPTION_KEY` و`docker/secrets/audit-signing-key.pem` | احتفظ بنسخة خارج هذا المضيف |
| فهارس البحث والذاكرات المخبأة والنسخ المحولة | لا تُنسخ احتياطيًا؛ يعاد بناؤها | — |

الأهداف: ألا تتجاوز نقطة الاستعادة 60 ثانية قبل العطل، وأن تتم الاستعادة خلال 60 دقيقة لقاعدة بيانات حجمها 500 GB.

من الأخطاء الشائعة استعادة قاعدة بيانات **دون ملفاتها**، ما يعرض صفحات معطوبة. كما أن استعادة قاعدة بيانات **دون `QUIRE_MASTER_KEY`** تعني تعذر فك تشفير بيانات اعتماد SSO وخطافات الويب وعمليات التكامل المحفوظة فيها؛ وإلى أن يكتمل تدوير المفتاح الرئيسي دون عناصر غير محسومة ([key-rotation.md](/ar/ops/key-rotation/)) يشمل ذلك المفاتيح المتقاعدة. كلاهما جزء من النسخة الاحتياطية.

## إنشاء النسخ الاحتياطية <!--quire:taking-backups-->

لإنشاء نسخة أساسية من المجموعة كاملة:

```sh
docker compose -f docker/compose.yaml --profile backup run --rm backup
```

يحتفظ الأمر بأحدث نسخ `QUIRE_BACKUP_KEEP` الأساسية (الافتراضي 5)، ويحذف ملفات WAL التي لم تعد أقدم نسخة بحاجة إليها كي لا ينمو الأرشيف بلا حدود. شغّله يوميًا بواسطة cron أو مؤقت systemd على المضيف:

```cron
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
```

أو دع الحزمة تجدوله: يشغل ملف `backup` خدمة `backup-scheduler` التي تنشئ نسخة أساسية كل `QUIRE_BACKUP_INTERVAL_HOURS` (افتراضيًا 24)، كما يشغل `backup-offsite` الموضح أدناه.

```sh
docker compose -f docker/compose.yaml --profile backup up -d
```

## نسخ مشفرة خارج المضيف <!--quire:encrypted-off-host-copies-->

توجد الوحدتان على المضيف نفسه الذي توجد عليه قاعدة البيانات، والنسخة الموجودة على الجهاز المتعطل ليست نسخة احتياطية. تنسخ خدمة `backup-offsite` كل نسخة أساسية وكل مقطع WAL مؤرشف عبر منفذ التخزين إلى مخزن منفصل، مع تشفيرها والاحتفاظ بها وفق سياسة:

- **التشفير.** تستخدم AES-256-GCM مع `QUIRE_BACKUP_ENCRYPTION_KEY` (أو الملف المحدد في `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): مفتاح من 32 بايت أنشئه بواسطة `openssl rand -hex 32`. لكل ملف قيمة nonce ووسم مصادقة خاصان، فلا يمكن قراءة النسخة دون المفتاح، ويُكتشف أي تعديل عليها. احتفظ بالمفتاح إلى جانب `QUIRE_MASTER_KEY` بعيدًا عن هذا المضيف ومخزن النسخ الاحتياطية. لا استعادة دون المفتاح.
- **الموقع.** تكون قيمة `QUIRE_BACKUP_STORAGE_DRIVER` واحدة من `s3` أو `azure` أو `local` (قرص بعيد مركب في `QUIRE_BACKUP_STORAGE_ROOT`). وتستخدم الإعدادات الخاصة بتخزين الملفات مع بادئة `QUIRE_BACKUP_`، مثل `QUIRE_BACKUP_S3_ENDPOINT` و`QUIRE_BACKUP_S3_BUCKET` و`QUIRE_BACKUP_S3_ACCESS_KEY_ID` وغيرها. استخدم حاوية مختلفة، ويفضل حسابًا مختلفًا أيضًا عن الملفات، مع بيانات اعتماد تسمح بالكتابة دون الحذف إذا أتاح الموفر ذلك.
- **الاحتفاظ.** يحتفظ بأحدث `QUIRE_BACKUP_OFFSITE_KEEP` نسخ أساسية (الافتراضي `QUIRE_BACKUP_KEEP` إن كان محددًا، وإلا 7) وملفات WAL التي تحتاجها أقدمها؛ وتحذف النسخ والمقاطع الأقدم من المخزن.
- **الموعد.** كل `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (افتراضيًا 300). الشحن قابل للتكرار دون أثر إضافي: يتخطى ما هو مخزن، ولا تُعد النسخة الأساسية مخزنة إلا بعد كتابة بيانها في النهاية.

يمكن تنفيذ الأوامر نفسها يدويًا:

```sh
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts ship
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
```

لاستعادة البيانات على مضيف جديد، أعد المجموعة أولًا، ثم اتبع الخطوات أدناه مع استخدام المجلد المسترجع بدل وحدة `pgbackup` وأرشيف `wal-archive` المسترجع بدل `pgwal`:

```sh
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
```

## الاستعادة إلى نقطة زمنية <!--quire:restoring-to-a-point-in-time-->

استخدم هذا الإجراء بعد فقدان بيانات، مثل استيراد خاطئ أو حذف مقرر أو ترحيل تعاقدي تحتاج إلى التراجع عنه. فهو يستبدل قاعدة البيانات الحية؛ لذلك تدرب عليه أولًا وفق التمرين أدناه.

1. **اختر الوقت المستهدف** بالتوقيت العالمي UTC، قبل وقوع الضرر مباشرة: `2026-09-24 09:30:00+00`. يوضح سجل التدقيق (`/admin/audit`) الوقت عادة.
2. **أوقف كل ما يكتب إلى قاعدة البيانات**: `docker compose -f docker/compose.yaml stop web content worker scheduler collab`
3. **احتفظ بالمجموعة المتضررة** إلى أن تتحقق من الاستعادة:
   ```sh
   docker compose -f docker/compose.yaml stop postgres
   docker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
   ```
4. **فك أحدث نسخة أساسية أقدم من الوقت المستهدف** في وحدة البيانات واطلب الاستعادة المحددة:
   ```sh
   docker run --rm -v quire_pgbackup:/backups:ro -v quire_postgres18-data:/var/lib/postgresql postgres:18-alpine sh -euc '
     base="$(ls -1d /backups/base-* | sort | tail -n 1)"   # or the one before the target
     rm -rf /var/lib/postgresql/18/docker && mkdir -p /var/lib/postgresql/18/docker
     tar -xzf "$base/base.tar.gz" -C /var/lib/postgresql/18/docker
     touch /var/lib/postgresql/18/docker/recovery.signal
     chown -R postgres:postgres /var/lib/postgresql/18/docker && chmod 700 /var/lib/postgresql/18/docker'
   ```
5. **نفذ الاستعادة**: شغّل Postgres مرة بإعدادات الاستعادة من ملف تجاوز Compose كي لا يتغير الملف العادي:
   ```yaml
   # docker/compose.recover.yaml
   services:
     postgres:
       command: [postgres, -c, "restore_command=cp /var/lib/postgresql/wal-archive/%f %p",
                 -c, "recovery_target_time=2026-09-24 09:30:00+00",
                 -c, recovery_target_action=promote, -c, archive_mode=off,
                 -c, max_connections=200, -c, hba_file=/etc/postgresql/pg_hba.conf]
   ```
   يستبدل ملف التجاوز الأمر كاملًا، لذلك يعيد ذكر الإعدادين اللذين تعتمد عليهما الاستعادة: يجب ألا تكون قيمة `max_connections` أقل من قيمة الخادم الأساسي (وإلا تتوقف الاستعادة برسالة «insufficient parameter settings»)، ويجب ذكر ملف `pg_hba.conf` المركب.
   ```sh
   docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgres
   docker compose -f docker/compose.yaml logs -f postgres   # wait for "database system is ready"
   ```
6. **تحقق من الاستعادة** قبل السماح للآخرين بالدخول: تحقق من سلسلة التدقيق (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`) ومن عودة البيانات المفقودة.
7. **عد إلى الوضع العادي**: `docker compose -f docker/compose.yaml up -d`. يعيد ذلك تشغيل Postgres مع تفعيل الأرشفة ويبدأ خط زمني جديد لـWAL. أنشئ نسخة أساسية جديدة فورًا.

تسبق البادئة `quire` اسم كل وحدة؛ ويعرض `docker volume ls` الأسماء الدقيقة.

## تمرين التحقق المجدول <!--quire:the-scheduled-verification-drill-->

تنفذ `backup-offsite` أيضًا تمرينًا كل `QUIRE_BACKUP_DRILL_INTERVAL_HOURS` (افتراضيًا 168، أي أسبوعيًا)، وتعيده في التشغيل التالي بعد الإخفاق. تجلب أحدث نسخة أساسية خارج المضيف وكل مقاطع WAL اللاحقة لها، وتفك تشفيرها (لإثبات استمرار صلاحية المفتاح وعدم تعديل الملفات)، وتقارن كل ملف ببيانه، وتتحقق من أن الأرشيف دليل بيانات Postgres ومن عدم وجود فجوات في WAL منذ النسخة الاحتياطية. يُكتب التقرير في المخزن ضمن `reports/drill-<time>.json` وفي سجل الخدمة؛ ويسمي التمرين الفاشل الملف أو أول مقطع مفقود.

## تمرين الاستعادة <!--quire:the-restore-drill-->

النسخة التي لم تُستعد من قبل ليست نسخة احتياطية مؤكدة. يستعيد التمرين أحدث نسخة أساسية مكتملة مأخوذة قبل الوقت المستهدف، مع أرشيف WAL، إلى قاعدة Postgres مؤقتة لا تشترك بشيء مع الحية، ثم يثبت نجاح النتيجة:

```sh
docker/scripts/restore-drill.sh                                  # to ninety minutes ago
docker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
```

يكون الوقت المستهدف بتوقيت UTC وبالصيغة نفسها تمامًا. يلزم وجود نسخة أساسية أقدم منه وملفات WAL مؤرشفة تتجاوزه. في التثبيت الجديد، أنشئ نسخة أساسية وانتظر ظهور المقطع المؤرشف التالي (خلال دقيقة على الأكثر عند وجود كتابات) قبل اختيار وقت بعدها. يحتاج التمرين إلى Docker وbash على المضيف فقط.

يفشل التمرين عند إخفاق أي خطوة:

1. **إمكانية الاستعادة**: تعيد المجموعة المؤقتة تشغيل WAL حتى الوقت المستهدف وتُفتح.
2. **الاكتمال**: قارن عدد الصفوف في كل جدول بقاعدة البيانات الحية (`tooling/restore-drill`). تقدمت القاعدة الحية منذ الوقت المستهدف، لذا قد يختلف الجدول بمقدار أكبر القيمتين، 500 صف أو عُشر حجمه، في أي اتجاه (تتسبب الكتابات بتأخر الاستعادة، وتتسبب عمليات الحذف بزيادة ما تحتفظ به)؛ ولا يُعد القسم المنشأ بعد الوقت المستهدف جدولًا مفقودًا. وسّع الهامش في تثبيت أكثر نشاطًا باستخدام `QUIRE_DRILL_MAX_BEHIND` و`QUIRE_DRILL_MAX_DRIFT_RATIO`. ويؤدي غياب جدول أو إفراغه إلى الفشل.
3. **السلامة**: تحقق من سلسلة تجزئة التدقيق على النسخة المستعادة.
4. **قابلية الاستخدام**: تحقق من قراءة دور التطبيق عبر أمان الصفوف.
5. **الوقت**: قس المدة من البدء إلى النجاح مقابل `QUIRE_DRILL_RTO_SECONDS` (الافتراضي 3600).

لا يكتب التمرين أبدًا إلى قاعدة البيانات الحية أو وحداتها: تُركب وحدتا النسخ الاحتياطي وWAL للقراءة فقط، وتُحذف المجموعة المؤقتة في النهاية سواء نجح التمرين أم فشل.

عيّن `QUIRE_DRILL_REPORT` إلى مسار لكتابة تقرير JSON سواء نجح التمرين أم فشل، وشغّله وفق جدول من مضيف Docker:

```cron
30 3 1 * * cd /srv/quire && QUIRE_DRILL_REPORT=/var/log/quire-drill.json docker/scripts/restore-drill.sh >> /var/log/quire-drill.log 2>&1
```

شغله شهريًا وقبل كل ترقية. ويمنع فشل التمرين الترقية. ومرة كل ربع سنة، اطلب من شخص لم يكتب هذا الدليل تنفيذ استعادة حقيقية إلى نقطة زمنية على مضيف احتياطي بالاعتماد على هذه الوثيقة وحدها.

## الملفات <!--quire:files-->

توجد الملفات المحلية في وحدة `files`. أنشئ نسخة احتياطية منها مع قاعدة البيانات وفي الوقت نفسه، واستعدهما معًا:

```sh
docker run --rm -v quire_files:/files:ro -v "$PWD":/out alpine tar -czf /out/files-$(date -u +%Y%m%d).tar.gz -C /files .
```

عند استخدام التخزين الكائني، فعّل إصدارات الحاوية واحتفظ بالإصدارات غير الحالية 35 يومًا؛ وعندها يتولى المخزن نفسه الاستعادة إلى نقطة زمنية للملفات.

Source: https://docs.quirelms.com/ar/ops/backup-restore/index.mdx
