التعافي من الكوارث
دليل تشغيل التعافي من الكوارث لكاظمه
Section titled “دليل تشغيل التعافي من الكوارث لكاظمه”الإصدار: 0.6.x / المرحلة 4.5
الجمهور: المشغّلون الذين ينشرون كاظمه على عقدة واحدة أو بنسخ متعددة
ذوو الصلة: kazma_core.backup.restore، kazma_core.backup.restore_drill، SECURITY.md
1. ما الذي يجب حمايته
Section titled “1. ما الذي يجب حمايته”| الأصل | الموقع | الحرجية |
|---|---|---|
| الإعدادات + مؤشرات الأسرار | kazma-data/settings.db (+ الخزنة) | حرج |
| مفتاح تشفير الخزنة | متغير البيئة KAZMA_VAULT_KEY / .env | حرج — بدونه تصبح الأسرار غير قابلة للقراءة |
| سر المشغّل المشترك | KAZMA_SECRET | حرج |
| نقاط حفظ LangGraph | kazma-data/checkpoints.db | عالية — استمرارية المحادثة |
| جلسات الدردشة | kazma-data/chat_sessions.db (أو المسار المُهيأ) | عالية |
| مهام السرب | kazma-data/swarm_tasks.db | متوسطة–عالية |
| الذاكرة / المتجهات / الرسم البياني | kazma-data/vector_memory/، memory.db، vector.db، knowledge_graph.db | متوسطة |
| مهام Cron | kazma-data/cron.db | متوسطة |
| البيانات الوصفية لذكاء المستندات | {documents.storage_root}/documents.db (أو Postgres عندما تكون خلفية البيانات الوصفية PG) | عالية — المكتبة + المهام |
| كتل محتوى المستندات المُعنونة بالمحتوى | {documents.storage_root}/ (quarantine/originals/artifacts + manifests) | حرج — غير قابل للاسترداد دون نسخة احتياطية |
| ذاكرة الرسم البياني | Neo4j (bolt://…) — وحدة تخزين Docker، وليست في مجلد البيانات | متوسطة–عالية |
| إعداد MCP + الموصلات | kazma.yaml في جذر التثبيت، وليس في مجلد البيانات | حرج — الاسترداد بدونه يقلع بلا أدوات |
| جلسات الويب المُعتمة | ConfigStore / Postgres | منخفضة (يعيد المستخدمون تسجيل الدخول) |
خارج النطاق (أبدًا على قرص التطبيق وحده):
KAZMA_SECRETKAZMA_VAULT_KEY- مفاتيح API للمزوّدين إن لم تكن في الخزنة
- سر عميل OIDC
- بيانات اعتماد Postgres (
KAZMA_DATABASE_URL)
2. إجراء النسخ الاحتياطي (SQLite بعقدة واحدة)
Section titled “2. إجراء النسخ الاحتياطي (SQLite بعقدة واحدة)”التكرار
Section titled “التكرار”| البيئة | هدف RPO | الإجراء |
|---|---|---|
| مختبر / شخصي | على أفضل جهد | يوميًا أو قبل الترقيات |
| إنتاج بعقدة واحدة | ≤ 24h | نسخة يومية مؤتمتة + نسخ خارج الموقع |
| إنتاج بنسخ متعددة | ≤ 1h | نسخ احتياطي Postgres مستمر + لقطة التطبيق كل 6 ساعات |
كيف يعمل الآن (restic، منذ 2026-08-29)
Section titled “كيف يعمل الآن (restic، منذ 2026-08-29)”النسخ الاحتياطي تلقائي. لا شيء يحتاج إلى تشغيل يدوي.
تنتج كل دورة جيل تجهيز تحت
kazma-data/backups/universal/<epoch>/ يحتوي كل قاعدة بيانات SQLite
(بأمان WAL عبر Online Backup API)، والأصول، و.env، وkazma.yaml،
وresearch/، وتصدير JSONL لرسم Neo4j البياني. ويُفرَّغ Postgres منفصلًا إلى
kazma-data/backups/pg/ بـ pg_dump -Fc.
ثم تُؤخذ لقطة لذلك الجيل في مستودعي restic مستقلين:
| المستودع | الموقع | بيانات الاعتماد |
|---|---|---|
| محلي | kazma-data/backups/restic | عبارة المرور في ~/.kazma/restic.pass |
| خارج الموقع (موصى به) | s3:https://<account>.r2.cloudflarestorage.com/<bucket> (أو B2) | مفتاح مضيف للإلحاق فقط (PutObject/GetObject/ListBucket + DeleteObject على locks/* فقط) |
| خارج الموقع (قديم) | rclone:<remote>/restic | rclone OAuth — لا تستخدم Google Drive / حساب خدمة. حسابات الخدمة بلا حصة Drive؛ فحوصات الكتابة على rclone: قد تبدو سليمة بينما كل عملية رفع تفشل بخطأ 403. |
فضّل restic الأصلي لـ S3 (Cloudflare R2 أو Backblaze B2). يجب ألا يستطيع
مفتاح المضيف تنفيذ restic forget --prune. احتفظ بمفتاح تقليم بوصول كامل
خارج هذه الآلة. تفحص remote_writable() المسار s3: بعملية PUT+DELETE
حقيقية بتوقيع SigV4 تحت locks/ — لم يعد يُعامل Remote من نوع rclone: يستطيع
السرد دون الكتابة على أنه سليم.
يستخدم المستودعان بيانات اعتماد مختلفة عمدًا: يجب ألا يفشل مسار خارج الموقع عند إبطال رمز موصل. فعل Drive+rclone ذلك بالضبط في 2026-08-27، فذهبت 29 نسخة احتياطية متتالية إلى المحلي فقط دون أن يلاحظ أحد.
ما يتبقى على المشغّل (ليس كودًا): أنشئ حاوية R2/B2 ومفتاح المضيف للإلحاق
فقط، واضبط AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY، ووجّه
backups.restic.remote إلى s3:…، ونفّذ restic init، ثم شغّل
python -m kazma_core.backup.restore_drill ضد جيل مسترد قبل التقاعد عن
Drive. أودع عبارة مرور restic خارج هذا المضيف.
لماذا restic بدلًا من أرشيفات zip التي كان هذا الدليل يصفها: إزالة التكرار، والتشفير، واسترداد يختبره المشروع الأصلي بصرامة أكثر مما نستطيع. على هذا التثبيت كلّفت أربعة أجيال 131 MB حيث كلّفت خطة zip مساحة 3.8 GB، وكل جيل إضافي يضيف نحو 2 MB.
الاحتفاظ
Section titled “الاحتفاظ”على أساس الزمن، لا العدد: 24 ساعية، و30 يومية، و8 أسبوعية، و12 شهرية،
تطبقها مهمة restic_maintenance، التي تمسح أيضًا الأقفال القديمة وتشغّل
restic check. عبارة “احتفظ بآخر 30” تعني ثلاثين يومًا أو ثلاثين ساعة حسب
عدد مرات تشغيل الحلقة — وليست ضمانة استرداد.
تُطبَّق السياسة لكل نوع نسخة احتياطية (forget --group-by host,tags:
universal وpg وlegacy وprobe). التجميع الافتراضي في restic حسب المضيف +
المسارات، وكل نسخة universal مجلد backups/universal/<epoch> جديد — فكانت كل
لقطة مجموعتها الخاصة المفردة، وحتى 2026-09-23 لم يحذف الاحتفاظ شيئًا قط
(199 لقطة، 199 مجموعة). ثلاثون يومية لا سبعة هو خيار المشغّل من اليوم نفسه:
69 ذاكرة دردشة أُفرغت بين 08-30 و09-21 لم تكن قابلة للاسترداد إلا من لقطة
عمرها ثلاثة أسابيع.
التشغيل النظيف يسجّل [restic] maintenance ok: <local|remote> (forget --prune, check)؛
قبل وجود هذا السطر، كانت الصيانة التي لا تعمل قط لا تُميَّز عن الصيانة التي
تنجح دائمًا.
التجهيز على القرص يحتفظ بـ 2 جيلين فقط؛ التاريخ يعيش في restic.
عبارة المرور
Section titled “عبارة المرور”<install>/.kazma/restic.pass (تفوز ~/.kazma/restic.pass القديمة غير الفارغة
دون غيرها) تفك تشفير كل لقطة، المحلية والخارجية. وهي عمدًا ليست
KAZMA_SECRET — فالعيب الذي كان يُصلَح هو مفتاح الخزنة المسافر مع البيانات
التي يحميها.
احتفظ بنسخة خارج هذه الآلة. التشفير ينقل نقطة الفشل الوحيدة من الأرشيف إلى المفتاح. بدونه، كل نسخة احتياطية غير قابلة للقراءة نهائيًا.
التحقق دون استرجاع
Section titled “التحقق دون استرجاع”python -m kazma_core.backup.restore_drillرموز الخروج تميّز الدليل: 0 / PASS تعني نجاح كل فحص منطبق؛ 1 / FAIL
تعني أن فحصًا وجد فشلًا؛ 2 / UNVERIFIED تعني أن الفحوصات المطلوبة لم
تكتمل، دون دليل على تلف. غياب pg_restore، وقراءة الإعادة غير المتاحة،
ومستودع مقفل بنسخة احتياطية حية، لا يمكنها إثبات صحة الاسترداد. تصدر
التشغيلات غير الموثقة تحذيرًا عبر قناة العمليات القائمة؛ وتصدر الفحوصات
الفاشلة إنذارًا حرجًا. ثبّت أدوات العميل المفقودة أو اعالج التبعية
المُبلَّغ عنها وأعد تشغيل التمرين للقراءة فقط.
وهو يشغّل نفسه أيضًا، يوميًا، بعد خمس دقائق من الإقلاع ثم مرة كل 24 ساعة — تُحسب من آخر تشغيل مكتمل، مخزنة في مخزن الإعدادات، فلا تستطيع مضيفة تعيد التشغيل كثيرًا مواصلة تصفير الساعة. (كان أسبوعيًا، وكانت الحلقة تنام فترة كاملة قبل جولتها الأولى، ما يعني على مضيفة تعيد التشغيل بكثرة أنه لم يشغّل أبدًا. ثلاثة أيام من السجلات الحية: 34 بداية مجدول، صفر نتائج.)
ما تفحصه الجولة اليومية. السلامة وحدها تثبت أن البايتات غير تالفة؛ ولا تثبت أنك تستطيع استرجاع بياناتك. لذا، بترتيب العواقب:
| الفحص | ما الذي سيكشفه |
|---|---|
vault:decrypt — مفتاح .env الخاص بالنسخة الاحتياطية نفسها يفتح خزنة النسخة نفسها | KAZMA_VAULT_KEY قديم أو مُدوَّر. الملف موجود وبالحجم الصحيح تمامًا في الحالتين، وكل سر مشفر خلف مفتاح خاطئ مفقود |
الاكتمال مقابل databases.items الخاصة بالبيان التعريفي نفسه | قاعدة بيانات زعم البيان أنه أخذها ولم يأخذها |
PRAGMA integrity_check على كل ملف SQLite في نسخة scratch | تلف في الصفحات المنسوخة |
| صفر جداول يُعد فشلًا | ملف فارغ يجتاز integrity_check بكل سرور |
pg_restore --list على أرشيف Postgres | ترويسة dump مبتورة أو غير قابلة للقراءة |
ما تضيفه الجولة الأسبوعية، بقراءة البايتات لا الترويسات:
| الفحص | ما الذي سيكشفه |
|---|---|
تمرير dump Postgres عبر pg_restore --file=- | dump تالفة أقسام بياناتها خلف TOC سليم. كل كتلة تُقرأ — لكن لا شيء يُحمَّل، فهذا يثبت أن الأرشيف يُقرأ، لا أنه يُسترد |
| استرداد الـdump في قاعدة scratch وفحصها ثم إسقاطها | dump تُقرأ لكن لا تُسترد: مخطط لا يُعاد إنشاؤه، صفوف لا تُحمَّل، جداول كاظمه مفقودة، kazma_settings فارغة. مفعّلة افتراضيًا مع Postgres — انظر أدناه |
restic check --read-data-subset=5% | تعفّن البتات داخل حزم المستودع |
قراءة راجعة لـ HEAD للكائن الخارجي، بمقارنة الحجم المخزن بالمرفوع | رفع مبتور. الكائن الناقص والكامل يبدوان متطابقين من جانب الإرسال، الذي يعيد 200 لكليهما |
بروفة الاسترداد (kazma_core.backup.restore_rehearsal) هي فحص النسخ
الاحتياطي الوحيد الذي يكتب إلى خادم قاعدة البيانات. وهي مفعّلة افتراضيًا
مع Postgres (منذ 2026-09-27؛ كانت اختيارية، لذا لم يكن “الـdump يُسترد”
مستنتجًا قط إلا من “الـdump تُقرأ”). اضبط backups.pg.restore_rehearsal على
false، أو KAZMA_PG_RESTORE_REHEARSAL=0، لتعطيلها (=1 تفعّلها رغم
الإعداد). تنشئ الجولة الأسبوعية kazma_restore_rehearsal_<epoch> على الخادم
نفسه، وتسترد أحدث dump فيها بـ pg_restore، وتفحص أن جداول كاظمه عادت وأن
kazma_settings ليست فارغة، ثم تُسقطها. كل CREATE/DROP تعيد فحص الاسم
مقابل ذلك النمط بالضبط وترفض اسم قاعدة البيانات الحية نفسها؛ الانهيار بين
الاثنين يترك قاعدة scratch تزيلها الجولة التالية (الأقدم من يوم، النمط نفسه —
لا شيء غير ذلك). يحتاج مستخدم قاعدة البيانات إلى CREATEDB (ALTER ROLE <user> CREATEDB)؛ بدونه يبلّغ الفحص UNVERIFIED مع ذكر تلك المنحة، أبدًا
ليس بنسخة احتياطية فاشلة. جهّز حجم الخادم لنسخة ثانية من جداول كاظمه أثناء
تشغيلها.
لرؤيتها تعمل دون انتظار الجولة الأسبوعية، شغّل التمرين العميق الآن (يضم البروفة عندما تكون مفعّلة):
& '.venv\Scripts\python.exe' -m kazma_core.backup.restore_drill --deepالتمرين الذي لم يشغّل قط لا يثبت شيئًا. لا تصف النسخ الاحتياطي بأنها موثقة حتى تظهر نتيجة تمرين في السجل — وجود مجدول في الكود ليس دليلًا. بيان المرنة يعلّم هذه الآلية
proven_in_production=Falseلهذا السبب بالضبط، ويعود إلىTrueعند وجود نتيجة حية.
السكربتات القديمة
Section titled “السكربتات القديمة”ما زال scripts/backup_kazma.py وscripts/restore_kazma.py يعملان وما زالا
ينتجان ملفات zip. لم يعودا المسار الأساسي ولا يشملان تصدير الرسم البياني أو
kazma.yaml.
3. إجراء الاسترداد (عقدة واحدة)
Section titled “3. إجراء الاسترداد (عقدة واحدة)”هدف RTO: أقل من ساعة للعقدة الواحدة مع أسرار معروفة. تمت البروفة: 2026-08-29، من المستودعين المحلي والخارجي.
الخطوة 1 — اعرف ما الذي يمكنك الاسترداد إليه
Section titled “الخطوة 1 — اعرف ما الذي يمكنك الاسترداد إليه”python -m kazma_core.backup.restore --listيسرد كل جيل قابل للاسترداد مع لقطته وملف dump Postgres المرافق له.
لا تستخدم
restic restore latestأبدًا. يختار أحدث لقطة بوقت التقاط اللقطة، وهو ليس أحدث بيانات. الأجيال المُدخلة خارج الترتيب — استيراد جماعي، إعادة رفع — تحمل طوابع زمنية حديثة ومحتوى قديمًا، فقد يناولكlatestنسخة احتياطية تنقصهاkazma.yamlوتصدير الرسم البياني وهي تبدو نجاحًا نظيفًا. الأمر أعلاه يختار حسب الجيل؛ استخدمه بدل restic مباشرة.
الخطوة 2 — استرداد الملفات
Section titled “الخطوة 2 — استرداد الملفات”python -m kazma_core.backup.restore --target D:resh-kazmaأضف --generation <epoch> لاختيار نقطة محددة، أو --repo <path> للاسترداد من
المستودع الخارجي حين تكون الآلة مفقودة.
يجب أن يكون الهدف فارغًا — الاسترداد فوق شجرة قائمة يشوّك حالتين في واحدة تبدو معقولة وليست أيًّا منهما.
تحصل على تخطيط تثبيت: .env، وkazma.yaml، وkazma-data/،
وneo4j_graph.jsonl، وملف dump Postgres المقترن تحت pg/. كل قاعدة بيانات
SQLite مستردة تُفحص سلامتها قبل أن يبلّغ عن النجاح.
الخطوة 3 — تحميل قواعد البيانات
Section titled “الخطوة 3 — تحميل قواعد البيانات”غير تلقائي، وعمدًا: هذه تستبدل بيانات حية. تُطبع الأوامر الدقيقة في نهاية الخطوة 2 مع ملء المسارات.
# Postgrespg_restore --clean --if-exists -d "$env:KAZMA_DATABASE_URL" "D:resh-kazma\pg\…\pg_shared_….dump"
# Graph memory, into an EMPTY Neo4j (it refuses a populated one)python -c "from kazma_core.backup.neo4j_backup import restore_graph; print(restore_graph(r'D:resh-kazmaeo4j_graph.jsonl'))"الخطوة 4 — وجّه كاظمه إليه وابدأ
Section titled “الخطوة 4 — وجّه كاظمه إليه وابدأ”انسخ .env وkazma.yaml وkazma-data/ إلى جذر التثبيت، أو وجّه
KAZMA_DATA_DIR إلى الشجرة المستردة. ثم:
GET /health/ready→ready، كل الفحوص سليمة- تسجيل الدخول يعمل
- تظهر جلسة دردشة سابقة
- الإعدادات ← المزوّدون لا يزالون مُهيئين (الخزنة تُفتح — هذا يثبت أن
.envعاد) - الأدوات مدرجة (هذا يثبت أن
kazma.yamlعاد)
أنماط الفشل
Section titled “أنماط الفشل”| العَرَض | السبب المرجح | الإصلاح |
|---|---|---|
| الإعدادات فارغة / المفاتيح مفقودة | KAZMA_VAULT_KEY خاطئ أو مفقود | استرجع مفتاح الخزنة من مدير كلمات المرور |
| 401 في كل مكان | KAZMA_SECRET خاطئ | استرجع السر؛ امسح الكوكيز القديمة |
| SQLite “database is locked” | العملية لا تزال تعمل | اقتل uvicorn/python؛ أعد المحاولة |
| البحث المتجهي فارغ | وحدة تخزين المتجهات ليست في مسار النسخ الاحتياطي | استرجع مسار vector_memory / Chroma؛ أعد الفهرسة إن لزم |
4. النسخ المتعددة / Postgres (المرحلة 4.3)
Section titled “4. النسخ المتعددة / Postgres (المرحلة 4.3)”عندما يُضبط KAZMA_DATABASE_URL=postgresql://…:
| المكوّن | الخلفية | ملاحظات |
|---|---|---|
| الإعدادات / الجلسات / مخطط مستخدمي المنصة المشترك | Postgres (kazma_core.db) | مطلوب لاتساق النسخ المتعددة |
| ذاكرات التخزين المؤقت المحلية، Chroma، مؤقت كل عقدة | القرص المحلي | لا تشارك ملفات SQLite عبر NFS |
| نقاط الحفظ | فضّل منقّط Postgres عند تهيئته | انظر متغيرات البيئة أدناه |
متغيرات البيئة للنسخ المتعددة
Section titled “متغيرات البيئة للنسخ المتعددة”KAZMA_DATABASE_URL=postgresql://kazma:…@db:5432/kazmaKAZMA_DB_BACKEND=postgresKAZMA_PG_POOL_MIN=1KAZMA_PG_POOL_MAX=10KAZMA_PRODUCTION=1KAZMA_VAULT_KEY=…KAZMA_SECRET=… # or IdP-only with multi-userKAZMA_PUBLIC_URL=https://kazma.example.comحزم اختيارية:
pip install 'psycopg[binary,pool]>=3.1' 'langgraph-checkpoint-postgres>=2.0'# or: pip install -e ".[postgres]"النسخ الاحتياطي لـ Postgres
Section titled “النسخ الاحتياطي لـ Postgres”استخدم معيار منصتك:
- مُدار: فعّل النسخ الاحتياطي المؤتمت + PITR (RDS، Cloud SQL، Azure).
- مُستضاف ذاتيًا:
Terminal window pg_dump -Fc "$KAZMA_DATABASE_URL" -f kazma-$(date -u +%Y%m%d).dump - الاسترداد:
Terminal window pg_restore -d "$KAZMA_DATABASE_URL" --clean --if-exists kazma-YYYYMMDD.dump
قاعدة: لا تشغّل أبدًا نسخًا متعددة مقابل ملف SQLite مشترك.
مثال Compose: docker-compose.postgres.yml.
5. تعدد المستخدمين / IdP (المرحلة 4.4)
Section titled “5. تعدد المستخدمين / IdP (المرحلة 4.4)”| الوضع | الكيفية |
|---|---|
| مشغّل واحد | KAZMA_SECRET + جلسة مُعتمة (الافتراضي) |
| تعدد مستخدمين محلي | أنشئ مستخدمين عبر platform.users / create_local_user()؛ تسجيل الدخول باسم مستخدم+كلمة مرور |
| OIDC | KAZMA_OIDC_ISSUER، CLIENT_ID، CLIENT_SECRET، KAZMA_PUBLIC_URL → /api/auth/oidc/start |
الأدوار: viewer < operator < admin (انظر platform_rbac.py).
بعد استرداد التعافي من الكوارث، أعد الاختبار:
- تسجيل دخول admin
- يستطيع operator الدردشة/الموافقة
- لا يستطيع viewer الوصول إلى
/api/settings
6. قائمة فحص التمرين (تشغَّل فصليًا)
Section titled “6. قائمة فحص التمرين (تشغَّل فصليًا)”النسخ الاحتياطي يشغّل نفسه؛ التمرين موجود لإثبات أن الاسترداد لا يزال يعمل، وهو النصف الذي يتعفن دون ملاحظة.
-
python -m kazma_core.backup.restore --list— النقاط موجودة، والأحدث حديث - استرداد إلى مجلد تجهيز فارغ — توقع كل الخطوات خضراء
- تأكيد أن كل قاعدة بيانات SQLite اجتازت
integrity_check(الاسترداد يبلّغ عنه) - الاسترداد مرة واحدة من المستودع الخارجي، لا المحلي فقط (
--repo rclone:…) - تحميل dump Postgres في قاعدة بيانات تُرمى بعد الاستخدام
- تحميل الرسم البياني في Neo4j فارغ ومقارنة أعداد العقد/العلاقات
- تأكيد أن الخزنة تُفتح وأن الأدوات مدرجة (يثبت
.env+kazma.yaml) - قيّس الوقت (حدّث ملاحظة RTO أعلاه)
- تأكيد أن عبارة مرور restic لا تزال قابلة للاسترجاع من خارج هذه الآلة
- وثّق أي فجوات في هذا الملف
7. جهات الاتصال للحوادث
Section titled “7. جهات الاتصال للحوادث”| الحدث | الإجراء |
|---|---|
| اشتباه اختراق | دوّر KAZMA_SECRET وKAZMA_VAULT_KEY ومفاتيح المزوّدين؛ أبطل الجلسات؛ استرد من آخر نسخة احتياطية معروفة الصلاحية إن لزم |
| تلف البيانات | أوقف الكُتّاب → استرد من آخر zip/dump سليمة → اختبار دخان |
| فقدان مفتاح الخزنة | غير قابل للاسترداد للأسرار المخزنة في الخزنة — استرجع المفتاح من مخزن دون اتصال أو أعد إدخال الأسرار |
حافظ على هذا الدليل مع كل تغيير في بنية الإنتاج.