OpenVPN działa w powłoce, ale pod cronem zawodzi: to PATH
Polecenie, które łączy się z powłoki, może zawieść pod cronem lub subprocessem, zanim tunel wystartuje. Popraw ścieżkę, katalog roboczy i terminal.
To samo polecenie łączy się z powłoki i umiera pod cronem, więc naturalny wniosek jest taki, że winny jest tunel albo certyfikat. Zanim w to uwierzysz, zobacz, co mają wspólnego oba uruchomienia: plik binarny, konfiguracja i dane logowania są identyczne, a zmieniło się tylko środowisko procesu. Gdy program nie jest nawet znajdowany, żadna ilość debugowania TLS nie pomoże. Poprawka zwykle tkwi w ścieżce, katalogu roboczym i garści zmiennych środowiskowych, nie w VPN.
command -v openvpn w działającej powłoce wypisuje odpowiedź, i bardzo często jest to /usr/sbin/openvpn, katalog, którego cron nie umieszcza na swojej ścieżce wyszukiwania.
Tunel nigdy nie wystartował, więc tunel nie jest problemem
Zadania uruchamiane przez cron dziedziczą minimalne środowisko, zwykle PATH=/usr/bin:/bin, i nie mają terminala sterującego. Wszystko zainstalowane w /usr/sbin lub /usr/local/sbin po prostu nie występuje na tej liście, a wiersz logu mówi openvpn: not found zamiast błędu TLS. Pythonowy subprocess zawodzi inaczej: dziedziczy środowisko procesu interpretera, które może być bogatsze niż cronowe, ale nadal nie ma powłoki, która rozwinie tyldę. Oba błędy zdarzają się, zanim choć jeden pakiet opuści hosta.
Szybkim sposobem ich rozróżnienia jest czytanie pierwszego błędu zamiast ostatniego. Brakujący plik binarny daje wyjątek powłoki lub Pythona, zła konfiguracja daje komunikat użycia OpenVPN, a prawdziwy błąd uścisku dłoni daje znaczniki czasu i adresy peera. Tylko trzeci jest problemem tunelu.
Ścieżki bezwzględne i listy argumentów biją łańcuchy powłoki
Podaj rozwiązaną ścieżkę wprost i całkowicie pomiń powłokę. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) daje kod powrotu, stdout i stderr bez proszenia powłoki o interpretację nazwy pliku. Buduj ścieżkę konfiguracji z os.environ["HOME"] lub pathlib.Path.home(), żeby tyldy nigdy nie trzeba było rozwijać. Gdy naprawdę potrzebujesz składni powłoki, użyj shlex.split, bo dzielenie łańcucha przez .split() psuje się na każdej ścieżce ze spacją. Czytaj też stderr, ponieważ OpenVPN zapisuje tam większość swoich wczesnych niepowodzeń.
Ta sama zasada dotyczy crona: wiersz zadania nie jest powłoką logowania, więc nic nie wczytuje twojego profilu i nic nie definiuje twoich aliasów.
Co crontab faktycznie dziedziczy
Wpis w crontabie dostaje środowisko zadeklarowane przez crontab plus kilka odziedziczonych wartości domyślnych, więc wypisz ścieżkę w całości albo opakuj wywołanie w skrypt, który to robi. Wiersz taki jak PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin na początku pliku obejmuje zwykłe miejsca instalacji, a przekierowanie przez >> cron.log 2>&1 jest tym, co czyni następny błąd czytelnym. Profil chroniony hasłem nie ma pod cronem sposobu, by poprosić o swój sekret, bo nie istnieje terminal, na którym można zapytać. Takie połączenie należy do jednostki systemd, która umie czytać plik poświadczeń, a nie do zadania udającego interaktywne.
Ścieżki względne to druga pułapka. Próbne uruchomienie często odbywa się z katalogu z profilem, a zadanie crona startuje zupełnie gdzie indziej, więc --config office.ovpn rozwiązuje się do pliku, którego tam nie ma, a komunikat czyta się jako brakujący plik, nie brakujący tunel.
Udowodnij to, obdzierając środowisko
Odtwórz minimalne środowisko bezpośrednio: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Skarga na ścieżkę lub terminal z tego uruchomienia oznacza, że problem siedzi nad VPN. Jeśli się łączy, pozostałymi podejrzanymi są konto, pod którym działa, katalog, w którym startuje, i poświadczenie, które przedstawia. Każdy z tych wyników jest lepszy niż kolejna runda sprawdzania certyfikatów.
Potem zrób opakowanie samoopisującym się. Wychodź z osobnym statusem dla każdego osiągniętego etapu, loguj oba strumienie ze znacznikiem czasu, a następny błąd powie ci, czy umarł przy rozwiązywaniu ścieżki, przy uwierzytelnianiu, czy po tym, jak tunel już stał.
Jednostka potrzebuje też właściwej kolejności: Wants=network-online.target plus After=network-online.target nie pozwalają klientowi wystartować, zanim istnieje adres lub trasa domyślna, co inaczej objawia się jako błędy certyfikatu lub TLS, które znikają przy ręcznym restarcie.