Ставил я не так давно простой и легкий прокси-сервер 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-файлах, стоит доверять. Безопасность их использования и контроль доступа к ним других пользователей должны решаться другими средствами ОС. Посему я не вижу смысла загромождать скрипт дополнительными проверками.
За пояснение благодарю ketmar
Если функция
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/zapu