Я достаточно давно пользуюсь vestacp, но чего-то в ней все время не хватало. Может быть файл менеджера, а может правки php/js/css файлов. И я решил попробовать cloud9 IDE, которая заменила и файл менеджер и еще добавила много всяких плюшек.
Ранее я пользовался notepad++ и плагином nppftp и считал эту связку идеальной, но было в ней несколько неприятных особенностей:
- Эта связка не умела читать логи по символическим ссылкам (В vestacp логи доступны в домашнем каталоге пользователя именно по символическим ссылкам)
- Имея динамический IP адрес столкнулся с такой проблемой, что ftp соединение разрывалось спустя 2 минуты, а nppftp его не восстанавливал сразу. Сохраняешь файл — nppftp понимает, что соединение потеряно (файл не сохраняется), сохраняешь 2 раз — nppftp восстанавливает соединение (файл сохраняется, но чтение директории происходит с ошибкой), сохраняешь 3 раз — теперь все норм.
Я перепробовал много различных IDE прежде чем пришел к cloud9 и все они имели какие-то недостатки
Предполагается, что у нас уже есть сервер с ubuntu 16.04 и vestacp.
Установка cloud9 на ubuntu 16.04
Для начала установим зависимости для cloud9
1 |
sudo apt-get install build-essential |
Скачаем и установим cloud9 в директорию /opt
1 2 3 4 |
cd /opt sudo git clone https://github.com/c9/core.git c9sdk cd c9sdk sudo scripts/install-sdk.sh |
Уже сейчас мы можем посмотреть как выглядит cloud9 и что из себя представляет. Для этого запустим сервер командой
1 |
sudo /root/.c9/node/bin/node /opt/c9sdk/server.js --listen 0.0.0.0 -a test:test |
—listen — с какого адреса мы будем слушать вызовы. 0.0.0.0 — со всех
-a test:test — логин:пароль при подключении
Переходим в браузере по адресу http://192.168.0.14:8181 (не забудьте вместо 192.168.0.14 подставить адрес вашего сервера) и видим готовую к работе cloud9. Красота. Не правда ли?
На этом можно было бы и остановиться, но наша цель — привязать cloud9 к vestacp. В идеале сделать так, чтобы в vestacp появилась кнопка IDE, которая бы и открывала нам домашнюю директорию пользователя под именем которого мы залогинились в vestacp.
Связка cloud9 и VestaCP
Для начала установим node.js сервер, чтобы можно было любому пользователю обращаться к cloud9
1 |
sudo apt-get install nodejs-legacy |
Теперь мы можем запускать cloud9 от имени любого пользователя.
Важно! У пользователя должен быть установлен SSH Access: bash
Осталось только добавить в меню кнопку IDE с запуском нужной команды.
Для этого добавим 2 файла
- /usr/local/bin/v-start-cloud9, который будет отвечать за запуск clod9
- /usr/local/vesta/web/cloud9/index.php, который будет отвечать за отображение clod9
Так же нам понадобится править 2 файла весты:
- /usr/local/vesta/web/templates/admin/panel.html
- /usr/local/vesta/web/templates/user/panel.html
Давайте уже воспользуемся cloud9 для этих нужд:
Запускаем cloud9 с параметром -w. Этот параметр используется для того, чтобы указать нашу рабочую область
1 |
sudo node /opt/c9sdk/server.js --listen 0.0.0.0 -a test:test -w /usr/local/vesta |
Открываем в браузере http://192.168.0.14:8181 (не забудьте подставить ваш адрес) и создаем файл /usr/local/vesta/bin/v-start-cloud9
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/bash user=$1 password=$2 source $VESTA/func/main.sh while read str; do eval $str chown www-data:$user /var/log/apache2/domains/$DOMAIN.log chown www-data:$user /var/log/apache2/domains/$DOMAIN.error.log done < <(cat $USER_DATA/web.conf) pkill node su $user -c "node /opt/c9sdk/server.js -w /home/$user/web -p 8181 -l 0.0.0.0 -a $user:$password > /dev/null &" |
Тут стоит немного прокомментировать код:
Берем имя пользователя и пароль из параметров (об этом ниже)
Затем исправляем один неприятный косяк весты. Дело в том, что файл логов vesta кладет в /var/log/apache2/domains, а когда файл доходит до ограничений, заданных в настройках apache — создается новый файл с именем домена, а старый переименовывает — добавляет к нему «.1». И тут нас ждет сюрприз. Новый файл создает пользователь www-data с правами только для самого www-data и группы adm. Как следствие мы не можем его прочитать от имени пользователя, владельца домена.
После убиваем процесс node. Решение не самое изящное, но для моих потребностей этого вполне хватает, т.к. я не использую других приложений node.js и с сервером я работаю один. Просто для каждого домена создаю пользователя в весте и поэтому мне не важно у кого еще запущен node
Ну и последняя строка нужна для того, чтобы запустить cloud9 от имени пользователя весты и еще что бы эта команда выполнялась в фоне.
Вышеуказанному файлу следует установить права 770:
1 |
sudo chmod 770 /usr/local/vesta/bin/v-start-cloud9 |
Кстати, это тоже можно сделать из cloud9, т.к. там есть поддержка команд bash
Теперь создаем директорию /usr/local/vesta/web/cloud9, а в ней файл index.php
Файл index.php будет иметь следующее содержание:
1 2 3 4 5 6 7 8 9 10 11 |
<?php include($_SERVER['DOCUMENT_ROOT']."/inc/main.php"); function random_password( $length = 8 ) { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $password = substr( str_shuffle( $chars ), 0, $length ); return $password; } $password = random_password(8); exec (VESTA_CMD.'v-start-cloud9 '. $user .' '. $password); sleep(1); header('location:http://' . $user . ':' . $password . '@192.168.0.14:8181'); |
!!! Не забудьте поменять 192.168.0.14 на свой адрес
В этом файле мы запускаем сервер командой exec и устанавливаем пароль для входа, который сразу же используем, передавая его в заголовок location.
Таким образом мы защитим наш cloud9 от проникновения из вне и при этом нам не надо будет постоянно вводить логин/пароль руками
Осталось поправить два файла весты. Содержание у них примерно одинаковое. А отвечают они за отображение пункта меню «IDE» в нашей задаче.
В файле /usr/local/vesta/web/templates/user/panel.html находим следующее (12-18 строки)
1 2 3 4 5 6 7 |
<div class="l-menu clearfix"> <div class="l-menu__item <?php if($TAB == 'STATS' ) echo 'l-menu__item--active' ?>"><a href="/list/stats/"><?=__('Statistics')?></a></div> <div class="l-menu__item <?php if($TAB == 'LOG' ) echo 'l-menu__item--active' ?>"><a href="/list/log/"><?=__('Log')?></a></div> <?php if ((isset($_SESSION['FILEMANAGER_KEY'])) && (!empty($_SESSION['FILEMANAGER_KEY']))) {?><div class="l-menu__item <?php if($TAB == 'FILEMANAGER' ) echo 'l-menu__item--active' ?>"><a href="/list/directory/"><?=__('File Manager')?></a></div><?php }?> <?php if ($_SESSION['SOFTACULOUS']) {?><div class="l-menu__item"><a href="/softaculous/"><?=__('Apps')?></a></div><?php }?> </div> <!-- /.l-menu --> |
И добавляем наш пункт меню «IDE», т. е. приводим к такому виду:
1 2 3 4 5 6 7 8 |
<div class="l-menu clearfix"> <div class="l-menu__item <?php if($TAB == 'STATS' ) echo 'l-menu__item--active' ?>"><a href="/list/stats/"><?=__('Statistics')?></a></div> <div class="l-menu__item <?php if($TAB == 'LOG' ) echo 'l-menu__item--active' ?>"><a href="/list/log/"><?=__('Log')?></a></div> <?php if ((isset($_SESSION['FILEMANAGER_KEY'])) && (!empty($_SESSION['FILEMANAGER_KEY']))) {?><div class="l-menu__item <?php if($TAB == 'FILEMANAGER' ) echo 'l-menu__item--active' ?>"><a href="/list/directory/"><?=__('File Manager')?></a></div><?php }?> <?php if ($_SESSION['SOFTACULOUS']) {?><div class="l-menu__item"><a href="/softaculous/"><?=__('Apps')?></a></div><?php }?> <div class="l-menu__item"><a href="/cloud9/" target="_blank"><?=__('IDE')?></a></div> </div> <!-- /.l-menu --> |
Файл /usr/local/vesta/web/templates/admin/panel.html правим аналогичным образом, т.е. добавляем наш пункт меню, но теперь для админов
Находим следующие строки
1 2 3 4 5 6 7 8 9 10 11 12 |
<div class="l-menu clearfix noselect"> <div class="l-menu__item <?php if($TAB == 'PACKAGE' ) echo 'l-menu__item--active' ?>"><a href="/list/package/"><?=__('Packages')?></a></div> <div class="l-menu__item <?php if($TAB == 'IP' ) echo 'l-menu__item--active' ?>"><a href="/list/ip/"><?=__('IP')?></a></div> <div class="l-menu__item <?php if($TAB == 'RRD' ) echo 'l-menu__item--active' ?>"><a href="/list/rrd/"><?=__('Graphs')?></a></div> <div class="l-menu__item <?php if($TAB == 'STATS' ) echo 'l-menu__item--active' ?>"><a href="/list/stats/"><?=__('Statistics')?></a></div> <div class="l-menu__item <?php if($TAB == 'LOG' ) echo 'l-menu__item--active' ?>"><a href="/list/log/"><?=__('Log')?></a></div> <div class="l-menu__item <?php if($TAB == 'UPDATES' ) echo 'l-menu__item--active' ?>"><a href="/list/updates/"><?=__('Updates')?></a></div> <?php if ((isset($_SESSION['FIREWALL_SYSTEM'])) && (!empty($_SESSION['FIREWALL_SYSTEM']))) {?><div class="l-menu__item <?php if($TAB == 'FIREWALL' ) echo 'l-menu__item--active' ?>"><a href="/list/firewall/"><?=__('Firewall')?></a></div><?php }?> <?php if ((isset($_SESSION['FILEMANAGER_KEY'])) && (!empty($_SESSION['FILEMANAGER_KEY']))) {?><div class="l-menu__item <?php if($TAB == 'FILEMANAGER' ) echo 'l-menu__item--active' ?>"><a href="/list/directory/"><?=__('File Manager')?></a></div><?php }?> <?php if ($_SESSION['SOFTACULOUS'] == 'yes') {?><div class="l-menu__item"><a href="/softaculous/"><?=__('Apps')?></a></div><?php }?> <div class="l-menu__item <?php if($TAB == 'SERVER' ) echo 'l-menu__item--active' ?>"><a href="/list/server/"><?=__('Server')?></a></div> </div> |
И приводим их к такому виду:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div class="l-menu clearfix noselect"> <div class="l-menu__item <?php if($TAB == 'PACKAGE' ) echo 'l-menu__item--active' ?>"><a href="/list/package/"><?=__('Packages')?></a></div> <div class="l-menu__item <?php if($TAB == 'IP' ) echo 'l-menu__item--active' ?>"><a href="/list/ip/"><?=__('IP')?></a></div> <div class="l-menu__item <?php if($TAB == 'RRD' ) echo 'l-menu__item--active' ?>"><a href="/list/rrd/"><?=__('Graphs')?></a></div> <div class="l-menu__item <?php if($TAB == 'STATS' ) echo 'l-menu__item--active' ?>"><a href="/list/stats/"><?=__('Statistics')?></a></div> <div class="l-menu__item <?php if($TAB == 'LOG' ) echo 'l-menu__item--active' ?>"><a href="/list/log/"><?=__('Log')?></a></div> <div class="l-menu__item <?php if($TAB == 'UPDATES' ) echo 'l-menu__item--active' ?>"><a href="/list/updates/"><?=__('Updates')?></a></div> <?php if ((isset($_SESSION['FIREWALL_SYSTEM'])) && (!empty($_SESSION['FIREWALL_SYSTEM']))) {?><div class="l-menu__item <?php if($TAB == 'FIREWALL' ) echo 'l-menu__item--active' ?>"><a href="/list/firewall/"><?=__('Firewall')?></a></div><?php }?> <?php if ((isset($_SESSION['FILEMANAGER_KEY'])) && (!empty($_SESSION['FILEMANAGER_KEY']))) {?><div class="l-menu__item <?php if($TAB == 'FILEMANAGER' ) echo 'l-menu__item--active' ?>"><a href="/list/directory/"><?=__('File Manager')?></a></div><?php }?> <?php if ($_SESSION['SOFTACULOUS'] == 'yes') {?><div class="l-menu__item"><a href="/softaculous/"><?=__('Apps')?></a></div><?php }?> <div class="l-menu__item <?php if($TAB == 'SERVER' ) echo 'l-menu__item--active' ?>"><a href="/list/server/"><?=__('Server')?></a></div> <div class="l-menu__item"><a href="/cloud9/" target="_blank"><?=__('IDE')?></a></div> </div> |
Готово.
Заходим в vestacp под каким-нибудь пользователем
Должна появиться cloud9 в новой вкладке с предложением установить cloud9 для этого пользователя. Для каждого пользователя потребуется своя установка cloud9
Если не появилась или возникнут ошибки при установке — читаем статью заново. Если все прошло гладко — жмем Next два раза и наблюдаем процесс установки. В конце нажимаем Finish и пользуемся cloud9 ide из панели vestacp.
Ошибку npm можем либо игнорировать, либо доставить npm из репозитария ubuntu
Подведем итоги
В результате нашей работы мы получили IDE, в которую легко войти и работать с файлами нашего сервера локально (без FTP), что тоже дает определенные плюсы в работе. Преимущества облачных IDE перед настольными я описывать не буду, т.к. статья не об этом. При желании можно разбросать cloud9 по портам (для каждого пользователя свой порт) — тогда не нужно гасить процессы node при запуске нового экземпляра.
Из минусов могу лишь отметить, что нет русского языка. Но это так себе минус. Мне и на английском все понятно в этой IDE