Для корректной цифровой подписи необходимо сгенерировать gpg-ключ командой
gpg --gen-key
В консольном режиме у меня его сгенерировать не вышло(требует поделать что-нибудь на машине при генерации, а это знаете ли проблема при подключении по ssh к удаленной машине).
Однако я сгенерировал его на виртуалке, а после перекинул папку .gnupg на удаленную машину в профиль пользователя ( /home/user/ ). Далее командой
gpg --list-key
проверяем ключ (первый раз он попросит ввести пароль, который вы указали при генерации ключа).
Получаем в вывод что-то типа такого:
/home/user/.gnupg/pubring.gpg -------------------------------- pub 4096R/6G797EDE 2017-12-22 uid Sergei Surname (tel: <+375000000000 reserved: <test@gmail.com>) <admin@sergdudko.tk> sub 4096R/1A0D19A1 2017-12-22
Берем отсюда uid и пишем его команщду экспорта:
gpg --export -a "Sergei Surname (tel: <+375290000000 reserved: <test@sergdudko.tk>) <admin@sergdudko.tk>" > RPM-GPG-KEY
Импортируем ключ командой:
sudo rpm --import RPM-GPG-KEY
Этот же ключ необходимо предоставить вместе с будущим приложением (например на сайте) для подтверждения подписи.
Проверяем, что ключ импортирован (ищем в списке) командой:
rpm -qi gpg-pubkey | grep Summary
Для сборки пакета нам понадобится пакет rpm-build, установим его командой:
yum install rpm-build -yum
Далее в профиле пользователя (все в упор рекомендуют это делать не из под рута, я собирал и под рутом, но сейчас так не делаю. почему? а хз, просто не делаю, ничего страшного под рутом не случалось) создаем структуру каталогов:
rpmbuild/ +BUILD/ +BUILDROOT/ +RPMS/ +SOURCES/ +SPECS/ +SRPMS/
Подготовка закончена. Чтобы наше приложение выполняло что-нибудь полезное займемся исполняемыми файлами. Я решил запилить пакет для автоматического развертывания vpn-серверов. Т.к. в баше я сильно не ковырялся, а в системе уже стоит интерпретатор php и сам php я немного знаю (пысы: ну возьмите меня кто-нибудь джуном за адекватные бабки) на нем и напишу.
Для начала возьмем пакеты из репозитория epel: openvpn и easy-rsa. Первый собственно будет сервером, второй работает с сертификатами, без которых сервер не развернуть (это отнюдь не означает, что их нужно генерировать на одной и той же машине).
После этого попробовали настроить сервер привычным способом. Для настройки я буду использовать tap-устройство для поддержки полноценного Ethernet (отличия tun и tap думаю и сами нагуглите). Тем более у меня есть купленное приложение для андроида с поддержкой tap (OpenVPN Client собственно) и рут ему не нужен.
Первым делом в любом случае необходимо сгенерировать сертификаты сервера, его конфигурационный файл, ну и собственно запустить сервер. Не забудьте разрешить нужные порты в фаерволе например так
firewall-cmd --zone=public --add-port=1700-1955/tcp
После того как проделал создание сервера вручную, описал это в скрипте:
<?php //php /etc/openvpn/scripts/build_server.php --args -add 1 //php /etc/openvpn/scripts/build_server.php --args -remove 1 include(__DIR__ . '/settings.php'); $current = 'php /etc/openvpn/scripts/build_server.php --args '.$argv[2].' '.$argv[3] . PHP_EOL; if(!file_exists('/etc/openvpn/dudko-web-panel/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/', 0755, true)) { echo 'Не удалось создать каталог .../dudko-web-panel/!'; exit; } } if(!is_dir('/etc/openvpn/dudko-web-panel/logs')){ mkdir('/etc/openvpn/dudko-web-panel/logs');} $file = '/etc/openvpn/dudko-web-panel/logs/server'.$argv[3].date("Y-m-d").'.log'; if(file_exists($file)){ $current .= file_get_contents($file); } if($argv[2]=='-add'){ $db_connect = new mysqli($db_ipaddr, $db_user, $db_pass, $db_name); if ($db_connect->connect_errno) { echo "Не удалось подключиться к MySQL:" . $db_connect->connect_error; $current .= date("Y-m-d H:i:s").' '."Не удалось подключиться к MySQL:" . $db_connect->connect_error; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } $db_connect->query("SET NAMES utf8"); $result = $db_connect->query("SELECT * FROM `server_conf` WHERE `Id` = ".strval($argv[3]).""); while($row = $result->fetch_assoc()){ $variable = $row; } $db_connect->close(); if(!isset($variable['Id'])){ echo "Сервер отсутствует в MySQL!"; $current .= date("Y-m-d H:i:s").' '."Сервер отсутствует в MySQL!"; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } CreateServer('server'.$variable['Id'], intval($variable['Id']), $current, $variable); } if($argv[2]=='-remove'){ RemoveServer('server'.$argv[3], $current); } $current .= PHP_EOL; file_put_contents($file, $current); echo PHP_EOL; exit; function cmd_exec($operation, $cmd, &$stdout, &$stderr) { $outfile = tempnam(".", "cmd"); $errfile = tempnam(".", "cmd"); $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("file", $outfile, "w"), 2 => array("file", $errfile, "w") ); $proc = proc_open($cmd, $descriptorspec, $pipes); if (!is_resource($proc)) return 255; if($operation == 'ca'){ for($i=0;$i<8;$i++){ //8 переходов на новую строку для CA fwrite($pipes[0], "\n"); usleep(200); } } if($operation == 'server'){ for($i=0;$i<10;$i++){ //10 переходов на новую строку для SERVER, потом два раза yes и переход fwrite($pipes[0], "\n"); usleep(200); } sleep(3); fwrite($pipes[0], "y\n"); usleep(500); fwrite($pipes[0], "y\n"); } fclose($pipes[0]); $exit = proc_close($proc); $stdout = file($outfile); $stderr = file($errfile); unlink($outfile); unlink($errfile); return $exit; } function CreateServer($servername, $servernum, &$log, $variable){ /* предустановки */ if(!file_exists('/etc/openvpn/dudko-web-panel/settings/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/settings/', 0755, true)) { echo 'Не удалось создать каталог .../settings/!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось создать каталог .../settings/!'; $log .= PHP_EOL; return; } } if(!file_exists('/etc/openvpn/dudko-web-panel/rsa-key/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/rsa-key/', 0755, true)) { echo 'Не удалось создать каталог .../rsa-key/!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось создать каталог .../rsa-key/!'; $log .= PHP_EOL; return; } } if(!file_exists('/etc/openvpn/dudko-web-panel/easy-rsa/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/easy-rsa/', 0755, true)) { echo 'Не удалось создать каталог .../easy-rsa/!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось создать каталог .../easy-rsa/!'; $log .= PHP_EOL; return; } else { exec('cp -rp /usr/share/easy-rsa/2.0/* /etc/openvpn/dudko-web-panel/easy-rsa/', $callback); if(count($callback) != 0) { echo 'Возникли неполадки при копировании, выполните команду "cp -rp /usr/share/easy-rsa/2.0/* /etc/openvpn/dudko-web-panel/easy-rsa/" в shell!'; $log .= date("Y-m-d H:i:s").' '.'Возникли неполадки при копировании, выполните команду "cp -rp /usr/share/easy-rsa/2.0/* /etc/openvpn/dudko-web-panel/easy-rsa/" в shell!'; $log .= PHP_EOL; }; } } if(!file_exists('/etc/openvpn/dudko-web-panel/logs/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/logs/', 0755, true)) { echo 'Не удалось создать каталог .../logs/!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось создать каталог .../logs/!'; $log .= PHP_EOL; return; } } /* проверяем зависимости */ if(file_exists('/etc/openvpn/dudko-web-panel/settings/'.$servername.'/')) { echo 'Сервер уже существует(settings)!'; $log .= date("Y-m-d H:i:s").' '.'Сервер уже существует(settings)!'; $log .= PHP_EOL; return; } if(file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/')) { echo 'Сервер уже существует(rsa-key)!'; $log .= date("Y-m-d H:i:s").' '.'Сервер уже существует(rsa-key)!'; $log .= PHP_EOL; return; } if(file_exists('/etc/openvpn/dudko-web-panel/easy-rsa/vars-'.$servername)) { echo 'Сервер уже существует(vars)!'; $log .= date("Y-m-d H:i:s").' '.'Сервер уже существует(vars)!'; $log .= PHP_EOL; return; } if(file_exists('/etc/openvpn/'.$servername.'.conf')) { echo 'Сервер уже существует(config)!'; $log .= date("Y-m-d H:i:s").' '.'Сервер уже существует(config)!'; $log .= PHP_EOL; return; } /* настраиваем каталоги сервера */ if(!mkdir('/etc/openvpn/dudko-web-panel/settings/'.$servername.'/', 0755, true)) { echo 'Не удалось создать каталог .../settings/'.$servername.'/!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось создать каталог .../settings/'.$servername.'/!'; $log .= PHP_EOL; return; } if(!mkdir('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/', 0755, true)) { echo 'Не удалось создать каталог .../rsa-key/'.$servername.'/!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось создать каталог .../rsa-key/'.$servername.'/!'; $log .= PHP_EOL; return; } /* конфигурационный файл генерации сертификатов*/ $vars_file = 'export EASY_RSA="`pwd`"' . PHP_EOL . 'export OPENSSL="openssl"' . PHP_EOL . 'export PKCS11TOOL="pkcs11-tool"' . PHP_EOL . 'export GREP="grep"' . PHP_EOL . 'export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`' . PHP_EOL . 'export KEY_DIR="/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/"' . PHP_EOL . 'export PKCS11_MODULE_PATH="dummy"' . PHP_EOL . 'export PKCS11_PIN="dummy"' . PHP_EOL . 'export KEY_SIZE='.$variable['KEY_SIZE'].'' . PHP_EOL . 'export CA_EXPIRE='.$variable['CA_EXPIRE'].'' . PHP_EOL . 'export KEY_EXPIRE='.$variable['KEY_EXPIRE'].'' . PHP_EOL . 'export KEY_COUNTRY="'.$variable['KEY_COUNTRY'].'"' . PHP_EOL . 'export KEY_PROVINCE="'.$variable['KEY_PROVINCE'].'"' . PHP_EOL . 'export KEY_CITY="'.$variable['KEY_CITY'].'"' . PHP_EOL . 'export KEY_ORG="'.$variable['KEY_ORG'].'"' . PHP_EOL . 'export KEY_EMAIL="'.$variable['KEY_EMAIL'].'"' . PHP_EOL . 'export KEY_OU="'.$variable['KEY_OU'].'"' . PHP_EOL . 'export KEY_NAME="'.$variable['KEY_NAME'].'"' . PHP_EOL . 'export KEY_CN="'.$variable['KEY_CN'].'"' . PHP_EOL . 'export KEY_ALTNAMES="'.$variable['KEY_ALTNAMES'].'"' . PHP_EOL . 'echo OK: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR' . PHP_EOL; if(!file_put_contents('/etc/openvpn/dudko-web-panel/easy-rsa/vars-'.$servername, $vars_file)){ echo 'Не удалось записать файл vars-'.$servername.'!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось записать файл vars-'.$servername.'!'; $log .= PHP_EOL; return; } chmod('/etc/openvpn/dudko-web-panel/easy-rsa/vars-'.$servername, 0755); cmd_exec('ca', 'cd /etc/openvpn/dudko-web-panel/easy-rsa/ && source ./vars-'.$servername .' && ./clean-all && ./build-ca', $callback, $err); unset($callback);unset($err); cmd_exec('server','cd /etc/openvpn/dudko-web-panel/easy-rsa/ && source ./vars-'.$servername .' && ./build-key-server server', $callback, $err); $result_req = substr($err[31], 0, 17); //Data Base Updated unset($callback);unset($err); cmd_exec('dh','cd /etc/openvpn/dudko-web-panel/easy-rsa/ && source ./vars-'.$servername .' && ./build-dh', $callback, $err); unset($callback);unset($err); exec('openvpn --genkey --secret /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ta.key', $callback); /* конфигурационный файл сервера */ $config_file = 'mode server' .PHP_EOL . 'tls-server' .PHP_EOL . 'proto tcp-server' .PHP_EOL . 'dev tap' .PHP_EOL . 'port '.strval(1700 + $servernum).' # Порт' .PHP_EOL . 'daemon' .PHP_EOL . 'tls-auth /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ta.key 0' .PHP_EOL . 'ca /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ca.crt' .PHP_EOL . 'cert /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/server.crt' .PHP_EOL . 'key /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/server.key' .PHP_EOL . 'dh /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/dh2048.pem' .PHP_EOL . 'ifconfig-pool-persist /etc/openvpn/dudko-web-panel/settings/'.$servername.'/ipp.txt' .PHP_EOL . 'ifconfig 13.'.strval($servernum).'.0.1 255.255.255.0 # Внутренний IP сервера' .PHP_EOL . 'ifconfig-pool 13.'.strval($servernum).'.0.30 13.'.strval($servernum).'.0.255 # Пул адресов.' .PHP_EOL . 'client-to-client' .PHP_EOL . 'client-config-dir /etc/openvpn/dudko-web-panel/settings/'.$servername.'/' .PHP_EOL . 'push "route-gateway 13.'.strval($servernum).'.0.1"' .PHP_EOL . 'duplicate-cn' .PHP_EOL . 'verb 1' .PHP_EOL . 'cipher '.$variable['cipher'].' # Тип шифрования.' .PHP_EOL . 'persist-key' .PHP_EOL . 'log-append /etc/openvpn/dudko-web-panel/settings/'.$servername.'/openvpn.log # Лог-файл.' .PHP_EOL . 'persist-tun' .PHP_EOL . 'comp-lzo' .PHP_EOL; if(!file_put_contents('/etc/openvpn/'.$servername.'.conf', $config_file)){ echo 'Не удалось записать файл '.$servername.'.conf!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось записать файл '.$servername.'.conf!'; $log .= PHP_EOL; return; } chmod('/etc/openvpn/'.$servername.'.conf', 0755); $serverstring = '/etc/openvpn/dudko-web-panel/rsa-key/'.$servername; if(($result_req == 'Data Base Updated') && file_exists($serverstring.'/ca.crt') && file_exists($serverstring.'/ta.key') && file_exists($serverstring.'/server.crt') && file_exists($serverstring.'/server.key') && file_exists($serverstring.'/dh2048.pem') && file_exists('/etc/openvpn/'.$servername.'.conf')){ $result = exec('systemctl start openvpn@'.$servername, $callback); if($result == ""){ cmd_exec('systemctl','systemctl enable openvpn@'.$servername, $callback, $err); if(substr($err[0], 0, 7) == 'Created'){ unset($callback);unset($err); echo 'Сервер успешно создан!'; $log .= date("Y-m-d H:i:s").' '.'Сервер успешно создан!'; $log .= PHP_EOL; return; } unset($callback);unset($err); echo 'Не удалось добавить юнит в автозагрузку!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось добавить юнит в автозагрузку!'; $log .= PHP_EOL; return; } echo 'Не удалось запустить сервер!'; $log .= date("Y-m-d H:i:s").' '.'Не удалось запустить сервер!'; $log .= PHP_EOL; return; } else { echo 'Произошла ошибка в конфигурации сервера!'; $log .= date("Y-m-d H:i:s").' '.'Произошла ошибка в конфигурации сервера!'; $log .= PHP_EOL; RemoveServer($servername, $log2); $log .= $log2; return; } } function RemoveServer($servername, &$log2){ exec('rm -rf /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ y'); sleep(2); exec('rm -rf /etc/openvpn/dudko-web-panel/settings/'.$servername.'/ y'); sleep(2); exec('rm -rf /etc/openvpn/dudko-web-panel/easy-rsa/vars-'.$servername.' y'); exec('rm -rf /etc/openvpn/'.$servername.'.conf y'); exec('systemctl stop openvpn@'.$servername); exec('systemctl disable openvpn@'.$servername); echo 'Конфигурационные файлы удалены!'; $log2 .= date("Y-m-d H:i:s").' '.'Конфигурационные файлы удалены!'; $log2 .= PHP_EOL; return; } exit; ?>
Для функционирования скрипта необходимо создать таблицу в MySQL:
CREATE TABLE `server_conf` ( `Id` INT(11) NOT NULL AUTO_INCREMENT, `dev` VARCHAR(255) DEFAULT NULL, `hostname` VARCHAR(255) DEFAULT NULL, `cipher` VARCHAR(255) DEFAULT NULL, `KEY_SIZE` VARCHAR(255) DEFAULT NULL, `CA_EXPIRE` VARCHAR(255) DEFAULT NULL, `KEY_EXPIRE` VARCHAR(255) DEFAULT NULL, `KEY_COUNTRY` VARCHAR(255) DEFAULT NULL, `KEY_PROVINCE` VARCHAR(255) DEFAULT NULL, `KEY_CITY` VARCHAR(255) DEFAULT NULL, `KEY_ORG` VARCHAR(255) DEFAULT NULL, `KEY_EMAIL` VARCHAR(255) DEFAULT NULL, `KEY_OU` VARCHAR(255) DEFAULT NULL, `KEY_NAME` VARCHAR(255) DEFAULT NULL, `KEY_CN` VARCHAR(255) DEFAULT NULL, `KEY_ALTNAMES` VARCHAR(255) DEFAULT NULL, PRIMARY KEY (`Id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
С начинкой типа:
INSERT INTO `server_conf` VALUES (1,'tap','vpn.sergdudko.tk','DES-EDE3-CBC','2048','3650','1095','BY','MSQ','Minsk','Name Surname Service','admin@sergdudko.tk','VPN Service','Searhei Surname Key','sergdudko.tk','VPNSERGDUDKOTK');
Собственно в начинке стандартные параметры конфигурации OpenVPN сервера. Т.к. наш скрипт консольный, аргументы задаются параметром —args и в скрипте будут выглядеть как массив argv[].
Первое что делает скрипт, это разбирает аргументы на два вида: добавить сервер и удалить сервер. После этого вызывает соответствующую функцию.
Функция CreateServer создает сертификаты и конфигурационный файл, запускает сервер и добавляет его в атозагрузку. Т.к. для генерации сертификатов необходимо выполнять некоторые инъекции в shell, что невозможно осуществить стандартной exec (аргументы задаются до выполнения команды, в процессе же выполнения они недоступны), была написана функция cmd_exec, которая общается с shell по средствам потоков.
Функция RemoveServer удаляет все файлы сервера (сертификаты, в т.ч. клиентские, конфигурационный файл, логи), останавливает его и удаляет из автозагрузке. Она также вызывается в случае ошибки при создании сервера.
Следующее это создать сертификаты клиентов. Проделал эту операцию в shell и воспроизвел в скрипте:
<?php //php /etc/openvpn/scripts/build_client.php --args -add 1 3 test@email.by include(__DIR__ . '/settings.php'); $current = 'php /etc/openvpn/scripts/build_client.php --args '.$argv[2].' '.$argv[3].' '.$argv[4]; if(isset($argv[5])){ $current .= ' '.$argv[5]; } $current .= PHP_EOL; if(!file_exists('/etc/openvpn/dudko-web-panel/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/', 0755, true)) { echo 'Не удалось создать каталог .../dudko-web-panel/!'; exit; } } if(!is_dir('/etc/openvpn/dudko-web-panel/logs')){ mkdir('/etc/openvpn/dudko-web-panel/logs');} $file = '/etc/openvpn/dudko-web-panel/logs/server'.$argv[3].date("Y-m-d").'.log'; if(file_exists($file)){ $current .= file_get_contents($file); } $servername = 'server'.$argv[3]; if(!file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/')) { echo 'Отсутствует каталог .../rsa-key/'.$servername.'/!'; $current .= date("Y-m-d H:i:s").' '.'Отсутствует каталог .../rsa-key/'.$servername.'/!'; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } if(!file_exists('/etc/openvpn/dudko-web-panel/settings/'.$servername.'/')) { echo 'Отсутствует каталог .../settings/'.$servername.'/!'; $current .= date("Y-m-d H:i:s").' '.'Отсутствует каталог .../settings/'.$servername.'/!'; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } if(!file_exists('/etc/openvpn/dudko-web-panel/easy-rsa/')) { echo 'Отсутствует каталог .../easy-rsa/!'; $current .= date("Y-m-d H:i:s").' '.'Отсутствует каталог .../easy-rsa/!'; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } if(!file_exists('/etc/openvpn/dudko-web-panel/easy-rsa/vars-'.$servername)) { echo 'Отсутствует файл конфигурации .../easy-rsa/vars-'.$servername.'!'; $current .= date("Y-m-d H:i:s").' '.'Отсутствует файл конфигурации .../easy-rsa/vars-'.$servername.'!'; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } if($argv[2] == '-add'){ $clientstring = '/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/client'.$argv[4].'.'; if(file_exists($clientstring.'key') || file_exists($clientstring.'crt')) { echo 'Для клиента уже создан сертификат!'; $current .= date("Y-m-d H:i:s").' '.'Для клиента уже создан сертификат!'; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } CreateClient($argv[3], $current, $argv[4], $argv[5]); } if($argv[2] == '-remove'){ $clientstring = '/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/client'.$argv[4].'.'; if(!file_exists($clientstring.'key') || !file_exists($clientstring.'crt')) { echo 'Для клиента отсутствует сертификат!'; $current .= date("Y-m-d H:i:s").' '.'Для клиента отсутствует сертификат!'; $current .= PHP_EOL; echo PHP_EOL; file_put_contents($file, $current); exit; } RemoveClient($argv[3], $current, $argv[4]); } echo PHP_EOL; $current .= PHP_EOL; file_put_contents($file, $current); exit; function cmd_exec($usermail, $cmd, &$stdout, &$stderr) { $outfile = tempnam(".", "cmd"); $errfile = tempnam(".", "cmd"); $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("file", $outfile, "w"), 2 => array("file", $errfile, "w") ); $proc = proc_open($cmd, $descriptorspec, $pipes); if (!is_resource($proc)) return 255; if($usermail != ''){ for($i=0;$i<7;$i++){ //7 переходов fwrite($pipes[0], "\n"); usleep(200); } fwrite($pipes[0], $usermail."\n"); usleep(200); fwrite($pipes[0], "\n"); usleep(200); fwrite($pipes[0], "\n"); usleep(200); sleep(3); fwrite($pipes[0], "y\n"); usleep(500); fwrite($pipes[0], "y\n"); } fclose($pipes[0]); $exit = proc_close($proc); $stdout = file($outfile); $stderr = file($errfile); unlink($outfile); unlink($errfile); return $exit; } function CreateClient($servernum, &$log, $clientnum, $clientmail){ cmd_exec($clientmail, strval('cd /etc/openvpn/dudko-web-panel/easy-rsa/ && source ./vars-server'.$servernum .' && ./build-key client'.$clientnum), $callback, $err); $result_req = substr($err[31], 0, 17); unset($callback);unset($err); $clientstring = '/etc/openvpn/dudko-web-panel/rsa-key/server'.$servernum.'/client'.$clientnum; if(($result_req == 'Data Base Updated') && file_exists($clientstring.'.key') && file_exists($clientstring.'.crt')){ echo 'Сертификаты успешно созданы!'; exec('systemctl restart openvpn@server'.$servernum); $log .= date("Y-m-d H:i:s").' '.'Сертификаты успешно созданы!'; $log .= PHP_EOL; return; } echo 'Ошибка создания сертификатов!'; $log .= date("Y-m-d H:i:s").' '.'Ошибка создания сертификатов!'; $log .= PHP_EOL; return; } function RemoveClient($servernum, &$log, $clientnum){ $clientstring = '/etc/openvpn/dudko-web-panel/rsa-key/server'.$servernum; if(!file_exists($clientstring.'/crl.pem')){ $new_conf = file_get_contents('/etc/openvpn/server'.$servernum.'.conf'); $new_conf = $new_conf . 'crl-verify ' . $clientstring.'/crl.pem' . PHP_EOL; } cmd_exec('', strval('cd /etc/openvpn/dudko-web-panel/easy-rsa/ && source ./vars-server'.$servernum .' && ./revoke-full client'.$clientnum), $callback, $err); $result_req = substr($err[2], 0, 17); unset($callback);unset($err); if(($result_req == 'Data Base Updated') && file_exists($clientstring.'/crl.pem')){ if(isset($new_conf)){ file_put_contents('/etc/openvpn/server'.$servernum.'.conf', $new_conf); } echo 'Сертификат успешно отозван!'; exec('systemctl restart openvpn@server'.$servernum); $log .= date("Y-m-d H:i:s").' '.'Сертификат успешно отозван!'; $log .= PHP_EOL; return; } echo 'Ошибка отзыва сертификата!'; $log .= date("Y-m-d H:i:s").' '.'Ошибка отзыва сертификата!'; $log .= PHP_EOL; return; } exit; ?>
Тут все аналогично скрипту создания сервера, за исключением двух вещей. Конфигурационный файл клиента мы не создаем, т.к. нет смысла захламлять сервер, будем генерировать его налету. А также, при удалении клиента нам необходимо добавить СОС(список отзыва сертификатов) и добавить строку с настройкой СОС в конфигурационный файл сервера. При этом менять конфигурационный файл сервера нужно только в том случае, если до этого файл СОС не был создан.
Собственно был и обходной путь (мне показался костыльным) — создать клиента и сразу отозвать сертификат, после чего сгенерировать конфиг сервера и уже его запускать. Все дела в том, что если обозначить в файле конфигурации сервера настройку СОС, а сам файл не сгенерировать(как сгенерировать пустой я не нашел, а через «блокнот» не прокатило), сервер не запустится.
Для работы обоих скриптов нам нужны настройки базы данных и даты(если в php.ini не настроена), я, как обычно, вывел эти настройки в отдельный скрипт:
<?php $db_ipaddr = '127.0.0.1'; //адрес БД $db_user = '*******'; //пользователь БД $db_pass = '*******'; //пароль $db_name = '******'; //имя БД date_default_timezone_set('Europe/Minsk'); header('Content-Type: text/text; charset=utf-8'); ?>
Сертификаты готовы, сервер создан. Но клиент должен как-то получить свои сертификаты. Наиболее популярный способ(после скачать) — отправить на email. К тому же отправить на email удобней, с т.з. того, что сертификаты генерируются на консольном сервере и качать особо неоткуда.
Для работы с электронной почтой использую класс SendMailSmtpClass.php, автор класса указан в его составе, расписывать работу не буду — просто приведу исходник:
<?php /** * SendMailSmtpClass * * Класс для отправки писем через SMTP с авторизацией * Может работать через SSL протокол * Тестировалось на почтовых серверах yandex.ru, mail.ru и gmail.com * * @author Ipatov Evgeniy <admin@ipatov-soft.ru> * @version 1.0 */ class SendMailSmtpClass { /** * * @var string $smtp_username - логин * @var string $smtp_password - пароль * @var string $smtp_host - хост * @var string $smtp_from - от кого * @var integer $smtp_port - порт * @var string $smtp_charset - кодировка * */ public $smtp_username; public $smtp_password; public $smtp_host; public $smtp_from; public $smtp_port; public $smtp_charset; public function __construct($smtp_username, $smtp_password, $smtp_host, $smtp_from, $smtp_port = 25, $smtp_charset = "utf-8") { $this->smtp_username = $smtp_username; $this->smtp_password = $smtp_password; $this->smtp_host = $smtp_host; $this->smtp_from = $smtp_from; $this->smtp_port = $smtp_port; $this->smtp_charset = $smtp_charset; } /** * Отправка письма * * @param string $mailTo - получатель письма * @param string $subject - тема письма * @param string $message - тело письма * @param string $headers - заголовки письма * * @return bool|string В случаи отправки вернет true, иначе текст ошибки * */ function send($mailTo, $subject, $message, $headers) { $contentMail = "Date: " . date("D, d M Y H:i:s") . " UT\r\n"; $contentMail .= 'Subject: =?' . $this->smtp_charset . '?B?' . base64_encode($subject) . "=?=\r\n"; $contentMail .= $headers . "\r\n"; $contentMail .= $message . "\r\n"; try { if(!$socket = @fsockopen($this->smtp_host, $this->smtp_port, $errorNumber, $errorDescription, 30)){ throw new Exception($errorNumber.".".$errorDescription); } if (!$this->_parseServer($socket, "220")){ throw new Exception('Connection error'); } $server_name = $_SERVER["SERVER_NAME"]; fputs($socket, "HELO $server_name\r\n"); if (!$this->_parseServer($socket, "250")) { fclose($socket); throw new Exception('Error of command sending: HELO'); } fputs($socket, "AUTH LOGIN\r\n"); if (!$this->_parseServer($socket, "334")) { fclose($socket); throw new Exception('Autorization error'); } fputs($socket, base64_encode($this->smtp_username) . "\r\n"); if (!$this->_parseServer($socket, "334")) { fclose($socket); throw new Exception('Autorization error'); } fputs($socket, base64_encode($this->smtp_password) . "\r\n"); if (!$this->_parseServer($socket, "235")) { fclose($socket); throw new Exception('Autorization error'); } fputs($socket, "MAIL FROM: <".$this->smtp_username.">\r\n"); if (!$this->_parseServer($socket, "250")) { fclose($socket); throw new Exception('Error of command sending: MAIL FROM'); } $mailTo = ltrim($mailTo, '<'); $mailTo = rtrim($mailTo, '>'); fputs($socket, "RCPT TO: <" . $mailTo . ">\r\n"); if (!$this->_parseServer($socket, "250")) { fclose($socket); throw new Exception('Error of command sending: RCPT TO'); } fputs($socket, "DATA\r\n"); if (!$this->_parseServer($socket, "354")) { fclose($socket); throw new Exception('Error of command sending: DATA'); } fputs($socket, $contentMail."\r\n.\r\n"); if (!$this->_parseServer($socket, "250")) { fclose($socket); throw new Exception("E-mail didn't sent"); } fputs($socket, "QUIT\r\n"); fclose($socket); } catch (Exception $e) { return $e->getMessage(); } return true; } private function _parseServer($socket, $response) { while (@substr($responseServer, 3, 1) != ' ') { if (!($responseServer = fgets($socket, 256))) { return false; } } if (!(substr($responseServer, 0, 3) == $response)) { return false; } return true; } }
Ну и собственно мой скрипт отправки файлов:
<?php // php /etc/openvpn/scripts/cert_send_toemail.php --args 1 1 admin@sergdudko.tk $servername = 'server'.$argv[2]; $servernum = $argv[2]; $usermail = $argv[4]; $certificat = 'client'.$argv[3]; /* загружаем настройки и настраиваем логгирование */ include(__DIR__ . '/settings.php'); $current = 'php /etc/openvpn/scripts/cert_send_toemail.php --args '.$argv[2].' '.$argv[3].' '.$argv[4] . PHP_EOL; if(!file_exists('/etc/openvpn/dudko-web-panel/')) { if(!mkdir('/etc/openvpn/dudko-web-panel/', 0755, true)) { echo 'Не удалось создать каталог .../dudko-web-panel/!'; exit; } } if(!is_dir('/etc/openvpn/dudko-web-panel/logs/')){ mkdir('/etc/openvpn/dudko-web-panel/logs/');} $file = '/etc/openvpn/dudko-web-panel/logs/'.$servername.date("Y-m-d").'.log'; if(file_exists($file)){ $current .= file_get_contents($file); } /* проверяем, что аргументы получены, требуемый сертификат имеется на сервере и не принадлежит серверу */ if(!isset($usermail) || !isset($certificat)|| !isset($servername)){ echo 'Некорректные параметры для отправки!'; exit; } if(!file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/'.$certificat.'.key') || !file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/'.$certificat.'.crt')){ echo 'Сертификаты для данного клиента отсутствуют!'; exit; } /* получаем переменные из БД для авторизации на почтовом сервере */ $db_connect = new mysqli($db_ipaddr, $db_user, $db_pass, $db_name); if ($db_connect->connect_errno) { $current .= date("Y-m-d H:i:s").' '."Не удалось подключиться к MySQL:" . $db_connect->connect_error; $current .= PHP_EOL; file_put_contents($file, $current); exit; } $db_connect->query("SET NAMES utf8"); $result = $db_connect->query("SELECT * FROM `post_conf`"); while($row = $result->fetch_assoc()){ $variable[$row['name']] = $row['value']; } $db_connect->close(); /* создаем архив с файлами */ //генерация пароля на архив $passwd = ''; $array = array_merge(range('A','Z'),range('a','z'),range('0','9')); $c = count($array); $longpass = rand(70,100); for($i=0;$i<$longpass;$i++) {$passwd .= $array[rand(0,$c-1)];} //создадим временный файл в памяти $zip_file = '/tmp/zip-cert-'.md5(time()).'.zip'; $config_file = '/tmp/'.$certificat.'.ovpn'; file_put_contents($config_file, generate_config($servernum, $certificat)); //вариант через shell(с паролем) $files_to_zip = ''; if(file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/'.$certificat.'.key')){ $files_to_zip .= $certificat.'.key '; } if(file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/'.$certificat.'.crt')){ $files_to_zip .= $certificat.'.crt '; } if(file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ta.key')){ $files_to_zip .= 'ta.key '; } if(file_exists('/etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ca.crt')){ $files_to_zip .= 'ca.crt '; } $answr = explode(' ', exec('cd /etc/openvpn/dudko-web-panel/rsa-key/'.$servername.'/ && zip '.$zip_file.' '.$files_to_zip.' -P '.$passwd)); if ($answr[2] != 'adding:') { $current .= date("Y-m-d H:i:s").' '."Ошибка создания архива"; $current .= PHP_EOL; unlink($zip_file); unlink($config_file); file_put_contents($file, $current); exit; } if(file_exists($config_file)){ $files_to_zip = $certificat.'.ovpn'; } $answr = explode(' ', exec('cd /tmp/ && zip '.$zip_file.' '.$files_to_zip.' -P '.$passwd)); if ($answr[2] != 'adding:') { $current .= date("Y-m-d H:i:s").' '."Ошибка добавления конфиг-файла в архив"; $current .= PHP_EOL; unlink($zip_file); unlink($config_file); file_put_contents($file, $current); exit; } /* отправка архива на почту */ $_SERVER["SERVER_NAME"] = 'sergdudko.tk'; require_once ("/etc/openvpn/scripts/SendMailSmtpClass.php"); // подключаем класс $mailSMTP = new SendMailSmtpClass($variable['email_user'], $variable['email_pass'], $variable['email_host'], $variable['email_name'], $variable['email_port']); // создаем экземпляр класса $headers= "MIME-Version: 1.0\r\n"; $un = rand(1000,9999); //$headers .= "Content-Type: multipart/alternative;boundary=\"----------".$un."\"".PHP_EOL; // кодировка письма $headers .= "Content-Type: multipart/mixed;boundary=\"----------".$un."\"".PHP_EOL; // кодировка письма $headers .= "Content-Language: ru". PHP_EOL; $headers .= 'From: '.$variable['email_name'].' <'.$variable['email_user'].'>'.PHP_EOL; // от кого письмо $headers .= 'To: '.$usermail.PHP_EOL; // от кого письмо $message = "------------".$un. PHP_EOL . "Content-Type:text/plain; charset=utf-8". PHP_EOL . "Content-Transfer-Encoding: 8bit". PHP_EOL . "Архив с сертификатами во вложении, пароль на архив был предоставлен на сайте.". PHP_EOL . "Обращаю внимание на то, что длинна не все архиваторы поддерживают заданную длинну пароля. Если вы получили сообщение о том, что пароль не верен - попробуйте другим архиватором.". PHP_EOL . "------------".$un. PHP_EOL . "Content-Type: application/octet-stream;name=\"file.zip\"". PHP_EOL . "Content-Transfer-Encoding:base64". PHP_EOL . "Content-Disposition:attachment;filename=\"file.zip\"". PHP_EOL .chunk_split(base64_encode(file_get_contents($zip_file))). PHP_EOL ." ------------".$un."--"; $subject = 'Ключи доступа'.PHP_EOL; $result = $mailSMTP->send($usermail, $subject, $message, $headers); // отправляем письмо if($result === true){ echo 'Сертификаты были успешно отправлены на '. $usermail . ', пароль на архив:' . PHP_EOL . $passwd .PHP_EOL; $current .= date("Y-m-d H:i:s").' '.'Сертификаты были успешно отправлены на '. $usermail .PHP_EOL; }else{ $current .= date("Y-m-d H:i:s").' '.'Попытка отправить сертификаты через резервный ящик!'.PHP_EOL; $mailSMTP_reserve = new SendMailSmtpClass($variable['email_user_reserve'], $variable['email_pass_reserve'], $variable['email_host_reserve'], $variable['email_name'], $variable['email_port_reserve']); // создаем экземпляр класса $result2 = $mailSMTP_reserve->send($usermail, $subject, $message, $headers); // отправляем письмо с резервного ящика if($result2 === true){ echo 'Сертификаты были успешно отправлены на '. $usermail . ', пароль на архив:' . PHP_EOL . $passwd .PHP_EOL; $current .= date("Y-m-d H:i:s").' '.'Сертификаты были успешно отправлены на '. $usermail .PHP_EOL; }else{ echo 'Ошибка отправки сертификатов на '. $usermail . '!' . PHP_EOL; $current .= date("Y-m-d H:i:s").' '.'Ошибка отправки сертификатов на '. $usermail . '!' . PHP_EOL; } } /* Удаляем архив и пишем содержимое лога в файл */ unlink($zip_file); unlink($config_file); $current .= PHP_EOL; file_put_contents($file, $current); function generate_config($server_num, $certificat){ include(__DIR__ . '/settings.php'); $db_connect = new mysqli($db_ipaddr, $db_user, $db_pass, $db_name); if ($db_connect->connect_errno) { $current .= date("Y-m-d H:i:s").' '."Не удалось подключиться к MySQL:" . $db_connect->connect_error; $current .= PHP_EOL; file_put_contents($file, $current); exit; } $db_connect->query("SET NAMES utf8"); $result = $db_connect->query("SELECT * FROM `server_conf` WHERE Id LIKE ".$server_num.""); while($row = $result->fetch_assoc()){ $variable_func = $row; $variable_func['port'] = 1700+intval($row['Id']); } $db_connect->close(); $conf_file = ''; $conf_file .= 'tls-client'.PHP_EOL; $conf_file .= 'proto tcp-client'.PHP_EOL; $conf_file .= 'remote '.$variable_func['hostname'].PHP_EOL; $conf_file .= 'dev '.$variable_func['dev'].PHP_EOL; $conf_file .= 'port '.$variable_func['port'].PHP_EOL; $conf_file .= 'pull '.PHP_EOL; $conf_file .= 'tls-auth ta.key 1'.PHP_EOL; $conf_file .= 'ca ca.crt'.PHP_EOL; $conf_file .= 'cert '.$certificat.'.crt'.PHP_EOL; $conf_file .= 'key '.$certificat.'.key'.PHP_EOL; $conf_file .= 'cipher '.$variable_func['cipher'].PHP_EOL; $conf_file .= 'comp-lzo'.PHP_EOL; return $conf_file; } exit; ?>
Для его работы нужно создать настройки подключения в MySQL(использование БД оправдано, т.к. в перспективе проект думаю дорабатывать и сделать интерфейс):
CREATE TABLE `post_conf` ( `name` VARCHAR(255) NOT NULL DEFAULT '', `value` VARCHAR(255) DEFAULT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
Мой скрипт использует два адреса, основной и резервный(если через один отправить не удалось — пробуем второй). Также он создает конфигурационный файл клиента на лету + упаковывает все файлы в архив. Сама же отправка вложений по электронной почте выглядит так: обязательный заголовок
Content-Type: multipart/mixed;boundary="----------111"
где ———-111 разделитель содержимого. Тело письма делится данным разделителем на в трех местах (начало текста, конец текста, конец вложения). Т.е. по сути у нас имеется две части письма с различными заголовками (см. код выше). Протестировано на основных публичных серверах, типа gmail. В керио, например, такие файлы не пропускает, но это опустим.
Для обслуживания этих трех скриптов создал отдельный, который будет приводить в порядок аргументы из консоли(т.е. их можно будет задавать в любом порядке) и будет изменять файл настроек базы данных и часового пояса. Также этот скрипт будет запускаться из консоли прямой командой(т.е. php перед указанием скрипта можно опустить).
#!/usr/bin/env php <?php if(isset($argv)){ for($i=0;$i<count($argv);$i++){ if($argv[$i] == '-help'){ helper(); } if($argv[$i] == '-settings'){ settings($argv); } $massive = explode(':', $argv[$i]); if(isset($massive[0]) && isset($massive[1])) { $command[$massive[0]] = $massive[1]; } } } if(isset($command['build'])){ $exec = 'php /etc/openvpn/scripts/build_'.$command['build'].'.php --args '; if(!isset($command['server']) || !isset($command['com'])){ helper(); } if($command['build'] == 'server'){ $exec .= '-'.$command['com'] . ' ' . $command['server']; } if($command['build'] == 'client'){ if(!isset($command['client'])){ helper(); } if($command['com'] != 'remove'){ if(!isset($command['email'])){ helper(); } } $exec .= '-'.$command['com'] . ' ' . $command['server'] . ' ' . $command['client']; if(isset($command['email'])){ $exec .= ' ' . $command['email']; } } } if(isset($command['send'])){ if($command['send'] == 'toemail'){ if(!isset($command['server']) || !isset($command['email']) || !isset($command['client'])){ helper(); } $exec = 'php /etc/openvpn/scripts/cert_send_'.$command['send'].'.php --args '.$command['server'].' '.$command['client'].' '.$command['email'].''; } } if(isset($exec)){ exec($exec, $callback); for($i=0;$i<count($callback);$i++){ echo $callback[$i]; if(($i+1)<count($callback)){ echo PHP_EOL; } } echo PHP_EOL; } else { helper(); } function helper(){ echo 'Доступные аргументы:' .PHP_EOL; echo 'Вызов справки: -help' .PHP_EOL; echo 'Создать/удалить openvpn сервер: build:server com:add(или remove) server:1(численный номер сервера)' .PHP_EOL; echo 'Создать/удалить сертификаты клиента: build:client com:add(или remove) server:1(численный номер сервера, к которому будет привязан клиент) email:admin@sergdudko.tk client:1(численный номер клиентского сертификата)' .PHP_EOL; echo 'Отправить сертификаты клиента на email: send:toemail(отправка на email) server:1(численный номер сервера, к которому будет привязан клиент) email:admin@sergdudko.tk client:1(численный номер клиентского сертификата)' .PHP_EOL; echo 'Для настройки программы, наберите -settings' .PHP_EOL; echo PHP_EOL; exit; } function settings($argv){ for($i=0;$i<count($argv);$i++){ if($argv[$i] != '-settings'){ $massive = explode(':', $argv[$i]); if(isset($massive[0]) && isset($massive[1])) { $command[$massive[0]] = $massive[1]; } } } $settings_file = "/etc/openvpn/scripts/settings.php"; if(!file_exists($settings_file)){ echo 'Файл с настройками не найден!' . PHP_EOL; exit; } $handle = @fopen($settings_file, "r"); if ($handle) { $i=0; while (($buffer = fgets($handle, 4096)) !== false) { $str[$i] = $buffer; $i++; } if (!feof($handle)) { echo "Error: unexpected fgets() fail\n"; } fclose($handle); } $current = ''; $flag = 0; for($i=0;$i<count($str);$i++){ if(isset($command['db_host']) && (substr($str[$i], 0, 10) == '$db_ipaddr')){ $str[$i] = '$db_ipaddr = \''.$command['db_host'].'\'; //адрес БД' . PHP_EOL; $flag = 1; } if(isset($command['db_name']) && (substr($str[$i], 0, 8) == '$db_name')){ $str[$i] = '$db_name = \''.$command['db_name'].'\'; //имя БД' . PHP_EOL; $flag = 1; } if(isset($command['db_user']) && (substr($str[$i], 0, 8) == '$db_user')){ $str[$i] = '$db_user = \''.$command['db_user'].'\'; //пользователь БД' . PHP_EOL; $flag = 1; } if(isset($command['db_pass']) && (substr($str[$i], 0, 8) == '$db_pass')){ $str[$i] = '$db_pass = \''.$command['db_pass'].'\'; //пароль' . PHP_EOL; $flag = 1; } if(isset($command['timezone']) && (substr($str[$i], 0, 25) == 'date_default_timezone_set')){ $str[$i] = 'date_default_timezone_set(\''.$command['timezone'].'\');' . PHP_EOL; $flag = 1; } $current .= $str[$i]; } if($flag == 1) { if(file_put_contents($settings_file, $current)){ echo 'Настройки изменены!' . PHP_EOL; } } else { echo 'Параметры: db_host:hostname(адрес БД MySQL или ip) db_name:name(имя БД) db_user:user(пользователь БД) db_pass:password(пароль пользователя БД) encoding:Europe/Minsk(таймзона)' . PHP_EOL; echo '-settings обязательный префикс, лишние параметры можно пропустить' . PHP_EOL; } exit; } exit; ?>
Програмная часть минимально готова, вернемся к упаковке пакета и его цифровой подписи(собственно подписывается он автоматически). Для упаковки програмной части в rpm положим програмную часть в папку dudko-web-panel-0.0.1 и упакуем в одноименный tar.gz архив (это важно).
Далее архив необходимо положить в /home/user/rpmbuild/SOURCES/. В /home/user/ cjplflbv afqk .rpmmacros с содержимым:
%dist .fc7 %packager Name Surname <admin@sergdudko.tk> %vendor vpn.sergdudko.tk %_signature gpg %_gpg_name Name Surname (tel: <+375000000000 reserved: <test@gmail.com>) <admin@sergdudko.tk>
По содержимому расписывать не буду, все и так ясно: версия ОС, упаковщик, цифровая подпись.
Можно приступать к созданию пакета, которое собственно заключается в написании spec-файла. В папке /home/name/rpmbuild/SPECS/ файл dudko-web-panel.spec:
Summary: OpenVPN WEB Panel Name: dudko-web-panel Version: 0.0.2 Release: 1 License: GPL URL: https://sergdudko.tk Group: System/Configuration/Networking Source0: %{name}-%{version}.tar.gz BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: php Requires: openvpn Requires: easy-rsa %description This is Web panel for OpenVPN by Name Surname. %prep %setup -q %build %install rm -rf %{buildroot} install -d %{buildroot}%{_datadir}/%{name} install SendMailSmtpClass.php %{buildroot}%{_datadir}/%{name} install build_client.php %{buildroot}%{_datadir}/%{name} install build_server.php %{buildroot}%{_datadir}/%{name} install cert_send_toemail.php %{buildroot}%{_datadir}/%{name} install core_v1.php %{buildroot}%{_datadir}/%{name} install settings.php %{buildroot}%{_datadir}/%{name} install man.md %{buildroot}%{_datadir}/%{name} %post if ! [ -d /etc/openvpn/ ]; then mkdir -p /etc/openvpn/ fi if ! [ -d /etc/openvpn/scripts/ ]; then mkdir -p /etc/openvpn/scripts/ fi cp %{_datadir}/%{name}/SendMailSmtpClass.php /etc/openvpn/scripts/SendMailSmtpClass.php cp %{_datadir}/%{name}/build_client.php /etc/openvpn/scripts/build_client.php cp %{_datadir}/%{name}/build_server.php /etc/openvpn/scripts/build_server.php cp %{_datadir}/%{name}/cert_send_toemail.php /etc/openvpn/scripts/cert_send_toemail.php cp %{_datadir}/%{name}/core_v1.php /usr/bin/dudko-web-panel cp %{_datadir}/%{name}/settings.php /etc/openvpn/scripts/settings.php rm -rf %{buildroot}%{_datadir}/%{name}/ echo Успешно установлено! %postun rm -rf /etc/openvpn/scripts/ rm -rf /usr/bin/dudko-web-panel echo Успешно удалено! %files %dir %{_datadir}/%{name} %defattr(0755,root,root) %{_datadir}/%{name}/SendMailSmtpClass.php %{_datadir}/%{name}/build_client.php %{_datadir}/%{name}/build_server.php %{_datadir}/%{name}/cert_send_toemail.php %{_datadir}/%{name}/core_v1.php %{_datadir}/%{name}/settings.php %defattr(0644,root,root) %doc %{_datadir}/%{name}/man.md %clean rm -rf %{buildroot} %changelog * Tue Dec 22 2017 Name Surname - Initial build
Summary — публичное наименование
Name — имя пакета
Version — версия пакета
Release — релиз пакета
License — лицензия
URL — ссылка на сайт
Group — группа пакета, выбирается из стандартных
## Список возможных (допустимых) групп:
## Archiving/Backup
## Archiving/Cd burning
## Archiving/Compression
## Archiving/Other
## Books/Computer books
## Books/Faqs
## Books/Howtos
## Books/Literature
## Books/Other
## Communications
## Databases
## Development/C
## Development/C++
## Development/Databases
## Development/Debug
## Development/GNOME and GTK+
## Development/Java
## Development/KDE and Qt
## Development/Kernel
## Development/Other
## Development/Perl
## Development/PHP
## Development/Python
## Development/Ruby
## Development/X11
## Editors
## Education
## Emulators
## File tools
## Games/Adventure
## Games/Arcade
## Games/Boards
## Games/Cards
## Games/Other
## Games/Puzzles
## Games/Sports
## Games/Strategy
## Graphical desktop/Enlightenment
## Graphical desktop/FVWM based
## Graphical desktop/GNOME
## Graphical desktop/Icewm
## Graphical desktop/KDE
## Graphical desktop/Other
## Graphical desktop/Sawfish
## Graphical desktop/WindowMaker
## Graphical desktop/Xfce
## Graphics
## Monitoring
## Networking/Chat
## Networking/File transfer
## Networking/IRC
## Networking/Instant messaging
## Networking/Mail
## Networking/News
## Networking/Other
## Networking/Remote access
## Networking/WWW
## Office
## Publishing
## Sciences/Astronomy
## Sciences/Biology
## Sciences/Chemistry
## Sciences/Computer science
## Sciences/Geosciences
## Sciences/Mathematics
## Sciences/Other
## Sciences/Physics
## Shells
## Sound
## System/Base
## System/Cluster
## System/Configuration/Boot and Init
## System/Configuration/Hardware
## System/Configuration/Networking
## System/Configuration/Other
## System/Configuration/Packaging
## System/Configuration/Printing
## System/Fonts/Console
## System/Fonts/True type
## System/Fonts/Type1
## System/Fonts/X11 bitmap
## System/Internationalization
## System/Kernel and hardware
## System/Libraries
## System/Printing
## System/Servers
## System/X11
## Terminals
## Text tools
## Toys
## Video
Source0 — имя архива с приложением
BuildArch — требуемая архитектура ОС
BuildRoot — папка для распаковки пакета
Requires — требовать данный пакет, в противном случае не установится
секция %description — описание пакета
секция %prep — подготовка пакета к сборке, обычно включает %setup -q и %build (распаковку Source0)
секция %install — установка пакета, можно использовать bash, но обязательно должна быть минимум одна команда install
секция %post — постустановочная обработка пакета, например у меня bash раскладывает по папкам
секция %postun — скрипт, выполняемый после удаления пакета(в моем случае вычищает все созданные пакетом файлы, которые я разложил в %post)
секция %files — список файлов для упаковки в пакет
секция %clean — очистка временного окружения сборки
секция %changelog — лог установки
После создания spec-файла можно упаковать наши исходники в rpm командой (предварительно перейдя в каталог со спецфайлом или прописав полный путь в нижеследующей команде)
rpmbuild -ba dudko-web-panel.spec
Наш пакет будет в папке ../RPMS/noarch/
Проверим цифровую подпись командой
rpm -K ../RPMS/noarch/dudko-web-panel-0.0.2-1.noarch.rpm
Т.к. скрипт, который обслуживает все приложение размещен в /usr/bin/dudko-web-panel, то из консоли он будет запускаться командой dudko-web-panel
Протестировано на CentOS 7.1-7.4
php 5.4, 7.0
openvpn-2.4.4-1.el7.x86_64
easy-rsa-2.2.2-1.el7.noarch
Пакет:
dudko-web-panel.tar
GPG-ключ
RPM-GPG-KEY