В этой статье я расскажу, как создать групповую среду разработки сайтов через git с помощью git-хуков. Статья рассчитана на опытных системных администраторов, я лишь опишу алгоритм.
Многое на эту тему уже обсуждалось, а я добавлю, как автоматически создавать или удалять сайты при создании/удалении бранчей в git-репозитории. Такая возможность может пригодиться, к примеру, если над разными частями сайта работают разные программисты и нужны разные площадки (бранчи). После основной разработки и тестирования выполняется merge в основную ветку, а бранчи и тестовые сайты удаляются или архивируются.
Как это работает: по-умолчанию создается master-ветка, по которой доступен основной сайт. По необходимости программисты создают бранчи, которые автоматически становятся доступны по адресу branchname.projectname.domain.ru. Внесенные изменения тестируются и мержатся в основную ветку (будь то master или любая другая). А если бранч удалить, автоматически удаляется и одноименный сайт.
Настройка git
Создаем git-репозиторий, настраиваем его доступность по адресу factory.domain.ru/git/projectname и разрешаем авторизацию без пароля для IP, с которого будет работать сервер разработки. Настройка git-сервера неоднократно описывалась, поэтому детали этого пункта я пропущу.
Создаем git-хуки
Создаем файл /srv/git/projectname/hooks/post-update (этот скрипт будет автоматически обновлять код на сайте при изменениях в репозитории; для переменной PROJECT назначьте название проекта, а для GIT_URL — ссылку к git-репозиторию):
#!/bin/sh
PATH=/usr/bin:/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
PROJECT="projectname"
GIT_URL="http://factory.domain.ru/git"
git update-server-info
if [ ! -f $1 ]; then
exit 0
fi
while [ -n "$1" ]
do
REF=`echo $1 | awk --field-separator="/" '{print $2}'`
if [ $REF = "branches" -o $REF = "heads" ]; then
BRANCH=`echo $1 | awk --field-separator="/" '{print $3}'`
if [ ! -d /srv/www/$PROJECT/repo/master ]; then
mkdir -p /srv/www/$PROJECT/repo
GIT_SSL_NO_VERIFY=true git clone $GIT_URL/$PROJECT /srv/www/$PROJECT/repo/master
fi
if [ ! -d /srv/www/$PROJECT/repo/$BRANCH ]; then
GIT_SSL_NO_VERIFY=true git clone -b $BRANCH $GIT_URL/$PROJECT /srv/www/$PROJECT/repo/$BRANCH
else
cd /srv/www/$PROJECT/repo/$BRANCH
GIT_SSL_NO_VERIFY=true git fetch origin
GIT_SSL_NO_VERIFY=true git reset --hard origin/$BRANCH
GIT_SSL_NO_VERIFY=true git clean -d -f
GIT_SSL_NO_VERIFY=true git checkout
GIT_SSL_NO_VERIFY=true git pull
fi
fi
shift
done
Создаем файл /srv/git/projectname/hooks/update (этот скрипт будет автоматически создавать/удалять сайты при создании/удалении новых бранчей; для переменной PROJECT назначьте название проекта):
#!/bin/sh
refname="$1"
oldrev="$2"
newrev="$3"
PROJECT="projectname"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
BRANCH=`echo $1 | awk --field-separator="/" '{print $3}'`
delete () {
mv /srv/www/$PROJECT/repo/$BRANCH /srv/www/$PROJECT/repo/$BRANCH.removed_by_git
rm -rf /srv/www/$PROJECT/repo/$BRANCH.removed_by_git
}
case "$refname","$newrev_type" in
refs/heads/*,delete)
# delete branch
delete
;;
refs/remotes/*,delete)
# delete tracking branch
delete
;;
esac
exit 0
Настраиваем nginx и apache для поддержки доменов branch.projectname.domain.ru
Создаем vhost-конфиг и подключаем его к основному конфигу nginx:
server {
listen 80;
# projectname замените на название проекта, а domain - на основную часть домена. master-ветка будет доступна по адресу projectname.domain.ru
server_name ~^(?P<branch>.*).projectname.domain.ru$ projectname.domain.ru;
if ($branch = "") {
set $branch "master";
}
access_log /srv/www/projectname/logs/projectname.domain.ru-acc main;
error_log /srv/www/projectname/logs/projectname.domain.ru-err;
# Пример проксирования трафика на apache mod_php
location / {
proxy_pass http://127.0.0.1:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300;
client_max_body_size 256m;
proxy_buffer_size 16k;
proxy_buffers 32 16k;
}
# За исключением папки "data", которая будет доступна по адресу http://projectname.domain.ru/data. Из этой папки отдается только статика, даже если будут загружены исполняемые файлы.
# Я сделал, чтобы запись через php была разрешена только в data, таким образом можно избежать git-конфликтов, а так же частично обезопаситься от уязвимостей в коде.
# Если на уровне кода сложно сделать загрузку файлов вне DOCUMENT_ROOT, можно сделать симлинк и добавить его в репозиторий.
# Таким образом, права на /srv/www/projectname/data делаем, к примеру, apache:apache (или chmod 777), а на /srv/www/projectname/repo - username:username.
location ^~ /data/ {
root /srv/www/projectname;
}
# Статику отдаем в обход apache.
location ~* .(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|swf|flv|avi|djvu|mp3|mp4|ogv)$ {
root /srv/www/projectname/repo/$branch/htdocs;
}
# Запрещаем доступ к папке ".git".
location ~ /.git {
deny all;
}
# Если площадка используется исключительно для разработки, рекомендую создать файл /srv/www/robots.txt с содержимым, запрещающим индексацию сайта (комментарии в начале строк в robots.txt нужно убрать):
# User-Agent: *
# Disallow: /
# Таким образом, внезависимости от того, есть в репозитории robots.txt или нет, поисковики будут получать файл из /srv/www, который будет запрещать индексацию разрабатываемого сайта.
location = /robots.txt {
root /srv/www;
}
}
Остался лишь vhost-конфиг для apache:
<VirtualHost *>
# Хардкод-домен для master-бранча
DocumentRoot /srv/www/projectname/repo/master/htdocs
ServerName projectname.domain.ru
ErrorLog /srv/www/projectname/logs/projectname.domain.ru-err
<Location />
php_admin_value open_basedir "/usr/share/pear:/srv/www/projectname:/tmp"
php_admin_value upload_tmp_dir "/srv/www/projectname/tmp"
php_admin_value session.save_path "/srv/www/projectname/tmp"
php_admin_value memory_limit "256M"
php_value post_max_size "256M"
php_value upload_max_filesize "256M"
</Location>
<Directory /srv/www/projectname/repo>
Options Includes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
<VirtualHost *>
# Реализация "бранчей на лету" с использованием VirtualDocumentRoot
# При использовании rewrite-правил через .htaccess в рамках VirtualDocumentRoot может возникать ошибка 500, в этом случае может помочь настройка "RewriteBase /" в .htaccess.
VirtualDocumentRoot /srv/www/projectname/repo/%1/htdocs
ServerName dev.projectname.domain.ru
ServerAlias *.projectname.domain.ru
ErrorLog /srv/www/projectname/logs/projectname.domain.ru-err
<Location />
php_admin_value open_basedir "/usr/share/pear:/srv/www/projectname:/tmp"
php_admin_value upload_tmp_dir "/srv/www/projectname/tmp"
php_admin_value session.save_path "/srv/www/projectname/tmp"
php_admin_value memory_limit "256M"
php_value post_max_size "256M"
php_value upload_max_filesize "256M"
</Location>
<Directory /srv/www/projectname/repo>
Options Includes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Вот и все! Этот алгоритм можно прикрутить, например, к Redmine для автоматического создания репозиториев при создании новых проектов, а так же для управления правами доступа. Хорошей идеей будет написать плагин с разграничением прав доступа, который даст возможность делать деплой или роллбек на продакшен (в том числе с использованием Capistrano).
Подобную реализацию мы используем для большого рекламного агентства со штатом больше 100 человек и им очень нравится, ведь автоматизация значительно уменьшает ошибки, допущенные за счет человеческого фактора, а главное — не надо ночью будить админа, чтобы создать новый срочный проект или прописать доступы.
Пишите в комментариях, какие вопросы по этой теме вам наиболее интересны — в новой статье обязательно опишу!
Автор: olemskoi