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. 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, изчезващи при ръчно рестартиране.