OpenVPN يعمل في الصدفة ويفشل تحت cron: السبب هو PATH
الأمر الذي يتصل من صدفة قد يفشل تحت cron أو subprocess قبل أن يبدأ النفق. أصلح المسار ودليل العمل والطرفية.
الأمر نفسه يتصل من صدفة ويموت تحت cron، فيكون الاستنتاج الطبيعي أن النفق أو الشهادة هو موضع الخلل. انظر إلى ما تشترك فيه التجربتان قبل أن تصدّق ذلك: الملف التنفيذي والإعداد وبيانات الاعتماد متطابقة، ولم يتغير سوى بيئة العملية. وعندما لا يُعثر على البرنامج أصلًا، لا تنفع أي كمية من تنقيح TLS. الإصلاح يقيم عادةً في مسار ودليل عمل وحفنة من متغيرات البيئة، لا في VPN.
command -v openvpn في الصدفة التي تعمل يطبع الجواب، وهو غالبًا /usr/sbin/openvpn، وهو دليل لا يضعه cron في مسار البحث الخاص به.
لم يبدأ النفق أبدًا، إذن النفق ليس المشكلة
المهام التي يبدأها cron ترث بيئة محدودة، عادةً PATH=/usr/bin:/bin، ولا تحصل على طرفية تحكم. وكل ما ثُبّت في /usr/sbin أو /usr/local/sbin غائب ببساطة عن تلك القائمة، ويقرأ سطر السجل openvpn: not found بدل خطأ TLS. أما subprocess في بايثون فيفشل على نحو مختلف: فهو يرث بيئة عملية المفسّر، وقد تكون أغنى من بيئة cron لكنها لا تزال بلا صدفة توسّع علامة التلدة. كلا الفشلين يحدث قبل أن تغادر حزمة واحدة المضيف.
الطريقة السريعة للتمييز بينهما هي قراءة الخطأ الأول بدل الأخير. الملف التنفيذي المفقود يُنتج استثناءً في الصدفة أو بايثون، والإعداد السيئ يُنتج رسالة استخدام من OpenVPN، وفشل المصافحة الحقيقي يُنتج طوابع زمنية وعناوين أقران. الثلث فقط هو مشكلة نفق.
المسارات المطلقة وقوائم الوسائط أفضل من سلاسل الصدفة
مرّر المسار المحلول صراحةً وتجاوز الصدفة كليًا. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) يمنحك رمز إرجاع وstdout وstderr دون أن تطلب من صدفة تفسير اسم ملف. ابنِ مسار الإعداد من os.environ["HOME"] أو pathlib.Path.home()، حتى لا تُوسَّع علامة التلدة أبدًا. وعندما تحتاج فعلًا إلى صياغة الصدفة، استخدم shlex.split، لأن تقسيم سلسلة بـ .split() ينكسر عند أي مسار يحتوي على مسافة. اقرأ stderr أيضًا، لأن OpenVPN يكتب معظم إخفاقاته المبكرة هناك.
القاعدة نفسها تنطبق على cron: سطر المهمة ليس صدفة تسجيل دخول، فلا شيء يقرأ ملف التعريف الخاص بك ولا شيء يعرّف اختصاراتك.
ما يرثه crontab فعليًا
يدخل سطر crontab في الحيز بكل ما يعلنه crontab من بيئة زائد بعض القيم الموروثة، لذا اكتب المسار صراحةً أو غلّف الاستدعاء بسكربت يفعل ذلك. سطر مثل PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin في أعلى الملف يغطي مواضع التثبيت المعتادة، وإعادة التوجيه بـ >> cron.log 2>&1 هي ما يجعل الفشل التالي مقروءًا. الإعداد المحمي بعبارة مرور لا سبيل له لطلب سره تحت cron، لأنه لا توجد طرفية يُسأل عليها. مثل هذا الاتصال ينتمي إلى وحدة systemd تستطيع قراءة ملف اعتماد، لا إلى مهمة تتظاهر بأنها تفاعلية.
المسارات النسبية هي الفخ الثاني. غالبًا يجري تشغيل الاختبار من المجلد الذي يحوي الإعداد، أما مهمة cron فتبدأ في مكان آخر تمامًا، لذا يحل --config office.ovpn إلى ملف غير موجود وتُقرأ الرسالة كملف مفقود لا كنفق مفقود.
أثبت ذلك بتجريد البيئة
أعد إنتاج البيئة الدنيا مباشرة: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. أي شكوى بشأن المسار أو الطرفية من ذلك التشغيل تعني أن المشكلة تقع فوق VPN. إن اتصل، فالمشتبه بهم الباقون هم الحساب الذي يعمل تحته والدليل الذي يبدأ فيه وبيانات الاعتماد التي يقدّمها. أي من النتيجتين أفضل من جولة أخرى من فحص الشهادات.
اجعل الغلاف بعد ذلك يبلّغ عن نفسه. اخرج بحالة مميزة لكل مرحلة بلغها، وسجّل كلا التدفقين مع طابع زمني، وسيخبرك الفشل التالي هل مات عند حل المسار أم عند المصادقة أم بعد أن كان النفق قائمًا أصلًا.
تحتاج الوحدة أيضًا إلى ترتيب صحيح: Wants=network-online.target مع After=network-online.target يمنعان العميل من البدء قبل وجود عنوان أو مسار افتراضي، وإلا ظهر ذلك كأخطاء شهادة أو TLS تختفي عند إعادة تشغيل يدوية.