Толик Панков
hex_laden
............ .................. ................
October 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Толик Панков [userpic]
Запускающий скрипт для tinyproxy

Ставил я не так давно простой и легкий прокси-сервер tinyproxy, и с удивлением не обнаружил в комплекте запускающего (инициализационного) скрипта.
Что это за зверь такой, если кто не знает. Ко многим демонам в Linux в комплекте идет инициализационный скрипт, позволяющий демона запустить, "убить" или перезапустить из консоли командой вида daemonname start (stop, restart) без необходимости вручную отлавливать идентификатор процесса, убивать его командой kill и проверять, завершен ли процесс (или наоборот, стартовал ли он). К squid, например, такой скрипт идет, называется (в Slackware) rc.squid и лежит в /etc/rc.d, а к tinyproxy в комплекте не шло, но написать его оказалось не так и сложно.
Итак, скрипт будет получать из командной строки единственный параметр с командой:
start - запускать прокси-сервер
stop - останавливать его
restart - перезапускать (останавливать, а после остановки запускать)
status - отображать, запущен или не запущен прокси.

Определяем основные переменные
#!/bin/bash

PIDFILE="/home/provproxy/tinyproxy.pid"
TINYPROXYCMD="/usr/sbin/tinyproxy"
PIDVAL=0
WTIMEOUT=30
OK=0

CH_S[0]='-' #pseudographic items
CH_S[1]='/'
CH_S[2]='|'
CH_S[3]='\'
ITEM_ARR=0 #current item counter

PIDFILE - переменная, в которой указан PID-файл, файл, содержащий идентификатор основного процесса прокси-сервера. Tinyproxy при запуске создает сразу несколько процессов, позволяющих ему распараллеливать свою внутреннюю работу. Минимальное и максимальное число таких процессов задается в конфигурационном файле /etc/tinyproxy.conf параметрами MinSpareServers и MaxSpareServers, а процесс, идентификатор которого указан в PID-файле основной, управляющий. Ему можно послать сигнал, например, командой kill, и все остальные процессы тоже завершатся. Местоположение PID-файла также указывается в файле /etc/tinyproxy.conf в параметре PidFile. Конечно, правильнее читать сам конфигурационный файл, и выдергивать значение параметра оттуда, ну да ладно, параметр этот перенастраивается нечасто, посему пусть такое решение останется на моей совести -=^_^-=.
TINYPROXYCMD - путь к исполняемому файлу прокси-сервера
PIDVAL - здесь будет храниться значение PID, полученное из PID-файла.
WTIMEOUT - максимальное время ожидания запуска tinyproxy, или его завершения.
OK - флаг, принимающий значение 1 в случае успешного запуска/завершения, или 0 - в случае неуспеха. Вообще-то, можно и без него обойтись, но мне с ним удобнее и нагляднее.

CH_S[0] - CH_S[3], массив с псевдографическими элементами, для украшательства, отображения хода процесса запуска. Подробности про украшательства тут или тут , и если кому не надо, выбросить лишние команды из скрипта - дело нехитрое. Переменная ITEM_ARR предназначена для тех же целей.

Функции

Далее, определим в скрипте несколько функций.

process_status()
{
	тут код -=^_^-=
}

status_proxy()
{
	тут код -=^_^-=
}

start_proxy()
{
	тут код -=^_^-=
}

stop_proxy()
{
	тут код -=^_^-=
}

restart_proxy()
{
	тут код -=^_^-=
}


process_status() - проверяет наличие PID-файла, получает идентификатор процесса, определяет, существует ли процесс в памяти.
status_proxy() - отображает пользователю статус tinyproxy.
start_proxy() - запускает прокси-сервер, сообщает об успехе или неуспехе запуска.
stop_proxy() - останавливает его
restart_proxy() - перезапускает tinyproxy.

Проверка параметров.

Проверять параметры будем при помощи оператора case, после объявления функций.
case "$1" in
    start)
	start_proxy
	;;
    stop)
	stop_proxy
	;;
    restart)
	restart_proxy
	;;
    status)
	status_proxy
	;;
    *)
	echo "Usage: $0 {start|stop|restart|status}"
esac

Если первый параметр, переданный из командной строки start, stop, restart или status - выполняются соответствующие функции, если что-то еще (*) - выводим краткую справку по использованию скрипта. В списке источников в конце заметки есть ссылка на более подробное описание оператора.

Статус процесса

Получается функцией process_status(). Вот ее код:
    #Получаем PID
    if [ -e $PIDFILE ];then #если файл существует
	PIDVAL=`cat $PIDFILE` #читаем PID
	TMPGREP=`ps -p $PIDVAL|grep "tinyproxy" -c` #процесс запущен - 1 иначе 0
	if [ $TMPGREP -ge 1 ];then #процесс запущен
	    return #выходим из функции
	else #pid-файл есть, процесса нет
	    rm $PIDFILE #удаляем pid-файл
	fi
    fi
    PIDVAL=0

Сначала проверяется наличие PID-файла, если он существует, отправляем его содержимое (конструкция ``), прочитанное с помощью команды cat в переменную PIDVAL, далее, запрашиваем информацию о процессе (ps) по его PID (ключ -p). Если процесс существует, команда ps отправит на стандартный вывод что-то типа:

PID TTY TIME CMD
2100 ? 00:00:00 tinyproxy

А если процесса не существует:

PID TTY TIME CMD

Далее, этот вывод передается команде grep, которая фильтрует строки с именем искомого процесса (tinyproxy) и подсчитывает их количество (ключ -c). Если строк 1 процесс с данным PID существует, если 0 - процесса нет.
Если PID-файл существует, то в переменной $PIDVAL остается идентификатор процесса и происходит выход из функции
[...]
if [ $TMPGREP -ge 1 ];then #процесс запущен
	    return #выходим из функции

[...]

Если PID-файл существует, а процесс с данным PID не обнаружен, значит в PID-файле указан не тот PID, что, в большинстве случаев, может произойти из-за падения программы (с tinyproxy это случается довольно редко), либо из-за общего системного сбоя вызванного, например, отключением питания. Поэтому стоит PID-файл удалить.
Если процесс не обнаружен, или PID-файл не найден, то происходит выход из всех условных конструкций и переменной $PIDVAL присваивается значение 0
[...]
PIDVAL=0
[...]

Таким образом, если процесс существует, то в переменной $PIDVAL будет присутствовать его идентификатор, если не существует - значение 0.

Примечание о безопасности и стабильности использования PID-файлов
. Конечно, есть более универсальный способ найти процесс не обращаясь к PID-файлу, например, получить список процессов командой ps ax и отgrep'ать его, найдя нужное нам имя, и, если надо, то завершить его командой pkill имя_процесса, и данный способ весьма неплохо будет работать с тем же tinyproxy.
Но tinyproxy - это просто web-прокси сервер, и неизвестно, что произойдет, если применить такой метод, например, к серверу баз данных. Возможно, какие-то транзакции не завершатся, порушится сама база. Поэтому, информации, сохраняемой программами в PID-файлах, стоит доверять. Безопасность их использования и контроль доступа к ним других пользователей должны решаться другими средствами ОС. Посему я не вижу смысла загромождать скрипт дополнительными проверками.
За пояснение благодарю [info]ketmar

Отображение статуса tinyproxy

Если функция process_status() используется для получения статуса (и PID) процесса для внутренних целей скрипта, то функция status_proxy() выводит информацию пользователю. И хоть она очень проста, но лучше вынести ее отдельно, дабы не смешивать взаимодействие с пользователем со внутренней механикой программы.

Вот код этой функции:
status_proxy()
{
    process_status
    if [ $PIDVAL -eq 0 ]; then
	echo "Tinyproxy not running"
    else
	echo "Tinyproxy running [PID=$PIDVAL]"
    fi
}

Как я и говорил, функция очень проста, сначала вызывается функция process_status, и если в переменной $PIDVAL значение 0, то выводится сообщение о том, что tinyproxy не запущен, иначе, что запущен и дополнительно выводится его PID.

Запуск прокси-сервера

Выполняет его функция start_proxy(). Вот ее код:
start_proxy()
{
    process_status
    if [ "$PIDVAL" -ne 0  ]; then
	echo "Tinyproxy already work! [$PIDVAL]"
	return 1
    fi
    $TINYPROXYCMD
    
    RETCODE=$?
    if [ "$RETCODE" -ne 0 ]; then
	echo "Not starting (code $RETCODE)"
	return 1
    fi
    
    echo -n "Starting proxy ("$WTIMEOUT" secounds): "
    tput sc #save cursor position

    while [ $WTIMEOUT -ge 0 ]; do
    
	#print timeout and current pseudographic char
	printf '%3s %s' $WTIMEOUT ${CH_S[ITEM_ARR]}
	tput rc #restore cursor position
	sleep 1
	process_status
	if [ $PIDVAL -ne 0 ]; then #zapustili
    	    OK=1
    	    break
	fi

    
	#decrease timeout and increase current item ctr.
	let "WTIMEOUT=WTIMEOUT-1"
	let "ITEM_ARR=ITEM_ARR+1"
    
	if [ $ITEM_ARR -eq 4 ];then 
	    #if items ctr > number of array items
	    #starting with 0 item
	    let "ITEM_ARR=0"
	fi
        
    done

    #next message starting with new string
    printf '\n'
    
    if [ "$OK" -eq 1 ]; then
	echo "Started [$PIDVAL]"
    else
	echo "Not started :("
    fi
}

Команды для украшения отображения процесса вывода на экран я комментировать не буду. Им, как говорилось выше, посвящена отдельная заметка. Сначала необходимо получить статус процесса, вызвав функцию process_status.
Далее выполняется проверка статуса:
if [ "$PIDVAL" -ne 0  ]; then
	echo "Tinyproxy already work! [$PIDVAL]"
	return 1
fi

Если в переменной $PIDVALприсутствует какое-то значение, то будем считать, что процесс запущен. Выводим соответствующее сообщение и выходим из функции.
Иначе будет запущен процесс, путь к которому указан в переменной $TINYPROXYCMD
Далее необходимо сохранить код возврата запускаемого процесса, передаваемый скрипту в специальной переменной $? в переменную RETCODE:
RETCODE=$?
Далее код возврата проверяется. Если он равен 0, значит, процесс удалось запустить (хотя еще не факт, что он далее будет работать корректно), если же код отличен от 0, выводим код на консоль в соответствующем сообщении и завершаем работу.
if [ "$RETCODE" -ne 0 ]; then
	echo "Not starting (code $RETCODE)"
	return 1
fi

Например, если файл tinyproxy отсутствует по указанному в переменной пути, то код возврата будет равен 127 [5]
Далее выводим сообщение о том, что процесс был запущен, и в цикле
while [ $WTIMEOUT -ge 0 ]; do
[...]
done
проверяем статус процесса:
process_status
	if [ $PIDVAL -ne 0 ]; then #zapustili
    	    OK=1
    	    break
	fi
Как только функция process_status получит идентификатор процесса прокси-сервера, флагу OK будет присвоено значение 1, а цикл будет прерван командой break. Если же процесс так и не запустится, например, потому что необходимые серверу порты заблокированы файерволлом, то цикл завершится по истечении определенного времени.
Остальные команды в цикле как раз и предназначены для управления отсчетом времени и отображения данного процесса на экране.
После цикла остается проверить состояние флага OK
if [ "$OK" -eq 1 ]; then
	echo "Started [$PIDVAL]"
    else
	echo "Not started :("
    fi

и вывести соответствующее сообщение для пользователя.

ПРИМЕЧАНИЕ:
Если прокси не запускается, то для поиска неисправности удобно запустить прокси-сервер с ключом -d
tinyproxy -d
В таком случае прокси запустится не в виде фонового процесса (демона), а в виде обычного, и выдаст ошибки на консоль. Например, если на файерволе закрыты порты, необходимые серверу, будет выведено сообщение:
tinyproxy: Could not create listening socket.

Остановка прокси-сервера

За остановку прокси-сервера отвечает функция stop_proxy(). Ее код:
stop_proxy()
{
    process_status
    if [ $PIDVAL -eq 0 ]; then
	echo "Tinyproxy not work!"
	return 1
    fi

    echo -n "Stopping proxy [$PIDVAL] in $WTIMEOUT secounds: "
    
    tput sc #save cursor position

    while [ $WTIMEOUT -ge 0 ]; do
    
	#print timeout and current pseudographic char
	printf '%3s %s' $WTIMEOUT ${CH_S[ITEM_ARR]}
	tput rc #restore cursor position
	kill $PIDVAL
	sleep 1
	process_status
	if [ $PIDVAL -eq 0 ]; then #ubit
	    OK=1
	    break
	fi
    
    
	#decrease timeout and increase current item ctr.
	let "WTIMEOUT=WTIMEOUT-1"
	let "ITEM_ARR=ITEM_ARR+1"
    
	if [ $ITEM_ARR -eq 4 ];then 
	    #if items ctr > number of array items
	    #starting with 0 item
	    let "ITEM_ARR=0"
	fi
        
    done

    #next message starting with new string
    printf '\n'
    
    if [ "$OK" -eq 1 ]; then
	echo "Stopped!"
    else
	echo "Not stopped :("
    fi
}

Алгоритм похож на функцию запуска, только наоборот :)
Сначала получаем статус процесса функцией process_status() и проверяем значение переменной $PIDVAL. Если 0, то значит процесс и не запущен, о чем сообщаем и выходим из функции:

    process_status
    if [ $PIDVAL -eq 0 ]; then
	echo "Tinyproxy not work!"
	return 1
    fi

Иначе выводим необходимые сообщения и в цикле
while [ $WTIMEOUT -ge 0 ]; do
[...]
done
останавливаем процесс:
	kill $PIDVAL
	sleep 1
	process_status
	if [ $PIDVAL -eq 0 ]; then #ubit
	    OK=1
	    break
	fi
Посылаем команду kill с аргументом идентификатором процесса из переменной $PIDVAL, ждем 1 секунду, после чего запрашиваем и проверяем статус. Если в переменной $PIDVAL 0, значит, процесс успешно убит. Устанавливаем флаг OK и прерываем цикл.
После цикла проверяем состояние флага и выводим соответствующее сообщение:
    if [ "$OK" -eq 1 ]; then
	echo "Stopped!"
    else
	echo "Not stopped :("
    fi


Перезагрузка прокси-сервера.
Тут вообще все просто.
1. Запускаем функцию остановки stop_proxy.
2.
Сбрасываем флаг: $OK=0
Выполняем функцию запуска start_proxy

Вот нехитрый код функции restart_proxy():
restart_proxy()
{
    stop_proxy
    OK=0
    start_proxy
}


Скачать скрипт

С Pastebin
С Mega.nz

Список источников

1.Bash. Функции.
2.Возврат значений из функции
3.Оператор case
4.Tinyproxy
5.Коды завершения, имеющие предопределенный смысл

Это репост заметки из моего блога на сайте http://tolik-punkoff.com
Оригинал заметки находится здесь: http://tolik-punkoff.com/2016/11/05/zapuskayushhij-skript-dlya-tinyproxy/