نسخ Immich احتياطيًا واستعادته على VPS
تعرّف إلى عناصر نسخة Immich الصحيحة، ولماذا لا يكفي نسخ مجلد Postgres، وكيف تمنع خطأ الاستعادة الذي يترك الخط الزمني فارغًا رغم امتلاء القرص.
ما الذي يجب أن تتضمنه نسخة Immich الاحتياطية
تتكون نسخة Immich الاحتياطية من ثلاثة عناصر تُلتقط في اللحظة نفسها. الملفات الأصلية الموجودة ضمن UPLOAD_LOCATION. تفريغ SQL لقاعدة بيانات Postgres. و.env وdocker-compose.yml اللذان يصفان الحزمة. تعني الاستعادة تطبيق هذا التفريغ على قاعدة بيانات جديدة بينما يكون خادم Immich متوقفاً، ثم تشغيل بقية الحزمة بعد ذلك فقط. إذا أخطأت في الترتيب، فسينتهي بك الأمر إلى Immich يعمل ويعرض خطاً زمنياً فارغاً فوق قرص ممتلئ.
هذا الفصل مهم لأن Immich يحتفظ بحالته في مكانين لا توجد بينهما معرفة متبادلة. تحتوي Postgres على كل ألبوم، وكل عنقود وجوه، وكل رابط مشاركة، وكل حساب مستخدم ومفتاح API، والمسار المخزّن لكل ملف. أما نظام الملفات فيحتوي على وحدات البكسل. إذا استعدت الملفات من دون قاعدة البيانات، فلن يعرض Immich أي شيء. وإذا استعدت قاعدة البيانات من دون الملفات، فسيفتح كل ملف صورة معطّلة.
الأوامر هنا مكتوبة بالاستناد إلى Immich v3.1.0، وهو الإصدار الحالي في أوائل August 2026. يصدر المشروع إصدارات بسرعة، وقد تغيّر إجراء النسخ الاحتياطي الموثّق أكثر من مرة، لذلك تحقّق من الإصدار الذي تشغّله فعلياً قبل نسخ أي شيء. إذا لم تكن الحزمة قيد التشغيل بعد، فابدأ بـدليل تثبيت Immich ثم عُد إلى هنا.
اعرف إلى ماذا تشير مساراتك
يحدّد متغيران في .env كل شيء في هذه الصفحة. يشير UPLOAD_LOCATION إلى الدليل الأب الذي يكتب Immich جميع الوسائط داخله. ويشير DB_DATA_LOCATION إلى دليل بيانات Postgres.
يعيّن example.env الافتراضي UPLOAD_LOCATION=./library، وهو إعداد افتراضي مربك، لأن Immich ينشئ بعد ذلك مجلداً باسم library داخله. ستجد ملفاتك الأصلية في ./library/library. عيّن مساراً مطلقاً بدلاً من ذلك، حتى لا يعتمد أي script للنسخ الاحتياطي على الدليل الذي شغّلته منه.
UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0ينشئ Immich داخل UPLOAD_LOCATION عدة مجلدات. تحتوي ثلاثة منها على بيانات لا يمكن لأي job إعادة بنائها:
library: الملفات الأصلية مرتبة وفق قالب التخزين لديكupload: الملفات الأصلية التي لم تُنقل بعد إلى تخطيط القالب، بالإضافة إلى عمليات الرفع الجاريةprofile: صور ملفات المستخدمين الشخصية
إذا فقدت library، فستفقد الصورة. لا يحتفظ Immich بأي نسخة ثانية من الملف الأصلي في أي مكان.
لماذا لا يُعد نسخ دليل بيانات Postgres نسخة احتياطية
يبدو DB_DATA_LOCATION هدفاً سهلاً. فهو دليل، وrsync سينسخه، وينتهي النسخ من دون خطأ. لكنه لا يزال ليس نسخة احتياطية، لسببين يمكنك ملاحظة فشلهما.
السبب الأول هو عدم الاتساق. يكتب Postgres كل تغيير أولاً في سجل الكتابة المسبقة (WAL)، ثم يطبّقه لاحقاً على ملفات الجداول عند نقطة تحقق. لذلك تكون الملفات الموجودة على القرص في حالة انتقال في أي لحظة. إذا استغرق النسخ المتتابع أربع دقائق، فسيقرأ الملف الأول عند 02:00 والملف الأخير عند 02:04. لا ينتمي هذان الملفان إلى المعاملة نفسها. عند بدء Postgres باستخدام الناتج، إما أن يرفض البدء ويعرض PANIC: could not locate a valid checkpoint record، أو يبدأ ثم يتوقف عند أول قراءة لصفحة تالفة ويعرض invalid page in block 1234 of relation base/16384/.... ولا يمكن استعادة أي منهما من تلك النسخة.
يبقى السبب الثاني قائماً حتى إذا أوقفت كل شيء أولاً. يرتبط دليل بيانات Postgres بالملفات التنفيذية المحددة التي أنشأته. يثبّت Immich صورة قاعدة البيانات باستخدام digest، وحالياً هي ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0. هذه هي Postgres 14 مع إضافتي بحث متجهي جرى تجميعهما معها. لن يفتح دليل بيانات أنشأه هذا البناء باستخدام إصدار رئيسي مختلف من Postgres، كما لن يفتح باستخدام بناء يحتوي على إصدارات مختلفة من الإضافات. يجب أن يعيد خادم الاستعادة إنتاج الصورة نفسها تماماً. أما تفريغ SQL فلا يتأثر بذلك: فهو نص، ويمكن لأي خادم متوافق إعادة تشغيله.
يتجاوز pg_dump مشكلة عدم الاتساق مباشرة. فهو يقرأ قاعدة البيانات كاملة داخل لقطة واحدة من MVCC (التحكم في التزامن متعدد الإصدارات)، ولذلك يرى قاعدة البيانات كما كانت في لحظة واحدة، بينما تستمر عمليات الكتابة الأخرى من حوله. لهذا لا تحتاج إلى إيقاف Postgres لإنشاء التفريغ.
ما يمكن استبعاده من النسخة الاحتياطية
تُعاد هذه العناصر تلقائياً، لذلك يمكنك تخطيها:
thumbs: صور المعاينة والصور المصغرةencoded-video: الفيديو المُحوَّلDB_DATA_LOCATION: يُعاد بناؤه من ملف التفريغ- وحدة Docker
model-cache: نماذج تعلّم الآلة، التي تُنزَّل مجدداً عند الطلب
تخطي هذه العناصر مقايضة، وليس فائدة مجانية. تستغرق إعادة بناء الصور المصغرة وعمليات تحويل الفيديو لمكتبة كبيرة ساعات من وقت CPU على VPS صغير، وتعرض الواجهة الزمنية عناصر نائبة رمادية طوال هذه المدة. تعيد تشغيلها من Administration > Jobs، بعد ضبط "Generate Thumbnails" و"Transcode Videos" للتشغيل عند غياب الأصول. إذا كانت مساحة وجهة النسخ الاحتياطي كافية، فأدرجها وتجنب الانتظار. وإذا كنت قريباً من حد التخزين، فاستبعدها وخطط لإعادة البناء. يوضح تحديد حجم مكتبة Immich مدى نمو هذه المجلدات مقارنة بالملفات الأصلية.
هناك مجلد آخر يجدر بك معرفته. يحتوي UPLOAD_LOCATION/backups على ملفات تفريغ قاعدة البيانات التلقائية الخاصة بـImmich، التي تُكتب يومياً عند 02:00 مع الاحتفاظ بآخر 14 ملفاً، ويمكن ضبطها من Administration > Settings > Backup. لا تستهلك هذه الملفات شيئاً من مواردك، وهي مفيدة فعلاً. لكنها توجد على القرص نفسه الذي توجد عليه المكتبة التي تحميها، لذلك تفيد في معالجة عملية ترحيل فاشلة، ولا تفيد عند تعطل الخادم. أنشئ ملف التفريغ الخاص بك على أي حال، لأن ملف التفريغ الذي تبدأه بنفسك يُنشأ في اللحظة نفسها التي تُلتقط فيها لقطة الملفات المرتبطة به.
إنشاء تفريغ قاعدة البيانات
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname=immich --username=postgres \
| gzip > /srv/immich/backup/immich.sql.gzاستبدل immich وpostgres بـDB_DATABASE_NAME وDB_USERNAME إذا غيّرتهما. يضع --clean --if-exists قيمة DROP ... IF EXISTS قبل كل CREATE، لذلك يُعاد تشغيل التفريغ في قاعدة بيانات تحتوي مسبقاً على كائنات بدلاً من التوقف عند أول كائن.
والآن انتبه إلى التفصيل الذي يفسد نصوص النسخ الاحتياطي بصمت. هذا الأمر عبارة عن pipeline، وتعرض shell حالة الخروج للأمر الأخير في الـpipeline. إذا فشل pg_dump بسبب كلمة مرور خاطئة أو لأن الحاوية لا تعمل، يتلقى gzip تدفقاً فارغاً، ويكتب ملف gzip صالحاً تماماً، ثم ينتهي بحالة 0. يسجل النص البرمجي نجاح العملية، وتجد نفسك مع نسخة احتياطية حجمها 20 بايت. ضع pipefail في بداية كل نص برمجي للنسخ الاحتياطي:
#!/usr/bin/env bash
set -euo pipefailثم افحص النتيجة بدلاً من الوثوق برمز الخروج:
ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3يبدأ التفريغ السليم بالسطر -- PostgreSQL database dump. أما الملف الذي حجمه بضع مئات من البايتات فهو تفريغ فاشل، مهما ذكر النص البرمجي.
سجّل الإصدار الذي أنشأه بجانب التفريغ:
docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txtلا تعتمد على .env لهذا الغرض. يضبط الملف الافتراضي IMMICH_VERSION=v3، وهي وسم متحرك يتبع كل إصدار من سلسلة 3.x، لذلك لا يوضح أي إصدار أنشأ التفريغ فعلياً. ثبّت الوسم الدقيق في .env أيضاً.
أوقف الخادم، ثم أنشئ لقطة باستخدام restic
الملفات الموجودة تحت UPLOAD_LOCATION ليست ثابتة أثناء تشغيل Immich. يكتب الخادم تحميلات جديدة، وتنقل مهمة قالب التخزين الملفات بين المجلدات. إذا قرأت أداة النسخ الاحتياطي ملفاً أثناء الكتابة، فستحفظ وحدات البايت التي قرأتها كما لو كانت الملف كاملاً، ولن يُبلّغ أي مكوّن عن خطأ. أوقف حاوية الخادم طوال مدة العملية:
docker stop immich_serverاترك immich_postgres قيد التشغيل، لأن التفريغ يحتاج إليه. ستظل واجهة الويب والتطبيق المحمول غير متاحين إلى أن تبدأ الخادم مجدداً. وهذا يكون مقبولاً عادةً في مثيل منزلي عند الساعة 03:00.
يناسب restic هذه الحالة لأنه يزيل التكرار ويشفّر البيانات قبل مغادرتها الخادم. وجّهه إلى مستودع لا يوجد على هذا الخادم:
export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic initويعمل تخزين الكائنات بالطريقة نفسها. وهو الخيار الأفضل إذا أردت أن تكون النسخة خارج أجهزتك بالكامل:
export RESTIC_REPOSITORY=s3:https://s3.example.com/immich-backup
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
restic initيمكن أن تكون نقطة النهاية هذه حاوية MinIO تديرها بنفسك على جهاز ثانٍ، أو أي موفّر متوافق مع S3. يحميك وجود المستودع على القرص نفسه الذي توجد عليه المكتبة من الحذف غير المقصود، ولا يحميك من أي شيء آخر.
ثم أنشئ اللقطة، مع إدراج ما يهم فقط:
restic backup \
/srv/immich/backup/immich.sql.gz \
/srv/immich/backup/immich-version.txt \
/srv/immich/data/library \
/srv/immich/data/upload \
/srv/immich/data/profile \
/srv/immich/.env \
/srv/immich/docker-compose.yml
docker start immich_serverيقرأ restic الشجرة بأكملها في كل تشغيل، لكنه لا يرفع إلا الكتل التي لم يرها من قبل. لذلك تنقل اللقطة الأولى مكتبتك بالكامل، بينما تنقل كل لقطة لاحقة الصور الجديدة لذلك اليوم.
الاحتفاظ، والمفاتيح التي يجب حفظها في مكان آخر
restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12يحذف forget اللقطات من الفهرس. أما --prune فهو الجزء الذي يحذف البيانات التي كانت تلك اللقطات آخر مرجع إليها. شغّل forget من دون --prune، ولن تنخفض فاتورة التخزين أبداً.
فحوصات البنية منخفضة التكلفة، لذلك شغّل أحدها أسبوعياً:
restic checkيتحقق ذلك من اتساق البيانات الوصفية للمستودع. لكنه لا يقرأ بياناتك. مرة كل شهر، أعد قراءة عينة وقارنها بالتجزئات المسجلة لها:
restic check --read-data-subset=5%هذا هو الفحص الوحيد الذي يكتشف التلف الصامت في الواجهة الخلفية للتخزين، لأنه ينزّل كتل بيانات فعلية ويعيد حساب مجاميعها الاختبارية. إن إجراء --read-data كاملاً على مكتبة صور يعني تنزيل المستودع بأكمله. في التخزين الكائني المحسوب وفق حجم الاستخدام، يترتب على ذلك تكلفة فعلية. لذلك فإن استخدام مجموعة فرعية متغيرة هو الأسلوب العملي الذي يطبقه الناس فعلياً.
نصل الآن إلى الجزء الذي يتجاهله الناس. لا يمكن استرداد كلمة مرور مستودع restic. لا توجد آلية لإعادة تعيينها، ولا يمكن حل المشكلة عبر طلب دعم. إذا كانت النسخة الوحيدة منها موجودة في /root/.restic-password على الخادم الذي تحاول الاستعادة منه، فستكون نسخك الاحتياطية مجرد بيانات مشفّرة غير قابلة للاستخدام. وينطبق الأمر نفسه على مفتاح الوصول إلى التخزين الكائني وعلى DB_PASSWORD من .env. احتفظ بها كلها في مكان لا يعتمد على بقاء هذا الجهاز قيد التشغيل: اطبعها وضعها في درج، أو خزّنها في مدير كلمات مرور يعمل على أجهزة مختلفة. إذا كان مدير كلمات المرور مستضافاً ذاتياً أيضاً، فيجب التعامل معه بالطريقة نفسها، كما أن نسخ Vaultwarden احتياطياً مهمة مستقلة.
استعادة Immich بالترتيب الصحيح
ترتيب الاستعادة هو ما يحوّل النسخ الاحتياطية السليمة إلى جداول زمنية فارغة. اتبع هذا التسلسل على المضيف الجديد.
استعد ملف الإعداد أولاً. فهو يحدد الإصدار الذي يجب تشغيله، والمسارات التي تشير إليها الإعدادات.
restic restore latest --target /restore \
--include /srv/immich/.env \
--include /srv/immich/docker-compose.yml \
--include /srv/immich/backupثبّت الإصدار قبل بدء أي شيء. اقرأ immich-version.txt، واضبط IMMICH_VERSION في .env على الوسم نفسه تماماً، واترك أحدث إصدار جانباً مؤقتاً. لا يدعم Immich الرجوع إلى إصدار أقدم، حتى بين إصدارات التصحيح، لذلك إذا بدأ خادم أحدث باستخدام dump أقدم ونفّذ عمليات الترحيل، فلن توجد طريقة للعودة.
استعد ملفات الوسائط.
restic restore latest --target /restore --include /srv/immich/dataثم انقل library وupload وprofile بحيث تصبح مباشرة داخل المسار الذي يشير إليه UPLOAD_LOCATION على هذا المضيف. يمكن أن يتغير مسار المضيف نفسه، لأن ملف compose يربط ذلك الدليل بمسار ثابت داخل الحاوية. أما البنية الداخلية لذلك الدليل فلا يجوز أن تتغير.
ابدأ قاعدة البيانات وحدها. اترك DB_DATA_LOCATION فارغاً لكي يهيّئ Postgres عنقوداً جديداً.
cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgresيطبع pg_isready القيمة accepting connections بعد اكتمال الإعداد الأولي، ويستغرق ذلك بضع ثوانٍ. ينشئ docker compose create جميع الحاويات من دون تشغيلها، وهذا هو الهدف من هذه الخطوة: يجب ألا يعمل خادم Immich بعد. إذا بدأ الخادم باستخدام قاعدة بيانات فارغة، فسوف ينفّذ عمليات الترحيل، وينشئ مخططاً جديداً، ويطلب منك إنشاء حساب مسؤول جديد. عندها ستعيد تطبيق dump تحت تطبيق قيد التشغيل.
أعد تطبيق dump.
gunzip --stdout /restore/srv/immich/backup/immich.sql.gz \
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| docker exec -i immich_postgres psql --dbname=immich --username=postgres \
--single-transaction --set ON_ERROR_STOP=onهناك جزءان من هذه العملية ينفذان عملاً فعلياً. يوجد sed لأن pg_dump يكتب search_path فارغاً في مخرجاته كإجراء أمان، ولذلك لا يمكن للأسماء غير المؤهلة في dump أن تشير إلى مخطط غير متوقع. توجد أنواع البحث المتجهي في Immich داخل public، لذلك عندما يكون مسار البحث فارغاً، تصل الاستعادة إلى أول عمود معلن باستخدام نوع متجهي، ثم يتوقف psql مع ERROR: type "vector" does not exist. تؤدي إعادة public إلى مسار البحث إلى حل المشكلة.
يضع --single-transaction --set ON_ERROR_STOP=on عملية الاستعادة بأكملها داخل معاملة واحدة تتوقف عند أول خطأ. وستحصل إما على قاعدة بيانات كاملة أو على قاعدة بيانات لم تتغير. من دونه، يؤدي الفشل في منتصف العملية إلى ترك قاعدة بيانات تبدأ وتقبل تسجيل دخولك، لكنها تفتقد عدداً غير معروف من الألبومات، ولن تكتشف ذلك إلا بعد أسابيع.
ابدأ الآن كل المكونات.
docker compose up -d
docker compose ps
docker logs -f immich_serverانتظر ظهور سطر بدء تشغيل مثل Immich Server is listening on، ثم افتح المنفذ 2283 وسجّل الدخول باستخدام بيانات الاعتماد القديمة، لأن حسابات المستخدمين عادت مع dump. إذا عرضت صفحة تسجيل الدخول بدلاً من ذلك خيار إنشاء حساب المسؤول الأول، فهذا يعني أن قاعدة البيانات لم تُستعد. أوقف العملية واقرأ مخرجات psql مرة أخرى.
هناك تحذير بشأن تعليمات الاستعادة الرسمية، التي تبدأ بـ docker compose down -v. يحذف -v وحدات التخزين المسماة. في ملف compose الأصلي، يكون UPLOAD_LOCATION وDB_DATA_LOCATION عبارة عن bind mounts، ولذلك تبقى بعد تنفيذ الأمر. إذا غيّرت أيّاً منهما إلى وحدة تخزين مسماة، فسيحذف ذلك الأمر صورك. اقرأ ملف compose قبل كتابة الأمر.
لماذا يكون الخط الزمني فارغاً بعد الاستعادة
يُنشأ الخط الزمني من صفوف قاعدة البيانات. لا يفحص Immich المسار upload/ عند الإقلاع للعثور على الصور من جديد، لأن الملف الذي لا يملك صفاً لا يملك مالكاً أو تاريخاً أو ألبوماً. لذلك تكون الاستعادة غير الصحيحة الأكثر شيوعاً هي استعادة الملفات مع فقدان قاعدة البيانات. يبدأ Immich، وينشئ مخططاً فارغاً، ويمنحك نسخة تعمل ولا تحتوي على أي بيانات، بينما القرص ممتلئ بصورك. لا تكون البيانات مفقودة. لكنها لا تكون مرئية أيضاً. الحل هو إعادة تشغيل ملف التفريغ مع إيقاف الخادم، تماماً كما سبق.
الإصدار الثاني من المشكلة أقل وضوحاً. تُستعاد قاعدة البيانات، ويمتلئ الخط الزمني بالإدخالات، لكن يفشل فتح كل عنصر. هذا يعني أن الصفوف تشير إلى ملفات لا يستطيع الحاوي رؤيتها، ويحدث ذلك عادةً لأن library وupload وprofile أصبحت أعمق بمستوى واحد بعد تنفيذ restic restore --target /restore دون نقلها إلى المكان الصحيح. تحقّق من داخل الحاوي بدلاً من التخمين:
docker exec immich_server ls /dataيُثبّت ملف compose القياسي UPLOAD_LOCATION في المسار /data، لذلك يجب أن يعرض ذلك الأمر library وupload وprofile. إذا عرض دليلاً فارغاً أو مجلداً زائداً باسم srv، فإن bind mount يشير إلى المستوى الخطأ، بينما تكون الصفوف سليمة.
التطابق بين الإصدارين عند النسخ الاحتياطي والاستعادة
تُصدر Immich إصدارات جديدة باستمرار، ويتغير المخطط معها، لذلك يحتوي التفريغ على مخطط الخادم الذي أنشأه.
تعمل استعادة تفريغ قديم إلى خادم أحدث عادةً، لأن الخادم يطبّق عمليات الترحيل المعلّقة عند بدء التشغيل ويدفع المخطط إلى الأمام. ويُختبر هذا المسار عبر تسلسل الإصدارات. لكن تخطي عدة إصدارات رئيسية في خطوة واحدة يسبب المشكلات، إذ يحصر المشروع التغييرات غير المتوافقة في الإصدارات الرئيسية ويوثقها في سجل التغييرات.
لا تعمل استعادة تفريغ أحدث إلى خادم أقدم على الإطلاق. يحتوي التفريغ على جداول وأعمدة لا تعرفها الشفرة الأقدم، وتوضح Immich أن الرجوع إلى إصدار أقدم غير مدعوم حتى بين إصدارات التصحيح. ولا يوجد أمر rollback يمكن استخدامه.
لذلك تكون عملية الاستعادة الآمنة روتينية. شغّل الإصدار المطابق تماماً للإصدار الذي أنشأ التفريغ، واستعده، وسجّل الدخول، وتأكد من اكتمال الخط الزمني، ثم أجرِ الترقية بعد ذلك فقط. رقِّ إصداراً واحداً في كل مرة، مع تحديث IMMICH_VERSION وتشغيل docker compose pull && docker compose up -d بعد كل تحديث. ويساعد الاحتفاظ بتفريغات لمدة أسبوع هنا أيضاً: فإذا تبيّن أن أحدث تفريغ أُنشئ أثناء ترقية فاشلة، فسيظل تفريغ الأمس موجوداً في المستودع.
تحقّق من النسخة الاحتياطية كل شهر
النسخة الاحتياطية التي لم تستعدها قط ليست سوى تخمين. مرة واحدة كل شهر، استعدها في مثيل مؤقت وتحقّق من صورة. تستغرق العملية نحو عشرين دقيقة، وهي الشيء الوحيد الذي يحوّل بقية هذه الصفحة إلى خطة استرداد.
restic snapshots
restic stats latestيجب أن يعرض snapshots عملية التشغيل التي نُفِّذت الليلة الماضية. ويجب أن يعرض stats latest حجماً قريباً من حجم مكتبتك، لا بضعة ميغابايت.
استعد النسخة في دليل مؤقت، ويفضّل أن يكون ذلك على مضيف احتياطي:
restic restore latest --target /tmp/immich-drillانسخ docker-compose.yml و.env من المجموعة المستعادة، ثم غيّر ثلاثة أشياء في النسخة. وجّه UPLOAD_LOCATION وDB_DATA_LOCATION إلى دليلين ضمن /tmp/immich-drill. انشر منفذ الويب في مكان آخر، باستخدام 12283:2283 بدلاً من 2283:2283. احذف أسطر container_name:، لأن ملف compose الأساسي يثبّت أسماء مثل immich_server، ما يسبب تعارضاً بين مكدسين على المضيف نفسه، ويرفض Docker إنشاء المكدس الثاني.
نفّذ تسلسل الاستعادة المذكور أعلاه: قاعدة البيانات فقط، ثم أعد تشغيل ملف التفريغ، ثم docker compose up -d. بعد ذلك، نفّذ الفحوصات الأربعة التي تثبت نجاح العملية.
- سجّل الدخول باستخدام كلمة المرور التي كنت تستخدمها قبل الاختبار. يعني عمل الحسابات أن ملف التفريغ استُعيد بنجاح.
- افتح المخطط الزمني وانتقل إلى أقدم شهر. يعني وجود الأصول عبر النطاق الزمني الكامل استعادة جميع الصفوف، لا الصفوف الحديثة فقط.
- افتح صورة واحدة بالحجم الكامل ونزّل النسخة الأصلية.
- قارنها بالملف نفسه في مكتبتك الفعلية باستخدام
sha256sum. تعني مطابقة التجزئات بقاء وحدات البايت بعد مرورها ذهاباً وإياباً عبر restic.
بعد ذلك، أوقف بيئة الاختبار واحذفها باستخدام docker compose down -v في دليل الاختبار، ثم احذف /tmp/immich-drill. دوّن التاريخ في مكان ستراه، لأن قيمة هذه العملية تعتمد بالكامل على تكرارها في الشهر المقبل. إذا كنت لا تزال تختار خادم الصور الذي ستعتمده، فتعرض مقارنة PhotoPrism وImmich أوجه اختلافهما تحديداً في هذه النقطة.
FAQ
هل يجب أن أوقف Immich لإجراء نسخة احتياطية منه؟
أوقف immich_server واترك immich_postgres قيد التشغيل. لا تحتاج قاعدة البيانات إلى إيقاف مؤقت، لأن pg_dump يقرأ ضمن لقطة MVCC واحدة ويرى لحظة متسقة واحدة بغض النظر عما تكتبه العمليات الأخرى. الملفات هي سبب الإيقاف: يكتب الخادم التحميلات الجديدة، وتنقل مهمة قالب التخزين الملفات بين المجلدات، لذلك قد تقرأ أداة النسخ الاحتياطي ملفاً أثناء الكتابة إليه وتخزن نسخة مبتورة من دون أي خطأ. يؤدي تشغيل docker stop immich_server قبل أخذ اللقطة وdocker start immich_server بعدها إلى إزالة هذا التزامن المتسابق.
هل يمكنني نسخ مجلد بيانات Postgres بدلاً من تشغيل pg_dump؟
لا. يقرأ النسخ المتتابع لمجلد بيانات قيد الاستخدام ملفات مختلفة في لحظات مختلفة، لذلك لا تمثل النتيجة حالة متسقة واحدة، ويرفضها Postgres عند بدء التشغيل مع PANIC: could not locate a valid checkpoint record أو يفشل لاحقاً بسبب صفحة تالفة. حتى النسخة التي تُؤخذ بعد إيقاف كل شيء تكون مرتبطة ببنية قاعدة البيانات المحددة تماماً: يثبّت Immich صورة Postgres 14 مع إصدارات محددة من امتدادات البحث المتجهي، ولن يُفتح المجلد باستخدام أي شيء آخر. تفريغ SQL نص عادي، ويمكن استعادته إلى أي خادم متوافق.
لماذا أصبح المخطط الزمني في Immich فارغاً بعد الاستعادة؟
لأن المخطط الزمني يُبنى من صفوف قاعدة البيانات، وقد استعدت الملفات من دون قاعدة البيانات. لا يفحص Immich أبداً upload/ لاكتشاف الصور من جديد، لذلك تظل الملفات التي لا تقابلها صفوف غير ظاهرة. الصور نفسها لم تتأثر. أوقف الخادم، واستعد التفريغ إلى Postgres تمت تهيئته حديثاً، ثم شغّل المكدس. أما إذا كان المخطط الزمني ممتلئاً لكن فشل فتح كل صورة، فالمشكلة معكوسة: library وupload وprofile ليست موجودة مباشرة داخل المجلد المرتبط بالحاوية. تحقّق من ذلك باستخدام docker exec immich_server ls /data.
ما مجلدات Immich التي يمكنني استبعادها من النسخة الاحتياطية؟
يُعاد إنشاء thumbs وencoded-video من الملفات الأصلية، ويُعاد بناء DB_DATA_LOCATION من التفريغ، لذلك لا يلزم إدراج أي منها في مجموعة النسخ الاحتياطية. يؤدي استبعادها إلى استهلاك الوقت بعد الاستعادة بدلاً من استهلاك مساحة التخزين قبلها، لأن إعادة بناء المعاينات وعمليات تحويل الترميز لمكتبة كبيرة تستهلك ساعات من CPU، وتُشغَّل من Administration > Jobs لمعالجة الأصول المفقودة. ما لا يمكنك استبعاده مطلقاً هو library وupload وprofile، لأنها تحتوي على النسخة الوحيدة من كل ملف أصلي.
هل يمكنني استعادة تفريغ Immich إلى إصدار أحدث؟
عادةً نعم، لأن الخادم يطبّق عمليات الترحيل المعلّقة عند بدء التشغيل وينقل المخطط إلى الأمام. أما العكس فيفشل: لا يدعم Immich الرجوع إلى إصدار أقدم، حتى بين إصدارات التصحيح، لذلك لا يمكن تحميل تفريغ من إصدار أحدث إلى خادم أقدم. نفّذ الاستعادة مع تثبيت IMMICH_VERSION على الإصدار الذي كتب التفريغ، وتأكد من اكتمال المخطط الزمني، ثم أجرِ الترقية بعد ذلك. سجّل الإصدار بجانب كل تفريغ باستخدام docker inspect --format '{{.Config.Image}}' immich_server، لأن IMMICH_VERSION=v3 الافتراضي وسم متغير لا يقدم لك أي معلومة.