OpenVPN chạy trong shell nhưng thất bại dưới cron: vấn đề là PATH
Một lệnh kết nối từ shell có thể thất bại dưới cron hoặc subprocess trước khi đường hầm khởi động. Sửa đường dẫn, thư mục làm việc và terminal.
Cùng một lệnh kết nối từ shell và chết dưới cron, nên kết luận tự nhiên là đường hầm hoặc chứng chỉ có lỗi. Trước khi tin điều đó, hãy xem hai lần chạy có gì chung: tệp nhị phân, cấu hình và thông tin đăng nhập giống hệt nhau, chỉ môi trường tiến trình thay đổi. Khi không tìm thấy chương trình, mọi mức độ gỡ lỗi TLS đều vô ích. Bản sửa thường nằm ở một đường dẫn, một thư mục làm việc và vài biến môi trường, không phải ở VPN.
command -v openvpn trong shell hoạt động sẽ in ra câu trả lời, và rất thường là /usr/sbin/openvpn, một thư mục mà cron không đặt trên đường dẫn tìm kiếm của nó.
Đường hầm chưa từng khởi động, nên đường hầm không phải vấn đề
Các tác vụ do cron khởi chạy thừa hưởng môi trường tối thiểu, theo quy ước là PATH=/usr/bin:/bin, và không có terminal điều khiển. Mọi thứ cài trong /usr/sbin hoặc /usr/local/sbin đơn giản là không có trong danh sách đó, và dòng nhật ký đọc openvpn: not found thay vì lỗi TLS. subprocess của Python thất bại khác đi: nó thừa hưởng môi trường của tiến trình thông dịch, có thể phong phú hơn của cron nhưng vẫn không có shell để mở rộng dấu ngã. Cả hai thất bại đều xảy ra trước khi một gói tin rời khỏi máy chủ.
Cách nhanh để phân biệt là đọc lỗi đầu tiên thay vì lỗi cuối cùng. Tệp nhị phân thiếu tạo ra ngoại lệ shell hoặc Python, cấu hình sai tạo ra thông báo cách dùng OpenVPN, và lỗi bắt tay thật tạo ra dấu thời gian và địa chỉ đối tác. Chỉ cái thứ ba là vấn đề đường hầm.
Đường dẫn tuyệt đối và danh sách đối số thắng chuỗi shell
Truyền đường dẫn đã phân giải một cách rõ ràng và bỏ qua shell hoàn toàn. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) cho bạn mã trả về, stdout và stderr mà không cần nhờ shell diễn giải tên tệp. Xây dựng đường dẫn cấu hình từ os.environ["HOME"] hoặc pathlib.Path.home(), để dấu ngã không bao giờ phải mở rộng. Khi thực sự cần cú pháp shell, hãy dùng shlex.split, vì việc tách chuỗi bằng .split() sẽ hỏng trên mọi đường dẫn có dấu cách. Cũng đọc stderr, vì OpenVPN ghi phần lớn thất bại sớm của nó ở đó.
Quy tắc tương tự áp dụng cho cron: dòng tác vụ không phải shell đăng nhập, nên không gì nạp hồ sơ của bạn và không gì định nghĩa bí danh của bạn.
crontab thực sự thừa hưởng những gì
Một mục crontab nhận môi trường mà crontab khai báo cộng vài giá trị mặc định được thừa hưởng, nên hãy viết đầy đủ đường dẫn hoặc bọc lệnh gọi trong một script làm việc đó. Một dòng như PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ở đầu tệp bao quát các vị trí cài đặt thông thường, và chuyển hướng bằng >> cron.log 2>&1 là thứ khiến lỗi tiếp theo đọc được. Hồ sơ được bảo vệ bằng cụm mật khẩu không có cách hỏi bí mật của nó dưới cron, vì không có terminal để hỏi. Kết nối như vậy thuộc về một unit systemd có thể đọc tệp thông tin đăng nhập, không phải một tác vụ giả vờ tương tác.
Đường dẫn tương đối là cái bẫy thứ hai. Lần chạy thử thường diễn ra từ thư mục chứa hồ sơ, còn tác vụ cron bắt đầu ở nơi hoàn toàn khác, nên --config office.ovpn phân giải thành một tệp không có ở đó và thông báo được đọc như tệp thiếu hơn là đường hầm thiếu.
Chứng minh bằng cách bóc môi trường
Tái tạo môi trường tối thiểu trực tiếp: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Một phàn nàn về đường dẫn hoặc terminal từ lần chạy đó nghĩa là vấn đề nằm trên VPN. Nếu nó kết nối, các nghi phạm còn lại là tài khoản mà nó chạy dưới, thư mục nó bắt đầu và thông tin đăng nhập nó trình bày. Cả hai kết quả đều tốt hơn một vòng kiểm tra chứng chỉ nữa.
Sau đó làm cho trình bao tự báo cáo. Thoát với trạng thái riêng cho mỗi giai đoạn đã đạt, ghi cả hai luồng kèm dấu thời gian, và lỗi tiếp theo sẽ cho bạn biết nó chết ở phân giải đường dẫn, ở xác thực hay sau khi đường hầm đã dựng.
Một unit cũng cần thứ tự đúng: Wants=network-online.target cùng với After=network-online.target giữ cho máy khách không khởi động trước khi có địa chỉ hoặc tuyến mặc định, điều mà nếu không sẽ hiện ra như lỗi chứng chỉ hoặc TLS biến mất khi khởi động lại thủ công.