Pull to refresh

Групповая разработка сайтов через git — автоматическое создание/удаление сайтов из git-бранчей

Reading time 6 min
Views 22K
В этой статье я расскажу, как создать групповую среду разработки сайтов через 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 человек и им очень нравится, ведь автоматизация значительно уменьшает ошибки, допущенные за счет человеческого фактора, а главное — не надо ночью будить админа, чтобы создать новый срочный проект или прописать доступы.

Пишите в комментариях, какие вопросы по этой теме вам наиболее интересны — в новой статье обязательно опишу!
Tags:
Hubs:
+37
Comments 14
Comments Comments 14

Articles