في إحدى الأمسيات، وبينما كنتُ أنا وشريكي نستعد لتجربة تثبيت توزيعة Linux ISO جديدة حظيت بثناء فريق Red Letter Systems وتقييمات ممتازة، فوجئنا بعطل مفاجئ في خادمنا المنزلي. حاولتُ الوصول إلى الخادم عبر تطبيق Tremotesf على هاتفي، لكن قائمة التورنت لم تظهر، ولم أتمكن من تحميل مشاركة SMB، ولا حتى الاتصال عبر SSH.
لم أكن أرغب في إفساد الأمسية ببدء جلسة تصحيح أخطاء مطولة، فافترضت أن المشكلة بسيطة وتعود إلى تغييرات حديثة في إعدادات NixOS، وقررت تأجيل الفحص لليوم التالي.
في اليوم التالي
في اليوم التالي، ومع استعداد كامل لمواجهة المشكلة، قمت بتوصيل Raspberry Pi بشاشة تصحيح الأخطاء المخصصة وأعدت تشغيل الجهاز.
بدأت نسخة NixOS الحالية في الإقلاع، وتم تحميل النواة… ثم توقف كل شيء. لم يظهر أي مخرج إضافي. وعند تجربة الإصدار السابق، حدث الشيء نفسه. عندها، أخرجت بطاقة microSD لإجراء فحص أعمق.
الخطوة الأولى البديهية كانت استخدام أداة fsck. فقد حدث انقطاع غامض للتيار الكهربائي في المبنى قبل أيام قليلة، وربما تزامن ذلك مع عملية كتابة لبعض الملفات الحيوية لعملية الإقلاع. أبلغت fsck.ext4 عن العديد من الأخطاء وقامت بإصلاحها. وعند تشغيلها مرة أخرى، أكدت نظافة نظام الملفات (FS). ولكن بمجرد إزالة البطاقة وإعادة توصيلها، ظهرت المزيد من الأخطاء…
باختصار، كانت البطاقة على الأرجح قد وصلت إلى نهايتها، والانقطاع الكهربائي لم يكن سوى الضربة القاضية. لقد حان وقت توديعها.
تقييم الوضع
لم يكن تعطل Raspberry Pi 4B مفاجئًا تمامًا، فقد كان يعمل لسنوات متواصلة على مدار الساعة طوال أيام الأسبوع. أدرك أن بطاقات SD لديها عدد محدود نسبيًا من دورات الكتابة قبل أن تبدأ في الفشل. المفاجأة كانت في قدرة بطاقتي على الصمود كل هذه الفترة، خاصة مع استخدامها كنظام ملفات جذري (root FS) دون اتخاذ أي احتياطات لتقليل عمليات الكتابة.
لحسن الحظ، لم أتعرض لخسارة كبيرة. كانت معظم البيانات المهمة مخزنة على أقراص صلبة خارجية، وكان تكوين NixOS الخاص بي يحوي إعداداتي بالكامل تقريبًا. الملفات الوحيدة التي كانت على بطاقة microSD ولم يكن لدي نسخة احتياطية منها هي ملفات .torrent وذاكرة التخزين المؤقت لخدمات Navidrome وJellyfin وslskd، والتي يمكن استعادتها أو تجديدها بسهولة. أما بيانات Immich، فقد كانت محمية بنسخة احتياطية آمنة على قرص خارجي، وكنتُ أقوم بعمل نسخ احتياطية منتظمة، لذا لم أفقد شيئًا منها.
لم يسبق لي أن فقدت محرك أقراص رئيسيًا بهذه الطريقة؛ كانت أسوأ تجاربي تقتصر على عدد قليل من محركات أقراص USB الميتة التي لا تحتوي على بيانات حيوية. لطالما كان هاجس تعطل الأقراص وفقدان البيانات يراودني منذ أن كنت مراهقًا، خاصة بعد أن قمت عن طريق الخطأ بمسح قسم OS X الرئيسي على جهاز MacBook الخاص بي أثناء محاولتي الأولى لتثبيت Linux. ولكن الآن، أصبحت الحاجة إلى احتياطات قوية أكثر إلحاحًا من أي وقت مضى.
تكوين الإعداد الجديد
شرعت في إعادة بناء الخادم المنزلي مع وضع بعض الأهداف في الاعتبار:
- تقليل عمليات الكتابة على بطاقة microSD.
- توفير تكرار للبيانات المخزنة على الأقراص الصلبة الخارجية.
- عمل نسخ احتياطية لكل شيء مهم.
التبديل، /tmp، والبيانات المتغيرة الأخرى
من المفيد توفر مساحة تبديل (Swap)، حتى مع وجود 8 جيجابايت من ذاكرة الوصول العشوائي (RAM) على جهاز Raspberry Pi الخاص بي. لكنني لا أرى أن استخدام بطاقة microSD للتبديل فكرة جيدة لضمان عمر طويل للبطاقة. الخيارات المتاحة كانت إما التبديل إلى قرص صلب خارجي (وهو بطيء جدًا) أو استخدام Zram.
Zram، التي كانت تُعرف سابقًا باسم Compcache، هي وحدة نواة Linux لإنشاء جهاز كتلة مضغوط في ذاكرة الوصول العشوائي (RAM)، أي قرص ذاكرة الوصول العشوائي مع ضغط البيانات الفوري.
الاستخدامات الشائعة لـ Zram تشمل مساحة التبديل (Swap) أو المجلد /tmp. لقد اخترت تمكين Zram لمساحة التبديل وليس للمجلد /tmp، والذي أبقيته كقرص RAM عادي. لست متأكدًا بنسبة 100% من كيفية تفاعل هاتين الميزتين، لكنني فضلت أن أترك النواة تحدد كيفية ومتى يتم تبديل الصفحات غير النشطة من /tmp إلى مساحة التبديل المضغوطة بدلاً من حجز كتلة منفصلة لها.
{
# تمكين جهاز المبادلة المضغوطة في الذاكرة
com.zramSwap.يُمكَِن =صحيح؛
# استخدم قرص RAM لـ /tmp
التمهيد.com.tmp.useTmpfs =صحيح؛
}
كما اخترت تخزين سجلات Journald في الذاكرة بدلاً من القرص. أحتاج إلى استكشاف ما إذا كان من الممكن تسجيل الدخول إلى الذاكرة ثم تدفقها إلى القرص بشكل دوري فقط (وفي هذه الحالة، سأقوم بتركيب /var/log إلى مجلد فرعي على القرص الصلب)، لكنني لم أتوصل إلى حل لهذا الجزء بعد.
{
# قم بتخزين سجلات اليومية في الذاكرة إلى /run/log/journal
خدمات.جورنالد.تخزين = volatile؛
}
تبقى بطاقة microSD مثبتة على المسار الجذري /، لكنني قمت بتعطيل خيار noatime لأنه يتسبب في عمليات كتابة عند كل وصول إلى ملف، ولا أرى فائدة كبيرة منه.
{
أنظمة الملفات./. = {
جهاز = /dev/disk/by-label/takodachi؛
fsType = ext4؛
خيارات = [[noatime ]؛
}؛
}
الأقراص الصلبة الخارجية
إذا كنتَ تواكب الأحداث التقنية، فمن المرجح أنك تدرك أن عام 2026 ليس الوقت المثالي لشراء أقراص صلبة جديدة (أو ذاكرة وصول عشوائي، أو وحدات معالجة رسومية، أو أي مكونات تُستخدم لبناء مراكز البيانات وتغذية جيوب المديرين التنفيذيين الذين يعانون من هوس الذكاء الاصطناعي). لذا، من الأفضل أن تكون مبدعًا وتستفيد مما هو متوفر لديك.
لدي مجموعة كبيرة من الأقراص الصلبة بمختلف الأنواع والأحجام والأعمار، أتعامل معها غالبًا كمخزن بارد للبيانات القديمة التي أرغب نوعًا ما في الاحتفاظ بها؛ إنها بمثابة درج الفوضى الرقمي. لطالما أردت استغلالها لغرض أسمى، ولم أجد هذا الغرض بعد. من هذه الكومة، اخترت قرصين صلبين قديمين بحجم 2.5 بوصة.
القرص الأول كان بسعة 500 جيجابايت ويحوي نظام ملفات Windows. كان يستخدم في حاسوب شريكي المحمول؛ وهو جهاز ضخم بوزن 3 كيلوجرامات يعود إلى حقبة سابقة من حواسيب الأعمال، مصمم ليكون أشبه بجهاز مكتبي يمكن نقله بسهولة إلى مكتب آخر أو إحضاره إلى المنزل أحيانًا، بدلاً من كونه جهازًا محمولًا بحق. في البداية، استبدلتُ القرص الصلب (HDD) بقرص صلب ذو حالة ثابتة (SSD) كعملية إنقاذ للحاسوب المحمول الذي كان يحتضر. وفي النهاية، قمت بتزويد شريكي بجهاز Surface Pro 5 يعمل بنظام NixOS، وقد تفضلت بمنحي الحاسوب المحمول القديم والقرص الصلب للعب بهما.
أما القرص الثاني، فكان بسعة 320 جيجابايت ويبدو أنه يحتوي على… تثبيت لمساعد منزلي؟ ليس لدي أدنى فكرة كيف انتهى به المطاف هناك. على أي حال، كان هذا القرص من نوع Hitachi وعليه شعار أبل، مما يعني أنه كان في الأصل داخل جهاز MacBook البلاستيكي الأبيض الخاص بي من عام 2006 أو جهاز Mac mini من نفس الفترة تقريبًا.
قمت بتوصيل هذين القرصين التاريخيين بمحطة إرساء (docking station) وأنشأت مجموعة btrfs مع تكرار RAID1. وقد أطلقت عليها اسمًا مناسبًا: Ponkotsu (والتي تعني باليابانية قطعة خردة).
sudo mkfs.btrfs --data raid1 --metadata raid1 --label ponkotsu /dev/sdX /dev/sdY
اخترت نظام ملفات btrfs لأنه مألوف لي (يعمل على جهازي المكتبي والمحمول)، وأحب ميزة المجلدات الفرعية (subvolumes) التي سأتناولها لاحقًا، وكذلك مرونة مجموعاته. لست مضطرًا لتخطيط بنية المجموعة مسبقًا، يمكنك إضافة محركات أقراص إليها متى شئت، ولا يشترط أن تكون بنفس الحجم.
المجلدات الفرعية
كانت خطتي تتلخص في تخصيص مجلد فرعي (btrfs subvolume) واحد لكل خدمة، ولتحقيق ذلك بشكل تعريفي، قمت بإنشاء وحدة autosubvol. يبدو التكوين الخاص بها كالتالي:
{
خدمات.com.autosubvol = {
يُمكَِن =صحيح؛
الأقراص.بونكوتسو = {
جهاز = /dev/disk/by-uuid/ ؛
مجلدات فرعية.immich = {
com.mountPoint = /var/lib/immich؛
mountOptions = [[noatime ]؛
مطلوب بواسطة = [[immich-server.service ]؛
}؛
}؛
}؛
}
تقوم الوحدة بإنشاء بعض وحدات systemd:
- واحد
.mountوحدة لكل قرص في/run/btrfs-roots/. - واحد
autosubvol-ensure-وحدة oneshot تحتوي على برنامج نصي يتحقق من وجود المجلد الفرعي، وإذا لم يكن موجودًا، يقوم بإنشائه. تم تكوينه مع- .service RemainAfterExit=trueبحيث.mountوحدة الحجم الفرعي يمكن أن تعتمد عليه. - واحد
.mountوحدة لكل مجلد فرعي، مثبتة على المحددmountPoint. ذلك يعتمد على ضمان autosubvol.serviceومجموعاتBefore=وRequiredBy=على الوحدات هوrequiredBy.
أعتقد أن هذا يجعل كل وحدة خدمة تبدو منظمة ونظيفة للغاية.
{
التكوين,
...
}:
{
خدمات.immich = {
يُمكَِن =صحيح؛
#...
}؛
خدمات.com.autosubvol.الأقراص.بونكوتسو.مجلدات فرعية.immich = {
com.mountPoint = التكوين.خدمات.immich.mediaLocation؛
mountOptions = [[noatime ]؛
مطلوب بواسطة = [[immich-server.service ]؛
}؛
}
كما أنشأت مجلدًا فرعيًا خاصًا بـ /var/cache لتعزيز التنظيم.
{
أنظمة الملفات./var/cache = {
جهاز = /dev/disk/by-uuid/...؛
fsType = btrfs؛
خيارات = [[
subvol=@cache
noatime
nofail
]
}؛
}
النسخ الاحتياطية
عند إعداد Immich، قمت بإنشاء وحدة Nix تعمل كجسر بين sops-nix و Restickgs. لا يتجاوز التنفيذ بضعة أسطر من التعليمات البرمجية، ويظهر في تكوين Nix الخاص بي كالتالي:
{
داخلي.النسخ الاحتياطية.ريستيك.immich = {
المسارات = [[/var/lib/immich ]؛
استبعاد = [[
/var/lib/immich/encoded-video
/var/lib/immich/thumbs
]
PruneOpts = [[
--keep-daily 7
--keep-weekly 4
--keep-monthly 3
]
}؛
}
بما أن كل خدمة تقع في مجلد فرعي خاص بها، فمن المحتمل أن أقوم بإعداد نظام نسخ احتياطي محلي باستخدام اللقطات (snapshots) وأداة btrfs send، مع تخصيص قرص كامل للنسخ الاحتياطية سيبقى غير متصل بالشبكة معظم الوقت. ولكن نظرًا لقلة الأقراص المتوفرة حاليًا وصعوبة الحصول على أقراص جديدة، سأؤجل هذه الخطوة لأوقات أفضل. في الوقت الراهن، سأعتمد على سلة S3 والنسخ المتماثل.
المجموعات والأذونات
أرغب في أن تتمكن العديد من الخدمات، مثل Transmission و Jellyfin و Navidrome وعدد قليل من غيرها، من الوصول إلى ملفات Linux ISO الخاصة بي. لقد عالجت هذه المسألة بإنشاء مجموعة باسم media وتعيينها كمجموعة إضافية لجميع التطبيقات التي تحتاج إلى قراءة هذه الملفات أو الكتابة فيها.
{
المستخدمين.المجموعات.وسائط = {}؛
المستخدمين.المستخدمين.steenuil.extraGroups = [[media ]؛
المستخدمين.المستخدمين.جيليفين.extraGroups = [[media ]؛
خدمات.الانتقال = {
مجموعة = media؛
downloadDirPermissions = 2775؛
إعدادات.umask = 002؛
}؛
}
الجزء الأساسي الذي يجعل هذا يعمل هو تعيين downloadDirPermissions لـ Transmission إلى 2775. الرقم 2 يشير إلى تفعيل بت setgid، مما يعني أنه إذا كان المجلد /srv/linux-isos مملوكًا لمجموعة media، فإن كل ملف ودليل يتم إنشاؤه بداخله سيرث مجموعة media بدلاً من المجموعة الأساسية للمنشئ.
المراقبة
هذا جزء لم أتقنه بالكامل بعد. الاحتياط الوحيد الذي اتخذته هو تشغيل أدوات btrfs بانتظام على المجموعة للكشف عن أي أخطاء وتحديد ما إذا كان يتوجب عليّ فحص إحصائيات SMART بشكل أدق بناءً على عدد الأخطاء في كل قرص.
أرغب في إعداد أدوات SMART (مثل smartmontools) وربما ربطها بلوحة تحكم جذابة مثل Scrutiny. ولكن نظرًا لأنني أدير العديد من الخدمات الأخرى ولا أمانع في الحصول على المزيد من الإحصائيات عنها، فقد يكون من المفيد إعداد Prometheus ثم اكتشاف كيفية عرض كل شيء على لوحة تحكم Grafana؟ لا أعرف شيئًا تقريبًا عن Grafana، لذا يبدو الأمر كله شاقًا ومبالغًا فيه بعض الشيء بالنسبة لي، لكنها قد تكون تجربة تعليمية جيدة.
تغذية المجموعة
لنعد إلى الموضوع الأصلي. كانت المجموعة تحتوي على قرصين. أردت إضافة قرص صلب بسعة 1 تيرابايت إلى المجموعة ونسخ محتوياته إليه أيضًا. كان القرص مقسمًا بالفعل إلى نصفين متساويين وكانت معظم البيانات في النصف الثاني، لذلك قمت بتنسيق النصف الأول كـ btrfs، ثم أضفته إلى المجموعة، ونسخت النصف الثاني إلى المجموعة، ثم قمت بتوسيع قسم btrfs ليملأ القرص بالكامل. لست متأكدًا من مدى فعالية هذه الطريقة من حيث السرعة، لكنها جنبتني الحاجة إلى إجراء btrfs balance.
لتجنيب Raspberry Pi بعض المشاكل، قمت بإعداد Immich على سطح المكتب واستعدت البيانات من النسخة الاحتياطية هناك. وبما أنني تجنبت نقل الصور المصغرة في الخطوات السابقة، فقد قمت بإعادة إنشائها كلها بصيغة webp.
هذه هي الحالة الراهنة للمجموعة: قرص بسعة 1 تيرابايت، وقرص بسعة 500 جيجابايت، وقرص بسعة 320 جيجابايت؛ مع تكرار RAID1 لكل من البيانات والبيانات الوصفية. أخطط لإضافة قرص قديم آخر بسعة 500 جيجابايت إليها، ومن المحتمل أن أبقيها على هذا النحو حتى تنخفض أسعار محركات الأقراص أو يتعطل أحدها.
لدي أيضًا قرص بسعة 10 تيرابايت لا يزال مهيأً بنظام ext4 وممتلئًا بالكامل بتوزيعات Linux ISO التي لا يمكنني إضافتها بشكل منطقي إلى المجموعة. لذلك، سأقوم بنسخ احتياطي لكل ما أعتبره مهمًا منه، وسأقوم بتركيبه حتى يتعطل. (YOLO).
بطاقة microSD الجديدة
بما أنني سأقلل بشكل كبير من عمليات الكتابة على بطاقة microSD، فقد اخترت بطاقة بسعة 64 جيجابايت بدلاً من 128 جيجابايت التي كنت أستخدمها سابقًا. بحثت عن بطاقة متينة للغاية (high endurance) لأنه يُفترض أن تحتوي على عدد أكبر من دورات الكتابة قبل أن تتعطل.
لست متأكدًا تمامًا مما حصلت عليه في النهاية. فقد حدثت هذه التجربة كلها قبل أيام قليلة من الإجازة التي أكتب فيها هذا المنشور، لذلك طلبت بطاقة كان من المفترض أن تصل يوم السبت، وهو اليوم السابق لمغادرتي. جاء صباح السبت ولم يتم شحن الطرد بعد، فاضطررت إلى إلغاء الطلب والتوجه إلى أقرب متجر للأجهزة المنزلية.
لم تكن التجربة سيئة للغاية. قمت بنزهة لطيفة، والتقطت صورة لبعض الخنافس الآسيوية التي تغزو جنوب أوروبا وتترك وراءها أثرًا من أوراق الشجر نصف المأكولة.
كان المتجر مكيّفًا، وألقيت نظرة على أحدث الهواتف والأدوات. خرجت ومعي بطاقة microSD بسعة 64 جيجابايت تدعي أنها عالية التحمل، وحتى جهاز UPS (مزود طاقة غير منقطع) بسعر مخفض.
النشر
قد يبدو NixOS مشروعًا مخيفًا عند البدء به، فهناك الكثير من المعلومات المتناثرة التي تحتاج إلى فهمها بشكل كامل. لكنك سرعان ما ستعتاد عليه؛ عقلك يتولى عملية الترجمة.
يمكنك تشغيل تثبيت NixOS على Raspberry Pi عن طريق تنزيل صورة NixOS الرسمية والبدء من نظام فارغ، أو يمكنك ببساطة أن تعلم أن كل ما تحتاجه هو هذه الوحدة:
{
ModulesPath,
...
}:
{
الواردات = [[
${modulesPath}/installer/sd-card/sd-image-aarch64.nix
]؛
sdImage.rootVolumeLabel = takodachi؛
}
التي ستقوم ببناء ملف .img، والذي يمكنك نقله باستخدام أمر dd إلى بطاقة microSD لتشغيل نظامك مباشرة، دون أي خطوات وسيطة.
الانتهاء
بعد نسخ الصورة إلى بطاقة microSD، كان كل ما علي فعله هو توصيل جميع المكونات بجهاز UPS الجديد وتشغيله. في غضون ثوانٍ، تمكنت من الاتصال بالـ Raspberry Pi عبر SSH والبدء في حل بعض المشكلات البسيطة في التكوين الجديد.
غادرت لقضاء إجازتي متأخرًا بعض الشيء، لكن مع وجود Navidrome يعمل بكفاءة وقادر على توفير الموسيقى للاستماع إليها أثناء القيادة.