SSD Nodes Learn 🎉 VPS من $4.99/شهر
الأدلة Matt Connorبقلم Matt Connor

نسخ Vaultwarden الاحتياطي واستعادته على VPS

تعلّم نسخ خزنة Vaultwarden الحية باستخدام sqlite3 .backup، وحفظ المرفقات وconfig.json وrsa_key، ثم اختبار الاستعادة قبل أن تحتاج إليها.

ما الذي يجب أن تتضمنه نسخة Vaultwarden الاحتياطية

نسخة Vaultwarden الاحتياطية هي نسخة من مجلد البيانات بالكامل، ويجب نسخ قاعدة البيانات الموجودة فيه بالطريقة الصحيحة. شغّل sqlite3 db.sqlite3 ".backup out.sqlite3" بدلاً من cp، لأن النسخ العادي لقاعدة بيانات قيد الكتابة قد ينتج ملفاً لا يمكن فتحه. ثم احتفظ بالملفات الموجودة بجانبها، فهذا هو الجزء الذي ينساه الناس عادةً.

في تثبيت Docker، يكون مجلد البيانات هو المجلد الذي ربطته بـ/data. قد يكون هذا مساراً على المضيف أو وحدة تخزين مسماة، ويحدد الفرق بين عمليات الربط بالمضيف ووحدات التخزين المسماة مكان وجود خزنتك فعلياً على القرص. إليك محتوياته.

  • db.sqlite3: كل حساب، وكل عنصر في الخزنة، وكل مجلد، وكل مؤسسة. يؤدي فقدان هذا الملف إلى فقدان الخزنة.
  • db.sqlite3-wal وdb.sqlite3-shm: سجل الكتابة المسبقة (WAL) وفهرس الذاكرة المشتركة الخاص به. تبقى عمليات الكتابة الحديثة هنا إلى أن يدمجها SQLite في الملف الرئيسي.
  • attachments/: الملفات التي أرفقها المستخدمون بعناصر الخزنة، وهي مشفّرة ومخزنة في مجلد مستقل لكل عنصر.
  • sends/: الملفات الموجودة خلف روابط Bitwarden Send.
  • config.json: كل إعداد حفظته من صفحة الإدارة.
  • rsa_key.pem، إضافة إلى rsa_key.der وrsa_key.pub.der في التثبيتات الأقدم: المفتاح الذي يوقّع رموز تسجيل الدخول.
  • icon_cache/: أيقونات المواقع التي جرى تنزيلها. هذا هو المجلد الوحيد الذي يمكنك تخطيه، لأن Vaultwarden يعيد تنزيل الأيقونات عند الطلب.

هل قاعدة بيانات Vaultwarden آمنة؟ ما الذي يحتويه الملف فعلياً

يجيب أمران عن ذلك، ويمكنك تشغيلهما الآن.

sudo apt update && sudo apt install -y sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select email from users;"
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select name from ciphers limit 1;"

يطبع الأمر الأول عناوين البريد الإلكتروني للمستخدمين بنص واضح. ويطبع الأمر الثاني اسم عنصر واحد، ويبدو كما يلي:

2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=

تُشفَّر أسماء العناصر وأسماء المستخدمين وكلمات المرور والملاحظات بواسطة العميل قبل إرسالها، لذلك يخزّن الخادم نصاً مشفراً لا يستطيع قراءته. تُمثّل البادئة 2. نوع التشفير في Bitwarden، وتتبعها متجهة تهيئة (IV)، والنص المشفر، ورمز MAC (رمز مصادقة الرسالة)، وكل منها بترميز base64 ويفصل بينها |. يُشتق المفتاح الذي يفك تشفيرها من كلمة المرور الرئيسية للحساب، ولا تصل كلمة المرور هذه إلى الخادم بصيغة قابلة للاستخدام. هذا الجزء متطابق سواء شغّلت Vaultwarden أو الخادم الرسمي، كما يوضّح مقارنة Vaultwarden وBitwarden المستضاف ذاتياً.

أما بقية قاعدة البيانات فليست مشفّرة. تُخزَّن عناوين البريد الإلكتروني وأسماء الحسابات وتلميحات كلمات المرور ورموز استرداد المصادقة الثنائية بنص واضح، إلى جانب بيانات وصفية مثل أوقات الإنشاء والجهة التنظيمية المالكة للعنصر. لذلك يُعد ملف النسخ الاحتياطي سراً بحد ذاته. فأي شخص يحوز هذا الملف يعرف هوية مستخدميك، ويمكنه مهاجمة الكتل المشفّرة دون اتصال وبالسرعة التي تسمح بها أجهزته. وتفرض هذه الحقيقة وحدها قواعد التخزين الواردة أدناه: تُشفَّر النسخة قبل مغادرتها الخادم.

لماذا لا يُعد نسخ db.sqlite3 أثناء تشغيل Vaultwarden نسخة احتياطية

يشغّل Vaultwarden قاعدة SQLite في وضع WAL افتراضياً (ENABLE_DB_WAL=true). تُكتب البيانات أولاً في db.sqlite3-wal، ولا تُدمج في db.sqlite3 إلا عند تنفيذ checkpoint. إذا نسخت db.sqlite3 وحده، فستحصل على حالة قاعدة البيانات عند آخر checkpoint. لذلك قد تكون كلمة مرور حُفظت قبل عشر دقائق مفقودة من أرشيفك من دون أي تنبيه.

ولا يُعد نسخ الملفات الثلاثة كلها باستخدام cp حلاً أيضاً. تُنشأ النسخ في لحظات مختلفة قليلاً، ولذلك قد يصف ملف WAL الذي حفظته إصدارات صفحات لا تتطابق مع الملف الرئيسي الذي حفظته. عندئذ تستعيد SQLite أحد الملفين من الآخر، وتكون النتيجة غير صحيحة. وستكتشف ذلك بعد وقت طويل:

Error: database disk image is malformed

يتجنب .backup هذه المشكلة لأنه يستخدم SQLite Online Backup API، التي توثّقها SQLite باعتبارها الطريقة المناسبة لنسخ قاعدة بيانات قد تكون قيد الاستخدام. يقرأ الصفحات مع قفل قراءة، ويبدأ من جديد إذا غيّر كاتبٌ الملف أثناء العملية. لذلك تكون البيانات المكتوبة على القرص متسقة وتمثل لحظة واحدة.

أنشئ نسخة من قاعدة البيانات باستخدام sqlite3 .backup

sudo apt update && sudo apt install -y sqlite3
sudo install -d -m 700 /var/backups/vaultwarden
OUT=/var/backups/vaultwarden/db-$(date '+%Y%m%d-%H%M').sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 ".backup '$OUT'"
sudo sqlite3 "$OUT" "PRAGMA integrity_check;"

يطبع الأمر الأخير ok في سطر مستقل. وأي نتيجة أخرى تعني أن النسخة غير قابلة للاستخدام، لذلك لا تحتفظ بها ولا تحذف النسخة السابقة. تُنفَّذ العملية كاملة على خادم قيد التشغيل، لذلك لا يتم تسجيل خروج أي مستخدم ولا تُعاد تهيئة أي حاوية.

لا توجد أداة sqlite3 داخل حاوية Vaultwarden. فالصورة مبنية على debian:trixie-slim وتحتوي على ca-certificates وcurl وlibmariadb3 وlibpq5 وopenssl، لذلك يفشل docker exec vaultwarden sqlite3 ... بالرسالة التالية:

exec: "sqlite3": executable file not found in $PATH

نفّذ الأمر على المضيف باستخدام المسار الموصول بدلاً من ذلك، وهذا ما تفعله الأوامر أعلاه. إذا كانت البيانات موجودة في وحدة تخزين مُسمّاة، يطبع docker volume inspect <name> مسار المضيف ضمن /var/lib/docker/volumes/.

أضاف Vaultwarden أيضاً أمر النسخ الاحتياطي الخاص به منذ الإصدار 1.32.1. على خادمك:

docker exec -it vaultwarden /vaultwarden backup

ينفّذ الأمر VACUUM INTO ويكتب db_YYYYMMDD_HHMMSS.sqlite3 داخل مجلد البيانات. وينتج عن ذلك أمران. توضع النسخة بجانب النسخة الأصلية على القرص نفسه، لذلك فهي خطوة مرحلية وليست نسخة احتياطية بعد. كما أن هذا الأمر يعمل مع SQLite فقط؛ أما عند استخدام MariaDB أو PostgreSQL، فيتوقف مع The database type is not SQLite. Backups only works for SQLite databases.

الملفات التي تُنسى عادةً

attachments/ يحتوي على النصوص المشفّرة تحت أسماء مبهمة. يحمل صف قاعدة البيانات الخاص بكل مرفق اسم الملف المشفّر ومواد المفتاح التي يحتاج إليها العميل لفك تشفير الملف. تصبح المرفقات غير المقترنة بقاعدة البيانات بيانات غير قابلة للقراءة، كما أن قاعدة البيانات من دون المرفقات تمنح المستخدمين عناصر تفشل تنزيلاتها. انسخ الاثنين في العملية نفسها.

config.json يحتوي على كل ما حفظته من صفحة الإدارة، وتكون قيمه لها الأولوية على متغيرات البيئة المطابقة. ولهذا الأمر جانبان: تؤدي استعادة ملف config.json قديم إلى تجاوز الإعدادات في ملف compose بهدوء، كما أن الملف نفسه حساس لأنه قد يحتوي على كلمة مرور SMTP ورمز المسؤول. خزّن ذلك الرمز كسلسلة PHC لـArgon2id (مسابقة تجزئة كلمات المرور) بدلاً من تخزينه بنص عادي. يطبع docker run --rm -it vaultwarden/server /vaultwarden hash سلسلة لك.

يوقّع rsa_key.pem رموز الويب JSON (JWT) التي تُبقي العملاء مسجّلي الدخول. إذا كان الملف مفقوداً عند بدء التشغيل، ينشئ Vaultwarden مفتاحاً جديداً، ولذلك تتوقف صلاحية كل رمز وقّعه المفتاح القديم، ويُسجَّل خروج جميع العملاء. تبقى محتويات Vault سليمة، لأنها مشفّرة بمفاتيح مشتقة من كلمة المرور الرئيسية. تؤدي استعادة ملف المفتاح إلى تجنّب تسجيل الخروج الجماعي.

sends/ يحتوي على الملفات الموجودة خلف روابط Send. يؤدي فقدانه إلى تعطيل تنزيلات هذه الملفات، ولا يؤثر في أي شيء آخر.

ضع كل شيء في برنامج نصي واحد

#!/bin/bash
set -euo pipefail

DATA=/opt/vaultwarden/data
DEST=/var/backups/vaultwarden
STAMP=$(date '+%Y%m%d-%H%M%S')
STAGE=$(mktemp -d /tmp/vw-stage.XXXXXX)

install -d -m 700 "$DEST"
sqlite3 "$DATA/db.sqlite3" ".backup '$STAGE/db.sqlite3'"
test "$(sqlite3 "$STAGE/db.sqlite3" 'PRAGMA integrity_check;')" = "ok"
cp -a "$DATA"/rsa_key* "$STAGE/"
for extra in config.json attachments sends; do
  if [ -e "$DATA/$extra" ]; then cp -a "$DATA/$extra" "$STAGE/"; fi
done
tar -C "$STAGE" -czf "$DEST/vw-$STAMP.tar.gz" .
chmod 600 "$DEST/vw-$STAMP.tar.gz"
rm -rf "$STAGE"
tar -tzf "$DEST/vw-$STAMP.tar.gz"

احفظه باسم /usr/local/sbin/vw-backup.sh، ثم chmod 700، وشغّله بصلاحيات root. ينفّذ السطر test عملاً فعلياً: يخرج sqlite3 بحالة 0 حتى عندما يبلّغ PRAGMA integrity_check عن تلف، لذلك فإن مقارنة الناتج مع ok هي ما يحوّل النسخة التالفة إلى فشل في البرنامج النصي. ثم يوقف set -euo pipefail كل شيء، بدلاً من السماح لـtar بإنشاء أرشيف سليم ظاهرياً حول قاعدة بيانات تالفة.

يسرد tar -tzf النهائي ما التقطته فعلياً. اقرأه في المرة الأولى. ابحث عن ./db.sqlite3 و./rsa_key.pem و./config.json و./attachments/، وعن غياب ./db.sqlite3-wal. شغّله كل ليلة باستخدام خدمة ومؤقت systemd بدلاً من cron إذا أردت ناتج journalctl ووحدة تُبلغ عن الفشل.

تحقّق من النسخة الاحتياطية باستعادتها إلى دليل مؤقت

النسخة الاحتياطية التي لم تُختبر مجرد تخمين. تستغرق الاستعادة إلى دليل مؤقت دقيقة واحدة ولا تمس أي بيانات قيد التشغيل.

sudo install -d -m 700 /tmp/vw-check
sudo tar -C /tmp/vw-check -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
ls -l /tmp/vw-check
sudo sqlite3 /tmp/vw-check/db.sqlite3 "PRAGMA integrity_check;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from users;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from ciphers;"
sudo du -sh /tmp/vw-check/attachments

هناك أربع نتائج مهمة. يطبع integrity_check قيمة ok. يطابق عدد المستخدمين عدد الحسابات التي تعرفها. يقترب عدد الشفرات من القيمة الفعلية في النظام الحي التي يعرضها sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;"، ولا يكون صفراً أبداً في مخزن مفاتيح مستخدم. يكون حجم دليل المرفقات قريباً من الحجم المتوقع، ويمكنك تخطي هذا الفحص إذا لم يرفع أحد مرفقات. بعد ذلك شغّل sudo rm -rf /tmp/vw-check، لأن هذا الدليل يحتوي الآن على نسخة ثانية من كل شيء.

هناك قاعدة واحدة عند استعادة أي دليل بيانات نُسخ يدوياً: احذف db.sqlite3-wal وdb.sqlite3-shm قبل بدء الخادم. وإلا فسيحاول SQLite استعادة قاعدة البيانات المستعادة باستخدام سجل ينتمي إلى نسخة أخرى منها، ما يؤدي إلى إتلاف قاعدة بيانات وصلت سليمة. لا تحتوي الأرشيفات التي ينشئها البرنامج النصي أعلاه على هذه الملفات، لأن .backup يكتب قاعدة بيانات كاملة واحدة.

الاستعادة على الخادم

تُنفَّذ هذه الأوامر على خادمك، مع إيقاف الحاوية. يجب ألا يكتب Vaultwarden في مجلد البيانات أثناء تغييره من تحته.

cd /opt/vaultwarden
docker compose stop vaultwarden
sudo mv data data.old.$(date '+%Y%m%d-%H%M%S')
sudo install -d -m 700 data
sudo tar -C data -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
sudo chown -R root:root data
docker compose start vaultwarden
docker compose logs --tail 20 vaultwarden

يجب أن يحدّد chown المستخدم الذي تعمل الحاوية باسمه. تعمل الصورة القياسية باسم root، لذلك تكون root:root صحيحة ما لم تضبط user: في ملف compose. في هذه الحالة، استخدم uid وgid المحددين هناك. إذا تعذر على الخادم الكتابة في مجلد البيانات، فستظهر صفحة تسجيل الدخول لكنها ستفشل في كل طلب، وستوضح السجلات السبب.

تنتهي عملية التشغيل السليمة بسطر Rocket التالي:

[INFO] Rocket has launched from http://0.0.0.0:80

بعد ذلك، سجّل الدخول من متصفح، وافتح عنصراً، ونزّل مرفقاً واحداً. إذا نجح تسجيل الدخول وفشلت تنزيلات المرفقات، فهذا يعني أن الأرشيف احتوى قاعدة البيانات، لكنه لم يحتوِ attachments/. أبقِ data.old.* إلى أن تتحقق من كل ذلك، ثم احذفه. التراجع هو الخطوات الثلاث نفسها، مع تبديل المجلدين في الاتجاه الآخر.

إذا لم تتطابق مساراتك مع المسارات الواردة هنا، يوضّح دليل تثبيت Vaultwarden على VPS ملف compose الذي تفترضه هذه الأوامر.

أين لا تضع النسخة الاحتياطية

  • لا تضعها على القرص نفسه الذي يحتوي على مجلد البيانات. فعطل وحدة تخزين واحدة يتسبب في فقدان النسختين، وينطبق الأمر نفسه على rm -rf واحد في المسار الخطأ.
  • لا تضعها على الخادم نفسه، حتى على وحدة تخزين ثانية. فالمهاجم الذي يصل إلى root يصل إلى نسخك الاحتياطية في الجلسة نفسها.
  • لا تضعها في تخزين الكائنات من دون تشفير، لأن الأرشيف يحتوي على عناوين بريد إلكتروني، وتلميحات كلمات المرور، ورموز الاسترداد، ونصوص vault المشفرة التي يمكن مهاجمتها دون اتصال.
  • لا تعتمد فقط على اللقطات التي يوفّرها مزود الخدمة. فهي تُستعاد بسرعة، وهذا مفيد، لكنها موجودة في الحساب نفسه الذي يوجد فيه الخادم، لذلك تؤثر فيها مشكلة الحساب نفسها.

النسخة الموجودة خارج الموقع هي الحالة التي يناسبها restic، لأن مستودع restic يُشفَّر على الجهاز قبل رفع أي شيء. على خادمك:

sudo apt install -y restic
export RESTIC_REPOSITORY=s3:https://s3.example.com/vaultwarden-backups
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /var/backups/vaultwarden --tag vaultwarden
restic snapshots --tag vaultwarden
restic forget --tag vaultwarden --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

وجّه restic إلى مجلد الأرشيف، وليس إلى مجلد البيانات المباشرة، حتى يرفع النسخة المتسقة التي تحققت منها مسبقاً. احتفظ بكلمة مرور المستودع في مكان آخر غير الخادم الذي تحميه: إذا فقدت كلمة المرور، فلن يمكن قراءة اللقطات، وهذا مقصود. عندما يدعم التخزين ذلك، امنح الخادم بيانات اعتماد تسمح بالكتابة ولا تسمح بالحذف، حتى لا يتمكن اختراق الخادم من محو سجله السابق. يشرح إعداد النسخ الاحتياطية باستخدام restic على VPS المستودع والجدولة بالكامل، بينما يشرح مقارنة restic مع BorgBackup كيفية اختيار الأداة إذا لم تحسم قرارك بعد.

اختبر الاستعادة وفق جدول زمني

حدّد يوماً واحداً كل شهر. اسحب أحدث لقطة إلى مجلد مؤقت باستخدام restic restore latest --tag vaultwarden --target /tmp/vw-check، وشغّل أمر PRAGMA integrity_check نفسه، ونفّذ عمليات عدّ الصفوف نفسها، ثم دوّن التاريخ والأعداد. النسخة الاحتياطية التي لم يستعدها أحد منذ ستة أشهر تكون حالتها غير معروفة. وستعرف حالتها أثناء انقطاع الخدمة، وهو أسوأ وقت لاكتشاف ذلك.

مرة واحدة سنوياً، نفّذ الاختبار الكامل. شغّل حاوية Vaultwarden ثانية على منفذ احتياطي باستخدام مجلد البيانات المستعاد، وسجّل الدخول بحساب فعلي. يثبت ذلك مسار كلمة المرور الرئيسية من البداية إلى النهاية، وهو أمر لا يستطيع أيّ عدّ للصفوف إثباته. يضمن restic check --read-data-subset=10% وفق الجدول الزمني نفسه أن البيانات المخزنة قابلة للقراءة، لا أنها مُدرجة فقط.

FAQ

هل يمكنني نسخ db.sqlite3 باستخدام cp أثناء تشغيل Vaultwarden؟

لا. يشغّل Vaultwarden SQLite في وضع WAL، لذلك تبقى عمليات الكتابة الحديثة في db.sqlite3-wal ولا تكون موجودة بعد في db.sqlite3. يؤدي إجراء cp للملف الرئيسي وحده إلى فقدانها بصمت، وقد يؤدي نسخ الملفين بشكل منفصل إلى إنشاء زوج غير متطابق يظهر لاحقاً على شكل Error: database disk image is malformed. استخدم sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'" بدلاً من ذلك. فهو يستخدم SQLite's Online Backup API وينتج ملفاً متسقاً واحداً بينما يواصل الخادم تقديم الخدمة.

هل يجب أن أوقف حاوية Vaultwarden لإنشاء نسخة احتياطية؟

لا، وهذه هي الفكرة من .backup. يكون نسخ قاعدة البيانات آمناً على خادم قيد التشغيل. تُكتب المرفقات وملفات Send عند رفع المستخدم لأحدها، لذلك قد لا يُدرج ملف أُضيف بين نسخ قاعدة البيانات وtar في أرشيف تلك الليلة، ما قد يؤدي في أسوأ الأحوال إلى فقدان مرفق واحد. إذا لم تكن بضع ثوانٍ من التوقف مشكلة، فإن تنفيذ docker compose stop قبل البرنامج النصي وdocker compose start بعده يلغي حتى هذا الاحتمال.

ماذا يحدث إذا استعدت النسخة من دون ملفات rsa_key؟

ينشئ Vaultwarden مفتاحاً جديداً عند بدء التشغيل. يوقّع هذا المفتاح رموز JSON web tokens (JWT) التي تُبقي الجلسات نشطة، لذلك يتوقف كل رمز حالي عن اجتياز التحقق، ويُسجَّل خروج جميع العملاء ويجب عليهم تسجيل الدخول من جديد. لا تتأثر محتويات الخزائن، لأنها مشفّرة باستخدام مفاتيح مشتقة من كلمة المرور الرئيسية لكل مستخدم، وليس باستخدام مفتاح RSA. استعد rsa_key.pem مع بقية مجلد البيانات، ولن يلاحظ أحد عملية الاستعادة.

هل أرشيف النسخة الاحتياطية آمن للرفع إلى تخزين الكائنات كما هو؟

لا. تكون أسماء العناصر وكلمات المرور والملاحظات على شكل نص مشفّر، لكن عناوين البريد الإلكتروني وأسماء الحسابات وتلميحات كلمات المرور ورموز استرداد المصادقة الثنائية تكون نصاً واضحاً في قاعدة البيانات، ويمكن لمهاجم يعمل دون اتصال تجربة فك النص المشفّر بالسرعة التي يختارها. شفّر الأرشيف قبل مغادرته الجهاز. يتولى مستودع restic ذلك نيابةً عنك، كما ينتج gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz ملفاً مشفراً واحداً يمكنك تسليمه إلى أي وحدة تخزين.

كيف أنشئ نسخة احتياطية من Vaultwarden عند استخدام PostgreSQL أو MariaDB؟

لا تنطبق خطوات SQLite، ويرفض الأمر المضمّن التنفيذ مع The database type is not SQLite. Backups only works for SQLite databases. أفرغ قاعدة البيانات باستخدام أداتها الأصلية، pg_dump أو mysqldump، وطبّق كل قاعدة أخرى كما هي. يجب وضع التفريغ في أرشيف واحد مع attachments/ وsends/ وconfig.json وملفات rsa_key، مع أخذها في التشغيل نفسه، وتشفيرها، وتخزينها في مكان آخر غير الخادم الذي أنشأها.