النسخ الاحتياطي والمزامنة
حفظ خزانتك في مستودع git خاص تملكه، يدويًّا أو على مؤقّت، وسجلّ الملاحظات الذي يأتي معه.
خزانتك مجلد من ملفات ماركداون. ولهذا فإن أقدم أداة نسخ احتياطي في العالم وأكثرها قابلية للنقل هي أيضًا أفضلها: git. يحتفظ git بكل نسخة من كل ملف، ويستطيع أن ينسخ المجلد كله إلى حاسوب آخر (يسمّى المستودع البعيد) وأن يعيده منه. ويستطيع أسطرلاب أن يقوم عنك بخطوتَي git: تسجيل نسخة (وهو ما يسمّى الإيداع) ثم إرسالها إلى المستودع البعيد (وهو الدفع)، إما حين تطلب، وإما كل بضع دقائق. والميزة معطَّلة تمامًا حتى تشغّلها.
يحتاج النسخ الاحتياطي والمزامنة إلى كلمة مرور مشرف في كل وضع. فمن دونها يستطيع كل من يصل إلى المنفذ أن يوجّه المستودع البعيد إلى خادمه هو، ويدفع خزانتك كلها إليه. انظر النشر.
1. جهّز مستودعًا بعيدًا تدفع إليه
أنشئ مستودعًا فارغًا وخاصًّا على أي مضيف git تستخدمه: Forgejo أو Gitea أو GitLab تستضيفه بنفسك، أو أحد المضيفين الكبار. والفراغ مهم: لا يفعل أسطرلاب إلا أن يضيف إيداعات فوق ما هو موجود (وهذا ما يسمّى التقديم السريع)، فالمستودع الذي فيه إيداعات خاصة به سيرفض المزامنة حتى توفّق أنت بين التاريخين بنفسك. انسخ رابط استنساخ المستودع، وكلا الشكلين يصلح:
https://git.example.com/you/vault.git # HTTPS: يحتاج رمزًا (أدناه)
git@git.example.com:you/vault.git # SSH: يحتاج مفتاحًا على هذا الجهاز
2. اختر كيف يسجّل هذا الخادم دخوله
يحتاج الخادم إلى إذن بالدفع إلى مستودعك. وثمّة طريقتان لإعطائه هذا الإذن.
- مفاتيح SSH (وهي الموصى بها). لا يخزّن أسطرلاب هنا أي سرّ على الإطلاق. يشغّل
gitباسم مستخدم النظام نفسه الذي يعمل به خادمك، ومفتاح SSH الخاص بذلك المستخدم (أو وكيل SSH عنده) هو الذي يتولّى المصادقة. أنشئ مفتاحًا للخادم (ssh-keygen -t ed25519)، وأضف نصفه العام إلى مستودعك بوصفه مفتاح نشر بصلاحية كتابة، ثم تأكد من أنه يعمل من الطرفية أولًا:ssh -T git@git.example.com، ثمgit pushواحدة بيدك. ولا تتخطَّ هذا الاختبار، فالمفتاح الذي له عبارة مرور ولا وكيل يحملها سيفشل في صمت تامّ حين يشغّله الخادم. - رمز وصول. للمستودعات عبر HTTPS. والرمز كلمةُ مرور صُنعت لغرض واحد. أنشئ رمزًا محدود الصلاحيات، مقصورًا على ذلك المستودع وحده، بصلاحية قراءة محتوياته وكتابتها، وبأقصر مدة صلاحية تحتملها. ولا تستخدم أبدًا رمزًا كلاسيكيًّا يصل إلى حسابك كله. ألصقه في الإعدادات ← النسخ الاحتياطي والمزامنة ← رمز الوصول، مع اسم المستخدم الذي يخصّه (كثير من المضيفين يتجاهل اسم المستخدم؛ فيكفي أي شيء غير فارغ).
أين يُحفظ الرمز. في ASTROLABE_DATA/git-credentials.json، ولا يقرأ هذا الملف إلا مستخدم النظام الذي يعمل به الخادم (بالصلاحيات 0600). ولا يُكتب أبدًا في settings.json، ولا في الخزانة، ولا في .git/config، ولا في رابط المستودع البعيد؛ ولهذا يرفض حقلُ المستودع رابطًا فيه رمز مطبوع (https://user:token@host/…). وحين يحتاج git إلى الرمز يُسلَّم إليه عبر GIT_ASKPASS ومتغيّر بيئة على عملية git تلك وحدها. فلا يظهر في سطر أوامر قطّ (وسطور الأوامر يقرؤها كل من على الجهاز بالأمر ps)، ولا يحطّ في مخزن الاعتمادات الخاص بجهازك (كل اتصال بالشبكة يعمل بـ-c credential.helper= لتعطيل تلك المساعدات). ولا تعيده الواجهة البرمجية أبدًا: يجيب GET /api/settings بـtokenSet: true ولا شيء بعدها، وكل خطأ من git تراه أو يصل إلى السجل يُنظَّف أولًا من الرمز ومن أي بيانات مستخدم في رابط. وزر مسح الرمز يحذف الملف.
3. شغّله
افتح الإعدادات ← النسخ الاحتياطي والمزامنة. شغّل مفتاح النسخ الاحتياطي (كل ما تحته يبقى معطَّلًا حتى تفعل)، وألصق رابط المستودع البعيد، ثم اختر فترة المزامنة التلقائية، من يدويًّا فقط إلى مرة كل يوم. أما الفرع (الافتراضي main) والسحب أولًا فينتظران خلف سطر متقدّم في القسم؛ وأكثر الخزائن لا تمسّهما. (وإذا حان موعد المؤقّت ومزامنةٌ ما زالت تعمل تُخطى تلك الدورة.) فإن لم تكن الخزانة مستودع git بعد فاضغط تهيئة المستودع: يشغّل هذا git init، ويصنع الإيداع الأول، ويكتب ملف .gitignore أو يمدّه حتى لا يمكن إيداع مجلد بياناتك أبدًا، ويوجّه origin إلى مستودعك البعيد. ويختفي الزر ما إن تصير الخزانة مستودعًا.
4. زامن
كل تشغيل يزامن أولًا. ما دام النسخ الاحتياطي والمزامنة مفعَّلًا، فإن الخادم حين يبدأ يجري مزامنة واحدة قبل أن يستقبل أول نافذة. وكل نافذة تُفتح بصفة مشرف، تبويب متصفح، أو الهاتف، أو نافذة سطح مكتب، تطلب من الخادم مزامنة أخرى، فيجريها ما لم تكن مزامنة قد جرت في الدقائق الخمس الأخيرة أو تكن واحدة تعمل الآن. وهكذا يكون أول ما يفعله أي جهاز أن يسحب ما دفعته الأجهزة الأخرى. ويبقى زر زامن الآن اليدوي متاحًا متى شئت.
وما دام النسخ الاحتياطي مفعَّلًا يعرض شريط الحالة رمز فرع صغيرًا: عاديًّا حين يكون كل شيء مودَعًا، ومعه عدد حين يكون ثمّة ما لم يودَع بعد، وذهبيًّا والمزامنة تعمل، وأحمر حين تكون آخر مزامنة قد فشلت. انقره تظهر لوحة صغيرة فيها الفرع، وكم إيداعًا أنت متقدّم على المستودع البعيد ومتأخّر عنه، ونتيجة آخر مزامنة، وبعد الفشل سطرُ الخطأ من git نفسه نصًّا يمكن تحديده، وزرُّ نسخ الخطأ، وقفزة بنقرة واحدة إلى قسم الإعدادات. وزر زامن الآن موجود في تلك اللوحة وفي لوحة الأوامر. والمزامنة الواحدة تفعل هذا:
- اختياريًّا
fetchثمmerge --ff-only؛ انظر أدناه؛ git add -A، ثم يُخرج.trash/(ومعهASTROLABE_DATAإن كنت وضعته داخل الخزانة) من الفهرس؛ انظر ما لا تُدرجه المزامنة أبدًا أدناه؛- إيداع باسم
astrolabe sync: <ISO timestamp>، ويُتخطّى كليًّا حين لا يكون شيء قد تغيّر؛ git push.
لماذا السحب بالتقديم السريع فقط
لأن البديل قد يتلف ملاحظاتك. فحين تكون نسختان من المستودع قد تغيّرتا كلتاهما، يكون جواب git المعتاد هو الدمج، وحين يكون السطر نفسه قد تغيّر في الجانبين يكتب الدمج علامات التعارض <<<<<<< داخل الملفات نفسها. ومهمّة خلفية لا يراقبها أحد تفعل ذلك بألف ملاحظة أسوأ من أي نسخة احتياطية فائتة. لذلك لا يدمج أسطرلاب أبدًا، ولا يعيد ترتيب التاريخ (ما يسمّيه git rebase) أبدًا، ولا يغيّر ذلك إعدادُ pull.rebase = true في ملف git الخاص بك، لأن الأمر git pull لا يعمل أصلًا. فإذا كان في المستودع البعيد إيداعات ليست عندك توقّفت المزامنة قبل أن تمسّ ملفًا واحدًا وأخبرتك أن التاريخين تباعدا. لا يودَع شيء، ولا يُدفع شيء، ولا تُعدَّل ملاحظة. وعندها توفّق أنت بين الاثنين في الطرفية، فذلك قرار ينبغي أن يتخذه إنسان. ولا يدفع أسطرلاب دفعًا قسريًّا يمحو ما على المستودع البعيد أبدًا.
ما لا تُدرجه المزامنة أبدًا
إدراج الملف هو وضع علامة عليه ليدخل في الإيداع التالي (وقائمة الملفات المدرجة تسمّى الفهرس). ويُخرج مساران من الفهرس في كل مزامنة، قبل أن يودَع أي شيء، مهما قال ملف .gitignore في خزانتك عنهما:
| المسار | لماذا |
|---|---|
.trash/ | حذف ملاحظة أو مرفق أو مجلد ينقله إلى هنا، وكل الوعد في ذلك أنه سلّة محلية: شيء تنبش فيه، وتسترجع منه، وتفرغه بلا عواقب (ومتصفح المهملات هو الباب: Ctrl/Cmd P ← فتح المهملات). وإيداعه كان سيجعل كل حذف تاريخًا دائمًا على المستودع البعيد، وذلك عكس الوعد تمامًا. والملف الصغير .astrolabe-trash.json بداخله، الذي يسجّل من أين جاء كل مدخل حتى يكون الاسترجاع استرجاعًا حقًّا، دفتر محلي تشمله القاعدة نفسها. |
ASTROLABE_DATA، حين يكون داخل الخزانة | فيه settings.json، وقاعدة بيانات التعليقات، ورمز الوصول إلى git. |
ويتم ذلك بالأمر git rm --cached على الفهرس، لا بقاعدة تجاهل، والفرق مهم. فقاعدة التجاهل ملفُك أنت ورأيُك أنت: في git تغلب آخر قاعدة مطابقة، فملف .gitignore الذي يقول .trash/ ثم يقول بعدها !.trash/ يلغي التجاهل من جديد، وبناءٌ قديم كان يكتفي بالسؤال «هل يوجد سطر .trash/؟» لم يجد ما يفعله فدفع المهملات. أما الإخراج من الفهرس فلا يسأل ملف تجاهل عن شيء. وهو يصلح أيضًا الخزانة التي دفعها بناء قديم: أول مزامنة بعد الترقية تدرج الإزالة، فتخرج المهملات من آخر نسخة على الفرع من تلقاء نفسها. (وتبقى في التاريخ، أي في الإيداعات القديمة؛ انظر الملاحظة عن إعادة كتابة التاريخ أدناه.)
وما زال أسطرلاب يضيف .trash/ و.obsidian/workspace*.json إلى ملف .gitignore عندك إن كانا غائبين، حتى يكون git status في الطرفية هادئًا هو الآخر، لكن ذلك مجاملة لا آلية.
نصائح لملف .gitignore
فيما عدا هذين المسارين تودَع الخزانة كما هي. فقرّر ما الذي لا ينتمي إلى نسخة احتياطية قبل الدفع الأول. وملف .gitignore في أعلى الخزانة يسرد الملفات التي على git أن يتركها وشأنها:
.obsidian/workspace* # حالة نافذة أوبسيديان لكل جهاز، إن كنت تستخدم أوبسيديان أيضًا
.DS_Store
*.pdf # المرفقات الكبيرة، إن كان لمستودعك البعيد حد حجم
أبقِ مجلد .obsidian/ نفسه إن أردت نسخ إعدادات أوبسيديان احتياطيًّا، واحذف المجلد كله إن لم ترد. ولا تودع مجلد بياناتك أبدًا. ففي ASTROLABE_DATA (الافتراضي ./data) ملف settings.json وبيانات التعليقات ورمز git، فأبقه خارج الخزانة. وهذا هو الافتراضي، وملف .gitignore في هذا المستودع نفسه يستثني data/ أصلًا.
فإن كنت قد وجّهت ASTROLABE_DATA إلى داخل الخزانة رغم ذلك، فإن أسطرلاب يدافع عنه بأربع طرق، وكلها تعمل على مستودع قائم فيه .gitignore قائم، وهذه هي الحالة العادية لا الاستثنائية. فزر تهيئة المستودع ينشئ .gitignore، أو يضيف قاعدة مجلد البيانات إلى الملف الموجود عندك. وكل مزامنة تعيد فحص القاعدة بالأمر git check-ignore وترفض أن تعمل إن كان المجلد ما زال غير متجاهَل. وكل ما أودعه بناء قديم يُسقط من الفهرس (git rm --cached). وهذا الإسقاط نفسه يعمل مرة أخرى بعد git add -A في كل مزامنة، فيبقى المجلد خارج الفهرس أيًّا كانت قاعدة التجاهل التي طابقت أخيرًا. فلا تدرج المزامنة اعتماداتك أبدًا. وثمّة تنبيه واحد: الملفات التي دُفعت من قبل تبقى في تاريخ المستودع البعيد. فإن حدث ذلك فبدّل الرمز وأعد كتابة التاريخ في الطرفية.
تاريخ الملاحظة: قراءة ما حفظته النسخة الاحتياطية
كل مزامنة تصنع إيداعًا، وكل إيداع يحمل نسخة من كل ملاحظة فيه. والسجل هو ما يتيح لك أن تقرأ تلك النسخ. افتح ملاحظة، ثم افتح قسم السجل في اللوحة اليمنى، تجد الإيداعات التي مسّت هذه الملاحظة، الأحدث أولًا، مع التاريخ، والرسالة، وكم سطرًا أضاف كل إيداع وكم حذف.
- انقر صفًّا لتقرأ تلك المراجعة، معروضةً كما تُعرض الملاحظة في أي مكان آخر.
- استرجاع هذه المراجعة يكتبها من جديد عبر مسار الحفظ العادي. ولهذا يمكن التراجع عنها بـ
Ctrl/Cmd+Zفي المحرر، والتنبيه الذي يليها يحمل زر تراجع خاصًّا به. والاسترجاع نفسه تعديل، فالنسخة التي استرجعت منها تصير تاريخًا بعد لقطة واحدة؛ فلا يضيع شيء في الحالين. - والسجل يتبع إعادة التسمية. فالملاحظة التي كان لها اسم آخر تحتفظ بكل ما كانته قبل تغيير الاسم.
يبدأ القسم مطويًّا، ولا يسأل git شيئًا حتى تفتحه: فقراءة سجلّ ملاحظة عملٌ حقيقي على git، وأنت في معظم الوقت تكتب لا تنظر إلى الوراء. وإذا فتحته مرة بقي مفتوحًا.
لقطة الآن
في لوحة الأوامر أمر لقطة الآن: إيداع واحد للخزانة كلها، على هذا الجهاز، من غير أن يُرسل شيء إلى أي مكان. وهو الزر الذي تضغطه قبل تعديل لست متأكدًا منه. ولا يحتاج إلى مستودع بعيد ولا رمز ولا شبكة: أي خزانة هي مستودع git تستطيع أخذ لقطة. وتظهر في السجل باسم لقطة.
فإن لم تكن الخزانة مستودع git بعد، قال قسم السجل ذلك وعرض عليك المفتاح؛ فتشغيل النسخ الاحتياطي والمزامنة (الخطوة 3 أعلاه) هو ما يبدأ حفظ السجل أصلًا.
والسجل للمشرف وحده: لا يستطيع الزائر أن يرى أن ملاحظة منشورة مرّت بإحدى عشرة مسودة، ولا أن يقرأ أيًّا منها.
النسخ، قبل git وإلى جانبه
كان تاريخ الملاحظة يبدأ يومَ تصير الخزانة مستودع git، ولا دقيقة قبله. والنسخ الاحتياطي والمزامنة معطَّل افتراضيًّا، ويبقى معطَّلًا في معظم النسخ أسابيع. وفي تلك المدة كان الحفظ التلقائي في المحرر، الذي يكتب بعد نحو 600 ملّي ثانية من توقفك عن الكتابة، قادرًا على أن يحوّل لصقًا خاطئًا، أو تحديدًا للكل ثم كتابة فوقه، إلى النسخة الوحيدة من الملاحظة خلال ثانية. فالمهملات تلتقط الحذف؛ ولم يكن شيء يلتقط الكتابة فوق النص.
أما الآن فكل حفظ يُبقي نسخة. فقبل أن تكتب الخزانة ملاحظةً تضع النصَّ الذي كانت الملاحظة تحمله قبل لحظة في مجلد البيانات، في ASTROLABE_DATA/versions/<sha1 لمسار الملاحظة>/<الوقت>.md، بجانب ملف index.json صغير يذكر المسار صراحةً ويسرد كل نسخة: متى استُبدلت، ومتى حُفظ ذلك النص آخر مرة، وحجمه، ولماذا حُفظ. ويحدث هذا على مسار الكتابة الوحيد في الخزانة، فيغطّي كل كتابة في المنتج: الحفظ التلقائي، وPUT من سكربت، وسياج متتبّع أو سِجِلّ يُحرَّر من صفحة الوسائط، وإعادة كتابة روابط الويكي التي تنشرها إعادة التسمية. ولا يحتاج إلى git ولا مستودع بعيد ولا إعداد، وهو مفعَّل منذ التشغيل الأول.
وتظهر النسخ في قسم السجل نفسه في اللوحة اليمنى، في خط زمني واحد مع الإيداعات، مرتّبةً بالوقت. وصفّ النسخة أهدأ من صفّ الإيداع، فالإيداع شيء سمّيته أنت، والنسخة شيء احتفظ به التطبيق، ويقول الصف لماذا وُجدت النسخة: حفظ سابق، أو قبل استرجاع، أو قبل إعادة كتابة الروابط. انقر واحدة لتقرأها معروضة. واسترجاع هذه المراجعة يسأل أولًا، ثم يكتبها من جديد عبر مسار الكتابة نفسه، فيُحفظ النص الذي تستبدله نسخةً هو الآخر، ويأخذ المحرر المفتوح النتيجة تعديلًا يمكن التراجع عنه، ويحمل التنبيه زر تراجع.
وما يمنع المخزن من التضخّم بلا حدّ:
- الحفظات المتتابعة تندمج. حفظان تلقائيان بينهما أقل من خمس دقائق لا تبقى منهما إلا النسخة الأقدم. فيترك بعدُ ظهرٍ كاملٌ من الكتابة نسخةً واحدة لكل خمس دقائق، والنسخة التي تتركها هي النص من قبل الدفعة، وهي التي تسبق الخطأ. والاسترجاع مستثنى: فالنص الذي يكتب فوقه يُحفظ دائمًا.
- حدود في ثلاثة اتجاهات. أربعون نسخة للملاحظة الواحدة على الأكثر (وتذهب الأقدم)؛ والملاحظة التي تتجاوز 2 ميغابايت لا تُحفظ لها نسخ؛ والمخزن كله يُبقى تحت 500 ميغابايت بحذف أقدم نسخة فيه أيًّا كانت ملاحظتها. ويُكتب كل ملف بطريقة الملف المؤقت ثم إعادة التسمية، بالصلاحيات
0600، ككل شيء آخر في مجلد البيانات. - إعادة التسمية تنقل المجلد. فالملاحظة التي أُعيدت تسميتها، أو التي داخل مجلد نُقل، تحتفظ بماضيها تحت اسمها الجديد. والملاحظة المسترجَعة من المهملات إلى اسم بجانب أصلها (لأن الأصل كان مشغولًا) تأخذ نسخها معها.
- الملاحظة المحذوفة تحتفظ بنسخها حتى تفرغ المهملات. فالمهملات وعدٌ بأن الحذف قابل للاسترداد، والنسخ جزء من معنى ذلك الوعد. ومحو مدخل نهائيًّا من متصفح المهملات يمحو نسخه أيضًا، إلا إن كانت ملاحظة حيّة قد أخذت المسار نفسه منذ ذلك، فيكون السجل عندئذ ملكًا لتلك الملاحظة.
- ويمكن إيقافه من الإعدادات. الإعدادات ← النسخ السابقة والتنقّل ← حفظ نسخ الملاحظات، أو
NOTE_VERSIONS=offفي.envليكون ذلك افتراض تلك النسخة. وحين يكون معطَّلًا يقول قسم السجل ذلك ويعرض عليك المفتاح. والنسخ لا تسافر أبدًا: تعيش فيASTROLABE_DATAالذي لا تدرجه المزامنة قطّ، ولا يستطيع الزائر أن يسردها أو يقرأها أو يسترجعها.
ولا تغني النسخ عن النسخ الاحتياطي. فهي على هذا الجهاز، في مجلد بيانات هذه النسخة، وتخفّ مع الوقت. أما git فهو ما يُخرج ماضي الملاحظة من البيت. ولهذا يقف الاثنان معًا في اللوحة: أحدهما موجود دائمًا، والآخر هو الذي يستحق أن تشغّله.
والواجهة البرمجية، للمشرف وحده: GET /api/versions?path= يسرد النسخ الأحدث أولًا ({ enabled, versions })، وGET /api/versions/one?path=&at= يجيب بنصّ نسخة واحدة، وPOST /api/versions/restore مع { path, at } يكتبها من جديد عبر مسار الكتابة العادي ويطلق حدث changed في الخزانة. ويأخذ الزائر 401 من الثلاثة.
خادمان وخزانة واحدة
من العادي جدًّا أن ينتهي بك الأمر إلى خادمَي أسطرلاب فوق المجلد نفسه. فتطبيق سطح المكتب يشغّل خادمًا خاصًّا به، وكثيرون يبقون خادمًا آخر يعمل خدمةَ نظام ليصلوا إلى الخزانة من متصفح. وgit pull، أو أوبسيديان، أو Syncthing، أو محرر نصوص يكتب في المجلد نفسه، كلها الحالة نفسها مع كاتب ثانٍ مختلف.
والضمان: ملاحظة لم تحفظها أنت لا يمكن أن يكتب فوقها أحد بنسخة قديمة كان ما زال يمسك بها. فكل حفظ يجريه المحرر يحمل وقت تعديل الملف الذي حُمّل منه، ويرفض الخادم الكتابة، بالرمز 409 ومن غير أن يمسّ شيئًا، إن لم يعد الملف على القرص هو ذلك الملف. ولا يضيع شيء حين يحدث هذا: يبقى نصّك في المحرر، ويتوقف الحفظ التلقائي حتى لا تكتب ضغطةُ المفتاح التالية فوق النسخة الأحدث، ويعرض شريطٌ فوق الملاحظة المخرجين: احتفظ بنسختي (اكتب نسختك فوق الملف الأحدث) أو استخدم نسخة القرص (خذ نسختهم، مع إمكان التراجع).
وقد احتاج هذا إلى أن يُقال صراحةً بسبب حادثة حقيقية. نُشرت ملاحظة من المتصفح، وكان تطبيق سطح المكتب مفتوحًا منذ أيام وفيه تلك الملاحظة محمّلة من قبل النشر. وكل خادم يراقب الخزانة لعملائه المتصلين به هو، فلم تصل رسالة «تغيّر هذا الملف» إلى تطبيق سطح المكتب النائم، وتيار أحداث المتصفح لا يعيد ما فاته والحاسوب المحمول مغلق. والرفض المذكور أعلاه هو ما حفظ الملاحظة. أما ما كان ناقصًا فهو أي طريقة لمعرفة ذلك قبل محاولة الحفظ.
لذلك صار أسطرلاب يعيد الفحص حين يستيقظ: حين يعود اتصاله بالخادم، أو حين تعود إلى نافذة كانت مخفيّة، يسأل الخادم عن الحالة الراهنة للملاحظات المفتوحة عندك. فالتي لم تمسّها تُعاد تحميلها في صمت؛ والتي فيها تعديلات لم تُحفظ يظهر لها شريط الحلّ نفسه فورًا، وأنت تنظر إليها، بدل أن يقاطعك لاحقًا.
وثمّة أمران يستحقان المعرفة في هذا الترتيب:
- لا ملاحظة مقفلة. فلا يملك أيٌّ من الخادمين الخزانة، ويمكن إيقاف أيّهما في أي وقت. والرفض أعلاه فحصٌ يُجرى لحظة الكتابة، لا حجزٌ مسبق. والنسخ الاحتياطي والمزامنة هو الاستثناء الوحيد المتعمّد، للسبب المذكور في القسم التالي: الإيداع عملية على الخزانة كلها بفهرس git واحد، فلا يمكن أن يجري إيداعان في وقت واحد.
- السكربتات والعملاء الأقدم ما زالوا يعملون. فالكتابة التي لا وقت تعديل معها، من
curl، أو من سكربت خاص بك، أو من بناء قديم لسطح المكتب، تتصرّف كما كانت دائمًا: آخر من يكتب يغلب. فالفحص اختياري، ولا يطلبه إلا العملاء القادرون على التعامل مع الرفض.
فإن شغّلت خادمين فوجّههما إلى مجلد الخزانة نفسه وأعطهما مجلدَي بيانات مختلفين (ASTROLABE_DATA)، إلا إن أردت أن يتشاركا الجلسات والإعدادات أيضًا.
مزامنة واحدة في كل مرة، عبر كل الخوادم
تُكتب الملاحظات واحدةً واحدة. أما الإيداع فيُكتب للخزانة كلها دفعةً واحدة، عبر ملف .git/index واحد يحرسه git بقفل خاص به. وخادما أسطرلاب يودعان المجلد نفسه في الثانية نفسها لا يصنعان نسختين احتياطيتين: بل يموت أحدهما بالرسالة «Another git process seems to be running in this repository»، وربما بعد أن أدرج نصف الخزانة. وليس هذا افتراضًا نظريًّا، بل هو السبب في أن المزامنة كانت لا تستحق أن تُشغَّل إلا يدويًّا على جهاز يشغّل تطبيق سطح المكتب أيضًا.
لذلك فإن كل دورة تغيّر المستودع، زامن الآن، ولقطة الآن، وتهيئة المستودع، وكل دورة مجدولة، تأخذ أولًا ملف قفل في .git/astrolabe-sync.lock، وتمسك به حتى تنتهي. ولا يمكن أن يوجد إلا ملف واحد منه، فلا تعمل إلا دورة واحدة في كل مرة عبر كل العمليات التي تتشارك تلك الخزانة: تطبيق سطح المكتب، وخدمة النظام، وطرفية ثانية، وفترة مجدولة. وهذا ما يجعل المزامنة التلقائية آمنةً لتُترك مفعَّلة إلى جانب تطبيق سطح المكتب.
- كيف يبدو التزاحم. أمر زامن الآن الذي يصل وخادمُ أسطرلاب آخر في منتصف دورته ليس خطأً ولا يعيد المحاولة. يقول آخر سطر في لوحة النسخ الاحتياطي «أسطرلاب آخر يزامن هذه الخزانة (pid …)؛ لم تفعل هذه الدورة شيئًا»، مسمّيًا العملية التي تمسك بالقفل، ويبدو رمز الحالة مشغولًا ما دام الآخر يعمل. ويجيب لقطة الآن وتهيئة المستودع بالرمز
409مع الجملة نفسها. أما الدورة المجدولة التي تجد الخزانة مقفلة فتتخطّى ببساطة وتحاول في المرة التالية؛ ولا تسجّل شيئًا. - كيف يتعافى الأمر بعد انهيار. ملف القفل مجرد ملف، ولا شيء في نظام التشغيل يزيله حين يموت صاحبه، فلولا ذلك لعطّل خادمُ أسطرلاب قُتل في منتصف المزامنة النسخَ الاحتياطي إلى الأبد. ويجوز لأي عملية أخرى أن تكسر القفل حين تكون العملية التي أخذته قد ذهبت (يسجّل القفل معرّف عمليته واسم مضيفه، ويُفحصان مقابل جدول العمليات في الجهاز نفسه) أو حين لا يكون شيء قد مسّه منذ خمس عشرة دقيقة. وفي الحالين يُسجَّل الكسر تحذيرًا يسمّي المالك الميت. ولا تُحسب الدورة البطيئة ميتةً أبدًا: فالمالك الحيّ يجدّد القفل كل دقيقة وهو يعمل.
- ما لا يشمله. القفل استشاري، فأمر
git commitالذي تشغّله بنفسك في الطرفية لا يتأثر؛ وذلك ما كان ملفindex.lockالخاص بـgit يحكم فيه دائمًا. وجهازان يتشاركان خزانة واحدة عبر نظام ملفات شبكي خارج ما يضمنه هذا القفل: فضمان أن ينشئ ملفَ القفل طرفٌ واحد لا اثنان يعتمد على نظام الملفات الشبكي نفسه، وفحص العمر ذو الخمس عشرة دقيقة هو التعافي الوحيد هناك.
الإعدادات تسافر مع الخزانة
وإعدادات النسخة نفسها تسافر أيضًا. فملف settings.json (اسم الموقع، وسطر التعريف، والشعار، واللغة، والملاحظة الرئيسية، والمجلدات، والخطوط، والتقاويم، وإعدادات المزامنة نفسها)، وملف designs.json الخاص بالمصمم، وcustom.css، وخطوطك المخصصة، كلها تُنسخ من ASTROLABE_DATA إلى <vault>/.astrolabe/ وبالعكس، كل بضع ثوانٍ ومرة عند التشغيل، وتغلب النسخة الأحدث، على كل خادم فوق المجلد. وحين يلتقي خادم بنسخة الخزانة من ملف ما أول مرة تغلب الخزانة بلا نقاش، فيأخذ الجهاز الجديد إعدادات الموقع بدل أن يدفع افتراضياته فوقها. اضبط اسم الموقع على النسخة المستضافة، فيكون عند تطبيق سطح المكتب على الجهاز الآخر بعد سحبته التالية؛ وارفع شعارًا في المصمم على الحاسوب المحمول، فيعرضه الموقع بعد الدفعة التالية. أما ما يبقى في مكانه فهو: رمز git (لكل جهاز رمزه)، وقاعدة بيانات التعليقات، وسجلّ تواريخ إنشاء الملاحظات، والجلسات. والقيمة التي تأخذها نسخة مستضافة من ملف .env الخاص بها (SITE_NAME وSITE_TAGLINE وHOME_NOTE…) تبقى لذلك الخادم وحده حتى تُحفظ في لوحة الإعدادات، فتُكتب في settings.json ومنه إلى الخزانة.
وخطوطك تسافر، وتصل جاهزة. فكل خط رفعته (fonts/custom/، مع الفهرس الذي يسمّي كل وجه) يُنسخ إلى .astrolabe/fonts/custom/ تحت حدّين: الملف الذي يتجاوز 5 ميغابايت يُترك، وحين يتجاوز المجلد 40 ميغابايت يُترك ما بقي، فلا يُطلب من مستودع بعيد أن يحمل خطًّا بحجم 80 ميغابايت أُسقط باليد؛ والصف أدناه يسمّي كل ملف بقي وسببه. أما عائلات الفهرس التي اخترتها (fonts/catalog/) فلا تُنسخ، لأنها تُجلب من جديد، بل يجلبها الجهاز المستقبِل بنفسه عند التشغيل وكلما وصل settings.json أو designs.json أحدث، فيرسم التثبيت الجديد بخطوطك من أول تحميل دون أن يفتح أحد الإعدادات ← المظهر.
والإعدادات ← النسخ السابقة والتنقّل ← ما يسافر هو تقرير المرآة نفسه: علامة لكل بند (الموقع، والتصاميم، وCSS المخصص، والخطوط مع عددها، والتخطيطات، والكتب، والحواشي، والتفضيلات مع عدد مفاتيحها): ✓ حين تحمله الخزانة، و⚠ حين يحمل هذا الجهاز ما لا تحمله الخزانة بعد، و– حين لا شيء يُنقل؛ ثم متى جرت آخر دورة وكم ملفًا تمت مطابقته، وسطر أحمر لكل ملف تعذّر على دورة نسخه (حدّ، أو صلاحية)، وزر أعد المزامنة الآن الذي يجري دورة وجلب الخطوط عند الطلب. وتحته الجملة التي توفّر عليك بحثًا على جهاز جديد: ما يبقى عليك إعداده هو رمز git أو مفتاح SSH، وكلمة مرور المشرف، ودفء الشاشة.
أما الإعدادات الموسومة هذا الجهاز، السمة، ولغة الواجهة والمحرر، وVim، وعرض عمود الكتابة، وترقيم العناوين، وشريط أدوات التنسيق، وعلى أي جانب يتعلّق الشريط الجانبي، فتُحفظ في ملف واحد داخل الخزانة هو .astrolabe/prefs.json، ويقرؤه كل خادم فوق ذلك المجلد: تطبيق سطح المكتب على جهاز لينكس عندك، وتطبيق سطح المكتب على جهاز ويندوز، والنسخة المستضافة التي يفتحها هاتفك. غيّر السمة على أحدها، فتكون عند الأخرى في تشغيلها التالي. ولا حساب هنا ولا شيء يُضبط: فما ينقل ملاحظاتك بين الأجهزة (Syncthing، أو مزامنة git، أو قرص مشترك) ينقل الملف معها، والنسخة المستضافة وتطبيق سطح المكتب فوق المجلد نفسه لا يحتاجان إلى شيء البتة.
ويدفع كل جهاز ما يغيّره بعد التغيير بثانية، ويسحب الملف قبل أن يرسم أي شيء، فلا يصل إعداد في منتصف الجلسة فيحرّك الأشياء من تحتك. ولكل مفتاح على حدة يغلب التغيير الأحدث، والإعداد الذي يُمسح على جهاز يُمسح على الجهاز التالي بدل أن يعود منه. وما يسافر هو ما يسمّيه الإنسان إعدادًا. وتسافر معه أيضًا سجلاتك أنت لا سجلات الجهاز: تخطيطاتك المسمّاة، ورفّ الكتب مع الصفحة التي تركت كل كتاب عندها، وملاحظاتك لنفسك على الكلمات. أما التبويبات، ومساحة العمل، وعروض اللوحات، وارتفاع رفّ الوسوم، وحالة الطيّ، وكل علامة انطواء، فتصف هذه النافذة على هذه الشاشة، وتبقى حيث هي. ويطفئ الخيار الإعدادات ← النسخ السابقة والتنقّل ← الإعدادات تسافر مع الخزانة ذلك للجهاز الذي ينبغي أن يحتفظ بإعداداته لنفسه. والملف للمشرف وحده ولا يصل إلى زائر أبدًا. وإن كنت تحفظ الخزانة في git فأودع مجلد .astrolabe/؛ فهو ليس في نصائح التجاهل لأنك تريده.
الاختبار بسيط: ثبّت أسطرلاب على جهاز جديد، ووجّهه إلى الخزانة، فتجد كل ما أعددته هناك. وهذا ما يعنيه ذلك، بندًا بندًا.
| يسافر مع الخزانة | يبقى على الجهاز، ولماذا |
|---|---|
| الموقع: الاسم، وسطر التعريف، والشعار، واللغة، والملاحظة الرئيسية، والمجلدات، والخطوط، والتقاويم، وإعدادات المزامنة | رمز git: بيانات الدخول ملك جهاز واحد |
مستندات المصمم والسمات المخصصة، وcustom.css، وكل خط رفعته (حتى 5 ميغابايت للملف، و40 ميغابايت في المجموع)، أما عائلات الفهرس التي اخترتها فتُجلب من جديد عند الوصول | تاريخ الملاحظات (النسخ): كبير، وgit نسخته الدائمة |
| التخطيطات المسمّاة، ورفّ الكتب وموضع التوقف في كل كتاب، وملاحظاتك لنفسك على الكلمات | تعليقات الزوار على الموقع العام: هي ملك الموقع الذي تلقّاها |
| تفضيلاتك: السمة، ولغة الواجهة والمحرر، ومفاتيح Vim وأرقام الأسطر النسبية، وعرض المحرر، وترقيم العناوين، وشريط التنسيق، وجانب الشريط الجانبي، وترتيب الوسوم، ومفتاح «ما الجديد»، والتصحيح الفرنسي، ووضع القراءة، ولوحة الخصائص، وحيث توقفت في كل دورة | دفء الشاشة وتعتيمها: يجيبان عن ضوء شاشة واحدة |
| رمز git أو مفتاح SSH وكلمة مرور المشرف: بيانات الدخول ملك جهاز واحد | |
| القراءة دون اتصال وتحديثات البرنامج: اختيار جهاز واحد بشأن قرصه وشبكته | |
| التبويبات، واللوحات، وعروضها، وحالة الطيّ، والأقسام المطوية: هذه النافذة على هذه الشاشة | |
| ملفات فهرس الخطوط المخزّنة: تُجلب من جديد ولا تُحمل أبدًا | |
ما جلبته الخلاصات وأي المقالات قرأت (feeds.db): قارئ واحد على جهاز واحد — أما المقالات التي احتفظت بها فملاحظات، وتسافر |
أشياء تستحق المعرفة
- كل استدعاء لـgit هو
execFileبقائمة معاملات ثابتة. ولا يمرّ أي أمر عبر سطر أوامر يمكن التلاعب به، ويُتحقّق من رابط المستودع البعيد واسم الفرع (نوع الرابط، ولا محارف خاصة بسطر الأوامر، ولا اعتمادات مضمّنة، واسم فرع صالح) قبل أن يُسلَّما. وuser@الذي يشبه الرمز (ghp_…أوgithub_pat_…أوglpat-…) يُرفض في كل نوع من الروابط، حتى في الشكلينgit@host:pathوssh://اللذين يصلح فيهما اسم المستخدم العادي. - وتأخذ عملية git التي يشغّلها أسطرلاب بيئة منظَّفة: تُزال
GIT_DIRوGIT_WORK_TREEوGIT_INDEX_FILEومتغيّرات مجلد الكائنات وGIT_CONFIG*(بما فيهاGIT_CONFIG_COUNT/GIT_CONFIG_KEY_n) وGIT_SSHوGIT_SSH_COMMANDوGIT_PROXY_COMMANDوGIT_EXTERNAL_DIFFكلها، فلا يستطيع شيء في بيئة الخادم نفسه أن يوجّه git إلى مستودع آخر أو إعداد آخر أو وسيلة نقل أخرى. فإن احتجت إلى أمر SSH مخصص، لمفتاح نشر بعينه مثلًا، فاضبطASTROLABE_GIT_SSH_COMMAND(مثلASTROLABE_GIT_SSH_COMMAND="ssh -i /path/to/vault_ed25519 -o IdentitiesOnly=yes") فيمرّره أسطرلاب إلى git بالضبط بوصفهGIT_SSH_COMMAND. - ولعبارة «متقدّم / متأخّر» حالة ثالثة. فحتى ينجح جلب أو دفع مرة واحدة لا يوجد فرع بعيد يُقارن به، فتقول اللوحة «لم يصل شيء إلى المستودع البعيد بعد» بدل «0 متقدّم · 0 متأخّر»، وهذا الأخير ما تقرؤه خزانة منسوخة احتياطيًّا بالكامل.
- والمزامنة للمشرف وحده، حتى للمشرف الذي يعاين الموقع العام. فلا يستطيع الزوار قراءة الحالة نفسها؛ فالفرع، وعدد ما لم يودَع، واسم المضيف البعيد، تقول عنك أكثر مما ينبغي.
- ولا تعمل إلا مزامنة واحدة في كل مرة، والمقصود في كل مرة على هذه الخزانة، لا في هذا الخادم فحسب: فالطلب الثاني داخل العملية نفسها يُجاب بـ
409فورًا، والدورة التي يبدؤها خادم أسطرلاب آخر فوق المجلد نفسه يوقفها ملف القفل المذكور أعلاه. - والمزامنة المجدولة الفاشلة تُسجَّل مرة واحدة، لا مرة في كل دورة.
- فإن لم تكن للجهاز هوية git مضبوطة أُجريت الإيداعات باسم
Astrolabe <astrolabe@localhost>؛ فاضبطuser.name/user.emailفي الخزانة (أو على مستوى النظام) لتستخدم اسمك. - والواجهة البرمجية لمن يكتب سكربتًا (للمشرف وحده):
GET /api/sync/status، وPOST /api/sync/init، وPOST /api/sync/now، وPOST /api/sync/snapshot(إيداع محلي بلا شبكة)، وGET /api/sync/travel(ما يحمله مجلد.astrolabe/في الخزانة، وآخر دورة للمرآة، وما تعذّر نسخه)، وPOST /api/sync/travel(دورة مرآة واحدة مع جلب الخطوط، والجواب نفسه)، ومعها مسارا السجل للقراءة فقط:GET /api/history?path=وGET /api/history/blob?path=&sha=. ويريد مسار blob المسارَ الذي تعيش تحته تلك المراجعة، ويعطيك السجل ذلك في كل صف: فالملاحظة التي أُعيدت تسميتها تعيش تحت اسمها القديم في إيداعاتها الأقدم.