Привет! В Mac OS чаще всего используют программу Tunnelblick для подключения по vpn. Я тоже ей пользовался и это было отвратительно, может у меня кривые руки и я не сумел ее грамотно настроить ( ну те проставить правильно галочки в gui интерефейсе ), но в целом получить от нее стабильности и простоты у меня так и не получилось.
Возможно эта программное обеспечение для подключения к vpn удобно когда тебе нужно подключать по 1 vpn для просмотра ютубчика на обеде ( хотя vpn для этого не нужен, достаточно настроить анонимный proxy ) но когда требуется что-то более сложное Tunnelblick начинает хромать.
Стандартная ситуация, вы работаете в крупной компании и вам требуется использовать корпоративные сервисы, которые развернуты внутри сети. Для этой цели вам выдали vpn.
Так же внутри компании есть разные отделы, например завязанные на какие-то конкретные проекты и внутри корпоративной сети у вас должен так же быть отдельный доступ к внутренним сервисам вашего отдела проекта в котором вы работаете, таким образом вам уже требуется подключаться к 2 vpn сетям одновременно приплюсуем к этому что вы живете в стране в которой часть ресурсов заблокирована из-за непростой геополитической ситуации и вам уже потребуется 3 vpn которые должны работать и не конфликтовать друг с другом.
Tunnelblick с 3 VPN уже работает плохо, давайте еще добавим, что VPN тоже не работает стабильно так как время от времени провайдер тестирует оборудование фильтрации VPN и связь пропадает. Это катастрофа, если вам требуется VPN для работы.
Допустим, еще в вашей компании время от времени обновляются ovpn и его нужно скачивать по специальной ссылке и таким образом, используя гуи приложения для vpn вам придется время от времени заниматься удалением, добавлением и если вы используете еще sock5 для своего подключения — правками ovpn файлов.
Каждый раз после того как Tunnelblick зависал, я грустил и вспоминал времена когда у меня был arch linux и для подключения к vpn я использовал простой скрипт на bash.
Последней каплей стала ситуация когда после обновления ОС, вместе с зависанием Tunnelblick стала пропадать сеть так что требовалась перезагрузка макбука для того чтобы сеть снова появилась ( позже я разобрался с этим и это оказалось, что это из-за того что Tunnelblick не мог правильно возвращал DNS сервера на место ). Это стало последней каплей терпения и я решил окончательно решить этот вопрос и удалив с радостью Tunnelblick написал свой скрипт для подключения к VPN, чем хочу поделится тут.
#!/bin/bash
VPN_PID="$HOME/.vpn/vpn.pid"
LOG_FILE="$HOME/.vpn/vpn_download.log"
# в этом файле нужно прописать ссылки ( каждая на своей строке по которым скрипт будет забирать обновленные ovpn файлы
# содержимое файла ~/.vpn/update.txt должно иметь вот такой вид:
# https://corporate.site/share/project.ovpn
# https://corporate.size/share/corporate.ovpn
URL_LIST_FILE="$HOME/.vpn/update.txt"
# пути к .ovpn файлам
TARGET_DIR="$HOME/.vpn/configs"
VPN_CONFIG_1="$TARGET_DIR/corporate.ovpn"
VPN_CONFIG_2="$TARGET_DIR/project.ovpn"
VPN_CONFIG_3="$TARGET_DIR/private.ovpn"
# логин и пароль от корпоративного VPN
# содержимое файла ~/.vpn/password.auth
# <login_vpn>
# <password_vpn>
VPN_PASSWORD="$HOME/.vpn/password.auth"
# пусть к команде openvpn
# в моем случае я ставил openvpn для mac os через homebrew
VPN_COMMAND="/opt/homebrew/sbin/openvpn"
# замените на ваш DNS
DNS_SERVER="8.8.8.8"
# замените на вашу сеть
NETWORK_SERVICE="Wi-Fi"
# таймауты и настройки проверки подключений к vpn
CHECK_TIMEOUT=10
MAX_ATTEMPTS=3
CONNECT_TIMEOUT=30
# Shadowsocks параметры, как настраивать я писал тут:
# Как настроить Shadowsocks - https://killercoder.ru/maskirovka-trafika-cherez-shadowsocks
# Как установить Shadowsocks - https://killercoder.ru/ustanavlivaem-shadowsocks-na-svoj-server
SHADOWSOCKS=false
SHADOWSOCKS_PROXY="socks5://127.0.0.1:1080"
# Парсинг аргументов
# Поддерживает 1 аргумент --sock ( подключение через sock proxy )
while [[ $# -gt 0 ]]; do
case "$1" in
--sock)
SHADOWSOCKS=true
shift
;;
*)
break
;;
esac
done
# загружает обновленные файлы ovpn с поддержкой загрузки через sock5 ( если дела совсем плохи.. )
download_file() {
local url=$1
local filename=$(basename "$url")
local target_path="$TARGET_DIR/$filename"
local retry=0
echo "$(date '+%Y-%m-%d %H:%M:%S') - Начало загрузки $filename" >> "$LOG_FILE"
while [ $retry -lt $MAX_ATTEMPTS ]; do
if [ "$SHADOWSOCKS" = true ]; then
if curl -sSL --fail --connect-timeout $CONNECT_TIMEOUT --proxy "$SHADOWSOCKS_PROXY" "$url" -o "$target_path"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - Успешно (через Shadowsocks): $filename сохранен в $target_path" >> "$LOG_FILE"
echo "✅ $filename успешно загружен через Shadowsocks"
return 0
fi
else
if curl -sSL --fail --connect-timeout $CONNECT_TIMEOUT "$url" -o "$target_path"; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - Успешно: $filename сохранен в $target_path" >> "$LOG_FILE"
echo "✅ $filename успешно загружен"
return 0
fi
fi
((retry++))
echo "$(date '+%Y-%m-%d %H:%M:%S') - Попытка $retry/$MAX_ATTEMPTS: Ошибка загрузки $filename" >> "$LOG_FILE"
sleep 2
done
echo "$(date '+%Y-%m-%d %H:%M:%S') - Ошибка: не удалось загрузить $filename после $MAX_ATTEMPTS попыток" >> "$LOG_FILE"
echo "❌ Не удалось загрузить $filename после $MAX_ATTEMPTS попыток"
return 1
}
update() {
if [ ! -f "$URL_LIST_FILE" ]; then
echo "❌ Файл $URL_LIST_FILE не найден!" | tee -a "$LOG_FILE"
exit 1
fi
stop_vpn
sleep 2
echo "=== Начало загрузки VPN-конфигов ===" | tee -a "$LOG_FILE"
echo "Чтение URL из файла: $URL_LIST_FILE" | tee -a "$LOG_FILE"
success_count=0
fail_count=0
while IFS= read -r url || [ -n "$url" ]; do
if [[ -z "$url" || "$url" == \#* ]]; then
continue
fi
download_file "$url"
if [ $? -eq 0 ]; then
((success_count++))
else
((fail_count++))
fi
done < "$URL_LIST_FILE"
echo "=== Загрузка завершена ===" | tee -a "$LOG_FILE"
echo "Успешно: $success_count файлов" | tee -a "$LOG_FILE"
echo "Не удалось: $fail_count файлов" | tee -a "$LOG_FILE"
if [ $fail_count -gt 0 ]; then
exit 1
else
start_vpn
exit 0
fi
}
# команда запуска личного VPN
start_private() {
stop_vpn
sleep 2
echo "🔒 Запускаю VPN private..."
if [ "$SHADOWSOCKS" = true ]; then
echo "Использую Shadowsocks proxy для подключения"
sudo $VPN_COMMAND \
--config "$VPN_CONFIG_3" \
--socks-proxy 127.0.0.1 1080 \
--log-append /var/log/openvpn.log &
ALL_PROXY=$SHADOWSOCKS_PROXY
else
sudo $VPN_COMMAND \
--config "$VPN_CONFIG_3" \
--log-append /var/log/openvpn.log &
fi
echo "VPN(private)" > $VPN_PID
}
# команда запуска корпоративного VPN
start_vpn() {
stop_vpn
echo "🔒 Запускаю VPN corporate..."
# Проверка наличия нужных файлов
if [[ ! -f "$VPN_CONFIG_1" ]]; then
echo "❌ Ошибка: файл конфигурации $VPN_CONFIG_1 не найден!"
exit 1
fi
if [[ ! -f "$VPN_CONFIG_2" ]]; then
echo "❌ Ошибка: файл конфигурации $VPN_CONFIG_2 не найден!"
exit 1
fi
if [[ ! -f "$VPN_PASSWORD" ]]; then
echo "❌ Ошибка: файл аутентификации $VPN_PASSWORD не найден!"
exit 1
fi
if [ "$SHADOWSOCKS" = true ]; then
echo "Использую Shadowsocks proxy для подключения"
sudo $VPN_COMMAND \
--config "$VPN_CONFIG_1" \
--auth-user-pass "$VPN_PASSWORD" \
--socks-proxy 127.0.0.1 1080 \
--log-append /var/log/openvpn.log &
sudo $VPN_COMMAND \
--config "$VPN_CONFIG_2" \
--auth-user-pass "$VPN_PASSWORD" \
--socks-proxy 127.0.0.1 1080 \
--log-append /var/log/openvpn.log &
else
sudo $VPN_COMMAND \
--config "$VPN_CONFIG_1" \
--auth-user-pass "$VPN_PASSWORD" \
--log-append /var/log/openvpn.log &
sudo $VPN_COMMAND \
--config "$VPN_CONFIG_2" \
--auth-user-pass "$VPN_PASSWORD" \
--log-append /var/log/openvpn.log &
fi
# тут же проверяем подключение
attempt=1
while [ $attempt -le $MAX_ATTEMPTS ]; do
echo "Проверка подключения (попытка $attempt/$MAX_ATTEMPTS)..."
if check_vpn_connection; then
echo "VPN успешно подключен!"
if set_dns; then
export CURRENT_VPN="work"
echo "VPN(corporate)" > $VPN_PID
exit 0
else
exit 1
fi
fi
sleep $CHECK_TIMEOUT
((attempt++))
done
}
stop_vpn() {
echo "🛑 Останавливаю VPN..."
sudo networksetup -setdnsservers $NETWORK_SERVICE empty
sudo pkill -SIGINT openvpn
rm -f $VPN_PID
}
log() {
sudo tail -f /var/log/openvpn.log
}
empty_dns() {
sudo networksetup -setdnsservers $NETWORK_SERVICE empty
}
status_vpn() {
if pgrep -x "openvpn" > /dev/null; then
echo "✅ VPN работает (PID: $(pgrep -x "openvpn"))."
if [ -f "$VPN_PID" ]; then
echo "Текущий профиль: $(cat "$VPN_PID")"
fi
if [ "$SHADOWSOCKS" = true ]; then
echo "Используется Shadowsocks proxy"
fi
else
echo "❌ VPN не запущен."
fi
}
clear_utun() {
for i in $(ifconfig | grep '^utun' | cut -d: -f1); do
if ! lsof -i | grep -q "$i"; then
echo "Удаляю $i..."
sudo ifconfig "$i" down
sudo ifconfig "$i" destroy
fi
done
}
# функция остановки DNS
set_dns() {
echo "Устанавливаю DNS $DNS_SERVER для $NETWORK_SERVICE..."
sudo networksetup -setdnsservers "$NETWORK_SERVICE" "$DNS_SERVER"
if networksetup -getdnsservers "$NETWORK_SERVICE" | grep -q "$DNS_SERVER"; then
echo "DNS успешно изменён"
return 0
else
echo "Ошибка: не удалось изменить DNS"
return 1
fi
}
# функция проверки соединения
check_vpn_connection() {
if ifconfig | grep -q '^utun\|^tun'; then
if ping -c 1 -t 2 10.34.96.1 >/dev/null 2>&1; then
return 0
fi
fi
return 1
}
case "$1" in
corp)
start_vpn
;;
stop)
stop_vpn
;;
restart)
stop_vpn
sleep 2
start_vpn
;;
status)
status_vpn
;;
private)
start_private
;;
empty_dns)
empty_dns
;;
log)
log
;;
clear_utun)
clear_utun
;;
update)
update
;;
*)
echo "Использование: $0 {start|stop|restart|status|update|clear_utun|empty_dns|log} [--sock]"
echo ""
echo "Опции:"
echo " --sock Использовать Shadowsocks proxy для подключения"
exit 1
esac
Все подробности как что работает есть в комментариях в коде
Есть еще несколько вещей, которые можно сделать для удобства. Например, чтобы каждый раз не вводить пароль при запросе sudo через visudo добавляете
user ALL=(ALL) NOPASSWD: /opt/homebrew/sbin/openvpn user ALL=(ALL) NOPASSWD: /usr/sbin/networksetup user ALL=(ALL) NOPASSWD: /opt/homebrew/sbin/openvpn user ALL=(ALL) NOPASSWD: /opt/homebrew/sbin/openvpn user ALL=(ALL) NOPASSWD: /usr/sbin/networksetup user ALL=(ALL) NOPASSWD: /usr/bin/pkill -SIGINT openvpn
Вместо user указываете своего пользователя, вместо команд правильные пути в вашей системе. Правильный путь можно узнать командой:
$ which openvpn
И так же по-аналогии с sock5 можно сделать вывод подключения в командной строке, если пользуетесь оболочкой zsh
vpn_status() {
if [[ -f ~/.vpn/vpn.pid ]]; then
local vpn_name=$(cat ~/.vpn/vpn.pid)
echo "🟢 VPN: %F{green}$vpn_name%f" # Иконка + название VPN
else
echo "🔴 VPN: %F{red}DISCONECT%f"
fi
}
PROMPT='
... %F{yellow}%f $(vpn_status) ...
На этом все.. Если есть вопросы, пишите в телегу а так же подписывайтесь на обновление сайта в группу телеграм
