, , , ,

10 июня, 2025

Скрипт запуска vpn для mac os и не только

Привет! В 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) ...

На этом все.. Если есть вопросы, пишите в телегу а так же подписывайтесь на обновление сайта в группу телеграм

,

9 апреля, 2014

URL без завершающих слэшей и www

Часто бывает нужно убрать www и завершающие слэши из всех URL. Данную операцию достаточно просто выполнить при помощи кода в .htaccess

#Если вы хотите, чтобы всегда был домен с www
RewriteCond %{HTTP_HOST} ^domen\.ru
RewriteRule ^(.*)$ http://www.domen.ru/$1 [R=301,L]
#Если вы хотите, чтобы всегда был домен без www
RewriteCond %{HTTP_HOST} ^www\.domen\.ru
RewriteRule ^(.*)$ http://domen.ru/$1 [R=301,L]
#Добавить завершающий слэш
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*[^/])$ $1/ [L,R=301]
#Удалить завершающий слэш
RewriteBase /
RewriteCond %{HTTP_HOST} (.*)
RewriteCond %{REQUEST_URI} /$ [NC]
RewriteRule ^(.*)(/)$ $1 [L,R=301]

, ,

17 февраля, 2013

Быстрое добавление множества директорий в .gitignore

Часто в проекте есть директория uploads/ с вложениями, содержимое в которых не нуждаются в поддержки версионности, для того чтобы не утомлять себя ручным вводом каждой директории можно воспользоваться командой:

 $ ls -l uploads | awk {'print "/uploads/"$9"/*"'} > ../.gitignore 

Дело сделано, нужные директории теперь не будут включены в репозитарий.

28 декабря, 2012

Выравнивание дива по вертикали и горизонтали

Иногда бывает нужно отцентрировать div на странице, это достаточно просто сделать через абсолютное позиционирование:

<!DOCTYPE html>
<html>
<head>
    <style type="text/css">
        div {
            background-color: #000;
            height: 500px;
            left: 50%;
            margin: -250px 0 0 -250px;
            top: 50%;
            position: absolute;
            width: 500px;
        }
    </style>
</head>
<body>
<div></div>
</body>
</html> 

Это создаст квадрат Малевича 500×500 пикселей по центу страницы.

, ,

3 августа, 2012

Групповое переименование файлов по маске

По долгу службы встала задача переименовать группу файлов в директории по маске ( убрать у php файлов префикс и постфикс ).
Исходные файлы вида aqNameClass.class.php нужно привести к виду NameClass.php

Решается эта задача одной командой shell:

for i in *.class.php; do mv "$i" `echo "$i" | sed "s/\.class//" | sed "s/^aq//"`; done

, ,

9 июля, 2012

Монтирование SD карты с правами пользователя в arch linux

Задача:

примонтировать SD карту на 64G как дополнительный жесткий диск, чтобы не-root мог читать ее и записывать в нее.

Решение:

mount -o uid=<uid> /dev/<dev_id> /mnt/<mount_point>

где,

<uid> — id пользователя ( узнать можно командой id или посмотреть в /etc/passwd )

<dev_id> — устройство sd карты

<mount_point> — каталог в который будет примонтирована SD-карта.

в /etc/fstab добавляем опцию монтированя uid=<uid>

и всё.

,

26 июля, 2010

Запись звука в Linux

Запись звука в Linux из консоли.
На моём субноуте есть микрофон, вот подумал, что неплохобы понять как записывать снимать звук с него, вот что нашёл:

Запись в wav:

$ arecord test.wav 

Переконвертировать wav в mp3 можно вот так:

$ cat test.wav | lame - > test.mp3

или сразу создать mp3

$ arecord | lame - > test.mp3

Вот так можно записать ядро системы linux в mp3 :)

$ cat /boot/mykernel.img > /dev/dsp &amp; arecord | lame - > test.mp3

,

20 июля, 2010

Получить снимок с web камеры через консоль Linux

Вот способ получисть снимок с web камеры из шел.
Искал способ как получить снимок (фото) с web камеры
Наткнулся только на статью про видеозахват через — ffmpeg. Вот ссылка — http://www.newmoldova.com/?q=node/85

Почитав немного ман по ffmpeg попробовал вот такую конструкцию:

$ ffmpeg -f video4linux2 -s 1024x768 -i /dev/video0 -f image2 snapshot.jpg 2&gt;/dev/null

Вроде работает :)
-f video2linux — формат видео для захвата
-s 1024×768 — размер кадра захвата
-i /dev/video0 — файла web камеры
-f image2 snapshot.jpg — выходной тим и имя файла

,

17 июля, 2010

javascript как узнать все свойства объекта

Небольшая функция на javascript, которая позволяет узнать все свойства объекта

Вот сама функция:

function fnShowProps(obj, objName) {
    var result = "";
    for (var i in obj) // обращение к свойствам объекта по индексу
        result += objName + "." + i + " = " + obj[i] + "\n";
    document.write(result);
}

Вот пример исползования:

fnShowProps(window.location, "location")

Результат:

location.pathname = /JavaScript/
location.protocol = http:
location.search = ?8
location.hash =
location.hostname = htmlcssjs.ru
location.href = http://htmlcssjs.ru/JavaScript/?8
location.host = htmlcssjs.ru
location.port =
location.reload = function reload() { [native code] }
location.replace = function replace() { [native code] }
location.assign = function assign() { [native code] }

Источник: http://htmlcssjs.ru/JavaScript/?8

 

,

foreach в javascript

Во несколько способов реализации foreach в javascript

Вариант 1:

for (var key in some_array) {
            var val = some_array [key];
	    alert (key+' = '+val);	
}

Вариант 2:

for(i=0, c=arr.length; i&lt;c; i++) {
	my_func(arr[i]);
}

Нужно отметить, что второй вариант не совсем реализует foreach, так как перебираются только значения массива без ключей.

,

14 июля, 2010

Поиск файла по содержимому этого файла

То что давно хотел узнать :)

Вот так можно найти строку по содержимому файла.

$ find /etc -type f -exec grep -Hn "nameserver" {} \; 2&gt;/dev/null

Вот так можно найти все файлы в каталоге /etc в которых есть слово «nameserver»
Опции grep:

H — выводим имя файла со строкой.
n — номер строки в файле.

Ошибки перенаправляем в /dev/null
При желании можно написать вот такой скрипт:

#/bin/sh

dir='.';
if [ -z $1 ] 
then
	echo 'Команду нужно запускать вот так: find.str  [dir]';
else
	if [ -z $2 ]
	then
		dir=$2
	fi
	
	find . -type f -exec grep -Hn $1 {} \; 2>/dev/null
fi

 

,

13 июля, 2010

Unix — пример работы с текстом

Скачал тут недавно книгу из двух текстовых файлов в koi8-r и с переводами строки в стиле Win (^M)

Решил поправить это дело таким вот образом:

$ cat book_p1.txt book_p2.txt | sed 's/\r//' | iconv -f koi8-r -t utf8 > book.txt

Вот так вот :) Unix — это круто!