Git: свой репозиторий под NGINX + WebDAV + uWSGI

Автор: | 02/03/2015
 

git-logoОбновлено: теперь работает push 🙂

Создание локального репозитория с доступом по HTTP.

Используется NGINX для обработки HTTP, uWSGI – для обработки CGI.

Предполагается, что имеется чистая система, поэтому – описывается установка каждого компонента.

Для того, что бы была возможнеость выполнять git push – требуется собрать NGINX с дополнительным модулями – http_dav_module и  nginx-dav-ext-module.

Иначе – git push будет возврашать ошибку:

$ git push http://git.domain.local/local.git master
error: Cannot access URL http://git.domain.local/local.git, return code 22
fatal: git-http-push failed
error: failed to push some refs to ‘http://git.domain.local/local.git’

А в логе NGINX будут присутвовать записи ввида:

10.***.***.96 – – [03/Mar/2015:11:12:46 +0200] “PROPFIND /local.git/ HTTP/1.1” 405 172 “-” “git/2.1.4”

Отключаем SELinux в файле /etc/selinux/config:

SELINUX=disabled

Устанавливаем необходимые пакеты:

# yum install pcre pcre-devel expat-devel gcc zlib-devel

Загружаем исходные коды NGINX:

# cd /tmp/
# wget http://nginx.org/download/nginx-1.6.2.tar.gz
# tar xfp nginx-1.6.2.tar.gz

Со страницы https://github.com/arut/nginx-dav-ext-module качаем модуль для NGINX:

# cd nginx-1.6.2
# wget https://github.com/arut/nginx-dav-ext-module/archive/master.zip
# unzip master.zip
Archive: master.zip
89d582d31ab624ff1c6a4cec0c1a52839507b323
creating: nginx-dav-ext-module-master/
inflating: nginx-dav-ext-module-master/README
inflating: nginx-dav-ext-module-master/config
inflating: nginx-dav-ext-module-master/ngx_http_dav_ext_module.c

Подготавливаем NGINX к сборке:

# ./configure --with-http_dav_module --add-module=nginx-dav-ext-module-master --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/tmp/nginx/client_body_temp --user=nginx --group=nginx
...
Configuration summary
 + using system PCRE library
 + OpenSSL library is not used
 + using builtin md5 code
 + sha1 library is not found
 + using system zlib library

 nginx path prefix: "/etc/nginx"
 nginx binary file: "/usr/sbin/nginx"
 nginx configuration prefix: "/etc/nginx"
 nginx configuration file: "/etc/nginx/nginx.conf"
 nginx pid file: "/var/run/nginx.pid"
 nginx error log file: "/var/log/nginx/error.log"
 nginx http access log file: "/var/log/nginx/access.log"
 nginx http client request body temporary files: "/var/tmp/nginx/client_body_temp"
 nginx http proxy temporary files: "proxy_temp"
 nginx http fastcgi temporary files: "fastcgi_temp"
 nginx http uwsgi temporary files: "uwsgi_temp"
 nginx http scgi temporary files: "scgi_temp"

Обратите внимание на опции --add-module=nginx-dav-ext-module-master и --with-http_dav_module – именно тут мы добавляем в сборку новые модули. WebDAV добавляет поддержку методов PUT, DELETE, MKCOL, COPY и MOVE, а nginx-dav-ext-module – методов PROPFIND и OPTIONS.

Ещё один нюанс, на который стоит обратить внимание. Может возникнуть ошибка вида:

2015/03/03 13:54:23 [crit] 2169#0: *3 pwrite() “/etc/nginx/client_body_temp/0000000002” failed (28: No space left on device), client: 10.***.***.96, server: git.domain.local, request: “POST /test.git/git-receive-pack HTTP/1.1”, host: “git.domain.local”

Она возникает, т.к. NGINX при получении данных с удалённого (вашего локального) репозитория сначала размещает их в директории, описанной в --http-client-body-temp-path. И если в этой директории (по умолчанию она = --prefix=/etc/nginx + client_body_temp) не хватит места – то git push будет падать с такой ошибкой:

fatal: The remote end hung up unexpectedly

Поэтому, если планируется загрузка больших объёмов – лучше сразу предусмотреть этот момент, и указать директорию в разделе, где достаточно места. В данном случае – я использовал --http-client-body-temp-path=/var/tmp/nginx/client_body_temp.

Собираем NGINX:

# make

Если у вас уже есть установленный NGINX – забекапьте файлы настроек.

Устанавливаем новый NGINX;

# make install

Если увас был сустановлен NGINX – он будет сохранён как /usr/sbin/nginx.old.

Проверяем установку:

# nginx -V
nginx version: nginx/1.6.2
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC)
configure arguments: --with-http_dav_module --add-module=nginx-dav-ext-module-master --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --lock-path=/var/run/nginx.lock

Для управления NGINX создаём init-скрипт (если раньше не было устанолвенного NGINX) /etc/init.d/nginx с таким содержимым:

#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig:   - 85 15
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse 
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /etc/nginx/nginx.conf
# config:      /etc/sysconfig/nginx
# pidfile:     /var/run/nginx.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

nginx="/usr/sbin/nginx"
prog=$(basename $nginx)

NGINX_CONF_FILE="/etc/nginx/nginx.conf"

[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx

lockfile=/var/lock/subsys/nginx

make_dirs() {
   # make required directories
   user=`$nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=([^ ]*).*/1/g' -`
   if [ -z "`grep $user /etc/passwd`" ]; then
       useradd -M -s /bin/nologin $user
   fi
   options=`$nginx -V 2>&1 | grep 'configure arguments:'`
   for opt in $options; do
       if [ `echo $opt | grep '.*-temp-path'` ]; then
           value=`echo $opt | cut -d "=" -f 2`
           if [ ! -d "$value" ]; then
               # echo "creating" $value
               mkdir -p $value && chown -R $user $value
           fi
       fi
   done
}

start() {
    [ -x $nginx ] || exit 5
    [ -f $NGINX_CONF_FILE ] || exit 6
    make_dirs
    echo -n $"Starting $prog: "
    daemon $nginx -c $NGINX_CONF_FILE
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog -QUIT
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    configtest || return $?
    stop
    sleep 1
    start
}

reload() {
    configtest || return $?
    echo -n $"Reloading $prog: "
    killproc $nginx -HUP
    RETVAL=$?
    echo
}

force_reload() {
    restart
}

configtest() {
  $nginx -t -c $NGINX_CONF_FILE
}

rh_status() {
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart|configtest)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
            ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
        exit 2
esac

Это скрипт для CentOS/RHEL/Fedora. Для других систем – можно найти готовые скрипты тут>>>.

Устанавивлаем права на зауск:

# chmod +x /etc/init.d/nginx

Добавляем в автозапуск:

# chkconfig nginx on
# chkconfig --list nginx
nginx           0:off   1:off   2:on    3:on    4:on    5:on    6:off

Устанавливаем Git:

# yum install git

Проверяем наличие файла модуля Git:

# file /usr/libexec/git-core/git-http-backend
/usr/libexec/git-core/git-http-backend: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

Далее требуется установить uWSGI. Учтите, что установка из PIP не подойдёт, так как тогда uWSGI будет установлен без модуля CGI (cgi_plugin.so), что приведёт к ошибке:

open(“./cgi_plugin.so”): No such file or directory [core/utils.c line 3675]
!!! UNABLE to load uWSGI plugin: ./cgi_plugin.so: cannot open shared object file: No such file or directory !!!

И ошибке 502 Bad Gateway или 500 Internal Server  при доступе к репозиторию.

Устанавливаем uWSGI из исходных кодов:

# curl http://uwsgi.it/install | bash -s cgi /usr/bin/uwsgi

Проверяем:

# uwsgi --version
2.0.9

Для хранения файлов конфигураций – создаём директорию:

# mkdir /etc/uwsgi/

Создаём каталог для логов:

# mkdir /var/log/uwsgi/

Создаём файл настроек для доступа к Git/etc/uwsgi/git_local.ini с таким содержимым:

[uwsgi]
plugins = cgi
socket = 127.0.0.1:9090
master = True
pidfile = /tmp/project-master.pid
daemonize=/var/log/uwsgi/git_local.log
cgi = /usr/libexec/git-core/git-http-backend

Для более простого управления uWSGI – создаём файл /etc/init.d/uwsgi с таким содержимым:

#!/bin/bash

### BEGIN INIT INFO
# Provides:          uwsgi
# Required-Start:    $syslog $remote_fs
# Should-Start:      $time ypbind smtp
# Required-Stop:     $syslog $remote_fs
# Should-Stop:       ypbind smtp
# Default-Start:     3 5
# Default-Stop:      0 1 2 6
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

# Check for missing binaries (stale symlinks should not happen)
UWSGI_BIN=`which uwsgi`
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
        if [ "$1" = "stop" ]; then exit 0;
        else exit 5; fi; }

UWSGI_EMPEROR_MODE=true
UWSGI_VASSALS="/etc/uwsgi/"
UWSGI_OPTIONS="--enable-threads --logto /var/log/uwsgi.log"
lockfile=/var/lock/subsys/uwsgi

UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"

if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
    UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
fi

case "$1" in
    start)
        echo "Starting uWSGI "
        daemon $UWSGI_BIN $UWSGI_OPTIONS &
        ;;
    stop)
        echo "Shutting down uWSGI "
        killproc $UWSGI_BIN
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        echo "Checking for service uWSGI "
        status $UWSGI_BIN
        ;;
    *)
        echo "Usage: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac
exit 0

Либо – используем supervisord, но проще таким скриптом.

Устанавливаем разрешение на его запуск:

# chmod +x /etc/init.d/uwsgi

Запускаем:

# service uwsgi start
Starting uWSGI

Проверяем:

# service uwsgi status
Checking for service uWSGI
uwsgi (pid 2377) is running...

Проверяем порт для Git репозитория:

# netstat -anp | grep 9090
tcp        0      0 127.0.0.1:9090              0.0.0.0:*                   LISTEN      2419/uwsgi

Проверяем лог:

# tail -f /var/log/uwsgi/git_local.log

Должна быть строка такого вида:

initialized CGI path: /usr/libexec/git-core/git-http-backend

Добавляем в автозапуск:

# chkconfig uwsgi on

Проверяем:

# chkconfig --list uwsgi
uwsgi           0:off   1:off   2:on    3:on    4:on    5:on    6:off

Настраиваем NGINX. Редактируем файл /etc/nginx/nginx.conf и добавляем хост :

    server {
        listen       80;
        server_name git.domain.local;
        root /var/gitrepo;

        location ~ {
            include uwsgi_params;
            uwsgi_pass  127.0.0.1:9090;
            uwsgi_modifier1 9;
            uwsgi_param GIT_HTTP_EXPORT_ALL "";
            uwsgi_param GIT_PROJECT_ROOT    /var/gitrepo;
            uwsgi_param PATH_INFO           $uri;

            # dav_methods - использует мордуль WebDAV
            dav_methods PUT DELETE MKCOL COPY MOVE;
            # dav_ext_methods - модуль nginx-dav-ext-module
            dav_ext_methods PROPFIND OPTIONS;
        }
    }

Кратко о uwsgi_modifier1, так как он нигде толком не описан – но имеет важное значение. В документации NGINX говорится, что:

Задаёт значение поля modifier1 в заголовке пакета uwsgi.

А “9” в заголовке пакета в указывает обрабатывать данные как CGI. По умолчанию используется uwsgi_modifier1 0 – т.е. Python.

Проверяем:

# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Запускаем:

# service nginx start
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]

Открываем порт 80 на фаерволе:

# iptables -I INPUT 2 -p tcp --dport 80 -j ACCEPT

Сохраняем:

# service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[  OK  ]

Создаём каталог для репозитория:

# mkdir -p /var/gitrepo/local.git
# cd /var/gitrepo/local.git/

Создаём сам репозиторий:

# git init --bare
Initialized empty Git repository in /var/gitrepo/local.git/

.git в конце имени репозитория – общепринятая принятая практика при создании репозитория, который предназначен только для хранения информации. Опция --bare указывает как раз на то, что в этом репозитории не будет рабочей копии данных, доступных для редактирования, а только служебные файлы Git, которые потребуются для отслеживания изменения данных в репозитории. Смотрите детали тут>>>.

Обновляем информацию о сервере:

# cd /var/gitrepo/local.git/
# git update-server-info

Она создаст файл /var/gitrepo/local.git/info/refs, который необходим для работы:

# ls -l info/
total 4
-rw-r--r-- 1 nginx nginx 240 Feb 28 16:08 exclude
-rw-r--r-- 1 nginx nginx   0 Feb 28 16:09 refs

Устанавливаем владельца каталога:

# chown -R nginx:nginx /var/gitrepo/

С удалённой машины проверяем:

$ git clone http://git.domain.local/local.git
Initialized empty Git repository in /root/local/.git/
warning: You appear to have cloned an empty repository.
$ ls -l local/
total 0
$ cd local/
$ touch file
$ git add file
$ git commit -m 'ggg' file
[master (root-commit) fb6c288] ggg
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file

И загружаем в центральный репозиторий:

$ git push http://git.domain.local/local.git master
Counting objects: 3, done.
Writing objects: 100% (3/3), 204 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To http://git.domain.local/local.git
* [new branch] master -> master

Готово.

Что бы закрыть доступ к репозиторию – можно использовать обычную HTTP-авторизацию.

Для того, что бы использовать утилиту htpasswd – ставим httpd-tools:

# yum install httpd-tools

Переходим в корень репозитория:

# cd /var/gitrepo

Создаём файл, пользователя и пароль:

# htpasswd -c .htpasswd setevoy
New password:
Re-type new password:
Adding password for user setevoy

Возвращаемся к файлу /etc/nginx/nginx.conf, и в блоке server {} добавляем вызов авторизации:

    server {
        listen       80;
        server_name git.domain.local;
        root /var/gitrepo;

        auth_basic "Password-protected Area";
        auth_basic_user_file /var/gitrepo/.htpasswd;

       ...

Проверяем, перезапускаем NGINX:

# nginx -t && service nginx restart
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]

Теперь при обращении к репозиторию по HTTP – будет требоваться авторизация:

$ git clone http://git.domain.local/local.git
Initialized empty Git repository in /root/local/.git/
error: The requested URL returned error: 401 Unauthorized while accessing http://git.domain.local/local.git/info/refs

Указываем пользователя-пароль в запросе:

$ git clone http://setevoy:[email protected]/local.git
Initialized empty Git repository in /root/local/.git/
warning: You appear to have cloned an empty repository.

Готово.

Последний штрих – сохранить удалённый репозиторий.

На рабочей машине создадим каталог:

$ mkdir /home/setevoy/local_git_repos
$ cd /home/setevoy/local_git_repos/
$ git init
Initialized empty Git repository in /home/setevoy/local_git_repos/.git/

Сохраняем удалённый репозиторий под именем local:

$ git remote add local http://setevoy:[email protected]/local.git

Проверяем:

$ git remote -v
local   http://setevoy:[email protected]/local.git (fetch)
local   http://setevoy:[email protected]/local.git (push)

Загружаем:

$ git clone local
Initialized empty Git repository in /home/setevoy/local_git_repos/local/.git/
warning: You appear to have cloned an empty repository.

На этом всё.

P.S. Но я настоятельно рекомендую не тратить время на HTTP, а настроить простой доступ по SSH – намного проще и удобнее.

Ссылки по теме

https://github.com

https://www.kernel.org

http://wiki.nginx.org

https://www.howtoforge.com