একই কমান্ড শেল থেকে সংযোগ করে আর cron-এর অধীনে মারা যায়, তাই স্বাভাবিক সিদ্ধান্ত হলো টানেল বা সার্টিফিকেট দোষী। এটা বিশ্বাস করার আগে দেখুন দুইটি চালানোর মধ্যে কী মিল আছে: বাইনারি, কনফিগারেশন ও ক্রেডেনশিয়াল অভিন্ন, কেবল প্রসেসের পরিবেশ বদলেছে। প্রোগ্রামটি যখন খুঁজেই পাওয়া যায় না, তখন TLS ডিবাগিংয়ের কোনো পরিমাণই কাজে আসে না। সমাধান সাধারণত একটি পথ, একটি কাজের ডিরেক্টরি আর মুঠোয় কিছু পরিবেশ-চলকের মধ্যে থাকে, VPN-এ নয়।

যে শেল কাজ করে তার মধ্যে command -v openvpn উত্তরটি ছাপে, এবং সেটি প্রায়ই /usr/sbin/openvpn, অর্থাৎ এমন একটি ডিরেক্টরি যা cron তার অনুসন্ধান-পথে রাখে না।

টানেল কখনোই শুরু হয়নি, তাই টানেল সমস্যা নয়

cron-এর চালু করা কাজগুলো ন্যূনতম পরিবেশ উত্তরাধিকার পায়, প্রচলিতভাবে PATH=/usr/bin:/bin, এবং কোনো নিয়ন্ত্রক টার্মিনাল পায় না। /usr/sbin বা /usr/local/sbin-এ ইনস্টল করা যা কিছু আছে তা ওই তালিকা থেকে সহজভাবে অনুপস্থিত, আর লগের লাইনটি TLS ত্রুটির বদলে openvpn: not found পড়ায়। Python-এর subprocess ভিন্নভাবে ব্যর্থ হয়: এটি ইন্টারপ্রেটার প্রসেসের পরিবেশ উত্তরাধিকার পায়, যা cron-এর চেয়ে সমৃদ্ধ হতে পারে কিন্তু টিল্ডা প্রসারিত করার কোনো শেল এখনো নেই। দুইটি ব্যর্থতাই একটি প্যাকেটও হোস্ট ছাড়ার আগে ঘটে।

তাদের আলাদা করার দ্রুত উপায় হলো শেষটি নয়, প্রথম ত্রুটিটি পড়া। অনুপস্থিত বাইনারি শেল বা Python এক্সসেপশন দেয়, খারাপ কনফিগারেশন 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 ত্রুটি হিসেবে দেখা দেয় যা হাতে রিস্টার্ট করলে মিলিয়ে যায়।

সম্পর্কিত নিবন্ধ