CH-J Server Managerإدارة الخوادم عبر SSH
القائمة

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

تنزيل الملف

تخضع هذه الترجمة حاليًا لمراجعة لغوية. إذا كان أي جزء غير واضح، فقارنه بـالنص الإنجليزي الأصلي.

الأسباب الجذرية

كان المحرر السابق يرفع ملف العمل إلى الدليل الأب للهدف، وهو دليل لا يستطيع حساب SSH الكتابة فيه غالبًا ضمن /etc أو /var. ولم تكن إعادة التسمية عبر SFTP توفر مسار sudo مُصرّحًا به، ولا فحصًا للتعارض مقابل الحالة الأساسية، ولا تحققًا من البيانات الوصفية الأمنية، ولا دلالات استبدال موثوقة. وكان معالج الأخطاء يحذف النسخة المؤقتة، فيزيل مادة الاسترداد حتى عندما يكون اكتمال العملية غير مؤكد.

كانت حالة الجلسة سابقًا تعرض حالة الاتصال فقط. ولم يكن هناك قياس ICMP أو قياس لاستجابة SSH موثّقة يغذّي مؤشر الطرفية.

بروتوكول الحفظ

  1. يفتح مساعد SSH ثابت ملفًا عاديًا ذا رابط واحد باستخدام عمليات نسبية إلى الواصف وO_NOFOLLOW لكل مكوّن من مكوّنات المسار. ويلتقط المحتوى وتجزئة SHA-256 والجهاز/inode وUID وGID وبتات الأذونات الكاملة وأوقات التعديل/التغيير بدقة النانوثانية وجميع السمات الممتدة القابلة للقراءة. يتحقق Core من التجزئة ويفك ترميز UTF-8 بصرامة مع الحفاظ على BOM. ويحصل كل مستند على editId معتم مقيّد بالإضافة وجلسة الطرفية وهوية الخادم.
  2. تحلّ عملية تحضير دون امتيازات الدليل المنزلي للحساب الموثّق عبر pwd.getpwuid(os.getuid())، وتنشئ $HOME/ch-j-sm/ بالوضع 0700، وتنشئ سجل استرداد (journal) خاصًا وحصريًا. ويُرفض الدليل المنزلي غير الآمن، أو مساحة العمل التي هي رابط رمزي، أو مساحة العمل المملوكة لحساب آخر.
  3. يرفع Core عبر SFTP ملف عمل مخفيًا بامتداد .tmp واسم عشوائي بطول 128 بت، مع إنشاء حصري (wx) والوضع 0600 ومهلة قناة/رفع مدتها ستون ثانية. يحتوي السجل على الهدف والحالة الأساسية الأصلية والتجزئة المتوقعة لملف العمل؛ ولا يحتوي على بيانات اعتماد.
  4. تنفّذ عملية exec واحدة عبر SSH مرحلة الإنهاء بأكملها، اختياريًا عبر منفّذ sudo القائم. يفتح المساعد نسخة العمل الخاصة ويحسب تجزئتها بأمان ثم يدفعها إلى القرص (flush). ويقفل inode الأصلي، ويفحص حالته الأساسية، وينشئ ملف وجهة حصريًا بجوار الهدف، ويكتب المحتوى كاملًا، ويطبّق البيانات الوصفية ويتحقق منها، ويفحص الأصل مرة أخرى، ويدفع البديل إلى القرص.
  5. يستبدل os.replace في الدليل نفسه الهدف بشكل ذري. ثم يُدفع الدليل الأب إلى القرص ويُتحقق من المسار النهائي والتجزئة وinode والبيانات الوصفية. ويبقى المسار الأصلي متاحًا حتى الاستبدال. ولا يُنقل ملف العمل أبدًا عبر أنظمة الملفات، ولا تزيله مرحلة الإنهاء أبدًا.
  6. بعد أن يتلقى Core نجاحًا متحققًا منه، تعيد عملية تنظيف منفصلة فحص المحتوى النهائي والبيانات الوصفية الأمنية قبل إزالة ملف العمل والسجل. وحتى إذا فشل التنظيف يُبلَّغ عن حفظ مؤكد مع recoveryAvailable: true.

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

البيانات الوصفية والامتيازات وحدود السلامة

يحافظ المساعد على UID وGID وجميع بتات أذونات POSIX ويتحقق منها، بما في ذلك بتات التنفيذ وsetuid وsetgid وsticky. ويعيد إنتاج السمات الممتدة في Linux بايتًا بايتًا، بما في ذلك POSIX ACL وcapabilities وتسميات SELinux. تُضبط الملكية ومحتوى الملف قبل السمات الأمنية، لأن الكتابة/chown قد تمحو capabilities أو بتات الأذونات الخاصة. وتُزال السمات الموروثة في الوجهة غير الموجودة في الأصل. ويعكس وقت التعديل الكتابة الجديدة ولا يُنسخ من الأصل.

تؤدي السمة غير القابلة للقراءة أو غير القابلة للتمثيل إلى إلغاء العملية؛ ولا تُسقط البيانات الوصفية بصمت. وتُرفض توقيعات IMA/EVM صراحةً لأن نسخ توقيعات السلامة المرتبطة بالمحتوى/inode سيبطلها. وتُرفض الروابط الرمزية في مسار الهدف أو الدليل المنزلي، والملفات ذات الروابط الصلبة، والملفات غير العادية. ولا يُستخدم أي بديل للاستبدال في المكان أو استبدال SFTP أضعف.

تعمل عمليات الحفظ العادية القابلة للكتابة باسم حساب SSH. وتتطلب عمليات الحفظ المحمية { sudo: true } صراحةً؛ وكلمة مرور sudo اختيارية عند استخدام sudo دون كلمة مرور. تُتحقق كلمات المرور وتُمرَّر فقط عبر stdin الخاص بالقناة، ولا تكون أبدًا داخل الأمر أو السجل أو ملف العمل أو أداة التسجيل أو الخطأ البعيد. وتستخدم جلسات SSH بحساب root منفّذ root المباشر القائم. ويترك فشل التفويض الأصل ونسخة العمل سليمين. كما ترفض جميع عمليات الحفظ المنفّذة بصلاحيات root (عبر SSH مباشر بحساب root أو عبر sudo) الأدلة المستهدفة/الأسلاف المملوكة لحسابات أخرى أو القابلة للكتابة منها أو التي تحمل ACL للوصول، بدلًا من الثقة بمسار ذي امتيازات يتحكم فيه حساب آخر.

يعمل المساعد الثابت بالخيارين python3 -I -B: فيتجاهل مسارات Python الخاصة بالمستخدم ودليل العمل وحزم site الخاصة بالمستخدم، ولا يكتب bytecode. تُقتبس المسارات ووسائط JSON للصدفة داخل Core. ولا تتلقى الإضافات أبدًا تنفيذ SSH عشوائيًا أو مقابض SFTP. وتظل sandbox وCSP وأذونات البيان والتحقق من مفتاح المضيف والتحقق من التحديثات قيد الاستخدام.

Plugin API والتكامل المتبقي مع Monaco

واجهة Monaco/File Manager الموزعة بشكل منفصل غير موجودة في هذه النسخة (checkout). ولم يكن ممكنًا هنا تنفيذ تكامل واجهتها ومطالبة sudo ومعالجة المخازن غير المحفوظة وترجماتها أو اختبارها. مصدر الإضافة المضمّن هو إضافة Hash & Checksum. ولم يُخترع أي مصدر بديل لـ File Manager.

تبقى الطرق القائمة متاحة. وتُتاح طرق/خيارات إضافية عبر preload داخل sandbox وبيئة تشغيل تتحقق من الأذونات:

const opened = await chjPlugin.files.readText(sessionId, path);
// For a protected read, explicitly pass { sudo: true, sudoPassword }.

const result = await chjPlugin.files.saveText(sessionId, path, editor.getValue(), {
  editId: opened.editId,
  sudo: true,           // Include only after the user authorizes elevation.
  sudoPassword         // Omit for passwordless sudo; never persist this value.
});

if (result.ok && result.value.status === "saved") {
  // Mark this exact submitted editor version as saved. Later edits stay dirty.
} else {
  // Keep the Monaco model dirty. Translate result.error.code/status.
  // result.error.recoveryId/recoveryPath identify retained working material.
}

await chjPlugin.files.closeText(opened.editId);
const recovery = await chjPlugin.files.listRecovery(sessionId);
const copy = await chjPlugin.files.readRecovery(sessionId, recoveryId);
// Review copy.status and copy.text; open a fresh document before another save.
await chjPlugin.files.cleanupRecovery(sessionId, recoveryId);
// Cleanup rejects anything whose target is not currently confirmed.

تقبل طرق الاسترداد وسيطًا أخيرًا اختياريًا { sudo: true, sudoPassword } عندما يتطلب فحص الهدف رفع الصلاحيات. وتحتفظ بمساحة عمل المستخدم الموثّق حتى عند التنفيذ عبر sudo. تبقى السجلات بعد إعادة تشغيل التطبيق وإعادة اتصال SSH؛ ولا يكرر الاسترداد مرحلة الإنهاء تلقائيًا أبدًا. راجع المخزن المسترد مقابل هدف مفتوح حديثًا قبل حفظه صراحةً.

يحمي files.read عمليات القراءة والفحص وتحرير المقابض. ويحمي files.write عمليات الحفظ وتنظيف الاسترداد. ولا يزال writeText يرفض (reject) عند الفشل للإضافات القائمة؛ أما saveText فيعيد مغلّفًا منظّمًا { ok, value/error } لأن Electron يُسقط الخصائص المخصصة من كائنات Error المرفوضة. ويمكن لـ writeText القديم بثلاث وسائط استخدام الحالة الأساسية لمستند واحد مفتوح. وتتطلب المستندات الحية المتعددة للمسار نفسه editId؛ وتفشل الاستدعاءات القديمة الملتبسة بأمان بدلًا من اختيار الحالة الأساسية لمستند آخر. وينبغي للإضافات تحرير المقابض غير المستخدمة.

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

الحالة التشيكية الألمانية الإنجليزية
جارٍ الحفظ Ukládání… Wird gespeichert… Saving…
مؤكَّد Uloženo Gespeichert Saved
فشل Uložení selhalo Speichern fehlgeschlagen Save failed
يلزم sudo Vyžadováno oprávnění sudo sudo-Autorisierung erforderlich Sudo authorization required
تعارض Původní soubor byl změněn Originaldatei wurde geändert Original file changed
انقطع الاتصال Spojení bylo přerušeno Verbindung unterbrochen Connection lost
غير معروف Výsledek uložení není znám Speicherergebnis unbekannt Save result unknown
الاسترداد Obnova je dostupná Wiederherstellung verfügbar Recovery available

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

Ping هو زمن الاستجابة الذي يُبلغ عنه رد ICMP echo فعلي من جهة العميل إلى عنوان IP المحلول الذي يستخدمه SSH. يستدعي Core الملف التنفيذي ping الخاص بالمنصة بشكل غير متزامن بمصفوفة وسائط ثابتة، دون صدفة ودون اشتراط البدء بصلاحيات المسؤول. وتُختار وسائط Linux وmacOS (بما في ذلك ping6) وWindows كلٌّ على حدة. وينتج عن حظر ICMP أو غياب الملف التنفيذي أو انتهاء المهلة أو المخرجات غير الصالحة أو رد Windows الذي يقتصر على حد أعلى مثل <1ms قيمة غير متاحة، لا رقم مختلق.

SSH RTT هو المدة الرتيبة المقاسة عبر performance.now() لطلب exec ثابت true عبر عميل SSH الموثّق القائم. ويشمل إعداد القناة ومعالجة الخادم وتسليم الاستجابة؛ وليس RTT شبكيًا خالصًا ولا مدة تسجيل الدخول عبر SSH. ويشرح التلميح المترجم للمؤشر هذا الفرق.

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

ينتقل زمن الاستجابة داخل حمولة IPC القائمة لحدث state / ssh:state:

latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" }

القيم غير المتاحة هي null. ويستخدم مؤشر الطرفية المدمج وحدة المللي ثانية ونصًا مترجمًا لعدم التوفر وتسميات بالتشيكية/الألمانية/الإنجليزية. ويختفي عند قطع الاتصال. ولم تكن هناك حاجة إلى قدرة تنفيذ إضافية للـ renderer ولا إلى طلب IPC إضافي.

التحقق والقيود التشغيلية

طلب المستخدم الاحتفاظ بالتغييرات في شجرة العمل القائمة، وهي حاليًا feature/hash-checksum-plugin؛ ولم يُجرَ أي تبديل للفرع أو تنظيف غير ذي صلة.

الملفات التي تغيّرت لهذه المهمة