خدمة VPN تفشل عند EXEC: قارن الوحدة والملف التنفيذي وصلاحيات sudoers
الفشل عند الخطوة EXEC يعني أن systemd لم يتمكن من تشغيل الملف التنفيذي. قارن مسار الوحدة والمسار المثبت ومنح sudoers قبل تعديل أي شيء.
بالأمس كان النفق يعمل، وهذا الصباح أصبحت الوحدة ميتة مع Failed at step EXEC spawning /usr/local/bin/vpnclient في السجل. بيانات الاعتماد والمسارات والحساب لا تدخل أبدًا في تلك الجملة. حلَّ systemd قيمة ExecStart إلى مسار، وطلب من النواة تنفيذه، فرفضت النواة، غالبًا لأن لا شيء موجود في ذلك المسار أو لأنه غير قابل للتنفيذ. تعامل مع العطل كمسألة ملف وقائمة وسائط حتى يثبت الأمر المحلَّل العكس.
لماذا يفشل EXEC قبل أن تبدأ المصادقة
لا يشغّل systemd قيمة ExecStart عبر صدفة. بل ينسخ العملية، ويبني متجه الوسائط من السطر، ويستدعي execve على العنصر الأول. إذا فشل ذلك الاستدعاء، فلا تصل الخدمة أبدًا إلى شيفرة عميل VPN الخاصة، ولن يساعد أي قدر من تعديل الملف الشخصي أو فحص بيانات الاعتماد.
يفصل رمز الحالة هذه العائلة عن الأعطال العادية. الوحدة التي تنتهي بـ status=203/EXEC لم تبدأ أي عملية أصلًا؛ والوحدة التي تبدأ ثم تنتهي بـ status=1 ربما فشلت في المصادقة أو DNS أو التوجيه. اقرأ الرمز قبل قراءة سجل العميل، لأن سجل العميل سيكون فارغًا في الحالة الأولى.
المسار في السجل هو المسار الذي استخدمه systemd، وليس بالضرورة ما عدّلته أنت. يمكن لملفات الإضافة ومخرجات المولّد أن تتجاوز ملف المورّد، كما أن ترك إعادة تحميل daemon-reload دون تنفيذ يُبقي السطر القديم في الذاكرة. لهذا فإن التشخيص الأول هو أن تسأل systemd عما يعتقده الأمر، بدلًا من فتح الملف الذي تتذكر أنك غيّرته.
ثلاثة أماكن يُكتب فيها مسار الملف التنفيذي
عميل VPN المثبَّت من حزمة توزيعة أو سكربت مورّد أو أرشيف إصدار قد يستقر في ثلاثة بادئات مختلفة. سجّلت الوحدة وقاعدة الامتياز والمثبِّت كلٌّ منها واحدًا من تلك البادئات، ويظهر العطل عندما يختلف اثنان من الثلاثة.
| Record | Common value | How to read it |
|---|---|---|
| Unit or drop-in | /usr/local/bin/vpnclient | systemctl cat vpnclient.service |
| Installed binary | /usr/bin/vpnclient | command -v vpnclient, then ls -l the result |
| Sudoers grant | /opt/vpnclient/bin/vpnclient | sudo grep -r vpnclient /etc/sudoers.d/ |
التعارض الملموس يبدو هكذا: سكربت المورّد يثبّت إلى /usr/local/bin، والأرشيف فُكّ تحت /opt، والوحدة لا تزال تذكر مسار /usr/bin القديم من حزمة سابقة. ملف واحد فقط من تلك الملفات موجود، ونادرًا ما يكون هو المذكور في ExecStart.
حدِّد المسار المعتمد قبل تغيير أي من الملفين. إذا كانت حزمة التوزيعة تملك الملف التنفيذي، فمسار الحزمة هو المعتمد وعلى الوحدة أن تتبعه. وإذا كان سكربت المورّد يملكه، فتنطبق القاعدة نفسها على بادئة المورّد. ونسخ الملف التنفيذي بين بادئتين ينشئ نسختين تتباعدان في الترقية التالية.
عندما يتحول أمر مُقتبس إلى اسم ملف
نمط فشل ثانٍ يختفي خلف السطر نفسه في السجل. يكتب أحدهم هذا في الوحدة ويتوقع أن يتصل العميل أثناء الإقلاع:
ExecStart=/usr/bin/"vpnclient connect office"يقسّم systemd القيمة إلى كلمات، فتصبح الكلمة الأولى /usr/bin/vpnclient connect office بعد إزالة علامات الاقتباس كتركيب تجميعي. ثم تبحث النواة عن ملف يحتوي اسمه على مسافات، وهو غير موجود. الخطأ مطابق لخطأ ملف تنفيذي مفقود مع أن الملف التنفيذي مثبَّت.
الصيغة المدعومة هي ملف تنفيذي حقيقي تتبعه وسائط منفصلة، مثل ExecStart=/usr/bin/vpnclient connect office. إذا احتاج سير العمل فعلًا إلى أنبوب أو سلسلة، فضعها في سكربت واستدعِ السكربت، لأن ExecStart لا يملك && ولا أنابيب ولا توسيعًا للنمط العام.
أصلح الوحدة، لا نظام الملفات
الإصلاح المُغري هو رابط رمزي من المسار المتوقع إلى الملف التنفيذي الحقيقي. وهو يعمل إلى أن تُحدَّث الحزمة فيشير الرابط إلى ملف محذوف، أو إلى أن تتوقف تسمية SELinux عن مطابقة الهدف. فضِّل تعديل الوحدة عبر ملف إضافة حتى يبقى ملف المورّد دون مساس وتبقى الترقيات متوقعة.
على قاعدة الامتياز أن تطابق المسار نفسه والأوامر الفرعية نفسها. فسطر sudoers من أجل /usr/local/bin/vpnclient لا يخوّل /usr/bin/vpnclient، والسطر الذي يسمح بكل وسيط يتيح لأي عملية محلية إعادة تهيئة النفق أو إيقافه. امنح الفعلين اللذين تحتاجهما الوحدة، وأبقِ الإزالة خارجها إذا كان إيقاف الخدمة يغلق الواجهة بالفعل.
هنا تُثمر المقارنة: ينبغي أن تذكر الوحدة وقاعدة بيانات الحزم وملف sudoers مسارًا واحدًا. وعندما تفعل ذلك، يختفي خطأ EXEC دون إعادة تشغيل ودون نسخة من الملف التنفيذي في موقع ثالث.
أثبت الأمر المحلَّل قبل إعادة التشغيل
sudo systemctl daemon-reload
sudo systemctl cat vpnclient.service
systemctl show -p ExecStart vpnclient.service
systemd-analyze verify /etc/systemd/system/vpnclient.service
sudo systemctl restart vpnclient.service
systemctl status vpnclient.service --no-pagersystemctl show هو المخرَج الحاسم لأنه يطبع قائمة الوسائط التي حلَّلها systemd فعلًا، بما في ذلك أي ملف إضافة. و systemd-analyze verify يلتقط مسارًا مفقودًا أو غير قابل للتنفيذ قبل أن تعيد التشغيل، وأمر الحالة بعد إعادة التشغيل يخبرك هل أصبحت العملية موجودة.
إذا بدأت الوحدة ثم فشلت، فقد تركت عائلة EXEC ودخلت معالجة الأخطاء الخاصة بالعميل. افحص السجل بحثًا عن رسالة العميل، لا عن فشل execve آخر. واحفظ فرق الوحدة وفرق sudoers معًا في سجل التغيير حتى لا تعيد الترقية التالية إدخال المسار المنقسم بصمت.