Поднимаем Mercurial на Windows-сервере (с Nginx)

в 16:26, , рубрики: bitbucket, hgweb, Mercurial, nginx, windows, Серверное администрирование, Системы управления версиями

Недавно случайно узнал, что BitBucket, где лежат мои Mercurial-репозитории, прекращает поддержку Mercurial: новые репозитории создавать уже нельзя, а существующие будут удалелы с 1.06.2020. Возможные варианты действий: перейти на Git, выбрать один из других сервисов, или настроить хостинг Mercurial на своём сервере. Сервер у меня есть, отказываться от Mercurial и менять привычки как-то не хочется, альтернативы BitBucket мне тоже не приглянулись — поэтому выбрал последний вариант. Задача вроде несложная, вот только сервер у меня под Windows, и, кажется, в процессе настройки я умудрился наступить на максимум возможных граблей. Надеюсь, эта статья поможет кому-нибудь избежать этого и сэкономить время.

Различные варианты организации Mercurial-хостинга описаны в документации. Их можно разделить на 3 группы:

  1. hg serve — т.е. сам Mercurial запускается в режиме сервера.
  2. HgWeb.cgi — официальный скрипт, входящий в дистрибутив Mercurial.
  3. Решения от внешних разработчиков — более функциональные, но со своими ограничениями и зачастую платные.

HG SERVE

Наиболее простым и логичным выглядит 1-й вариант. Как гласит документация, в Mercurial имеется встроенный веб-сервер, исполняющий HgWeb — именно он и работает, когда мы запускаем hg serve. Этот сервер не умеет делать аутентификацию, т.е. не умеет запрашивать логин/пароль пользователя, однако умеет делать авторизацию, т.е. давать/не давать тот или иной вид доспупа к репозиториям в соответствии с настройками доступа. Если нам нужны приватные репозитории, то для аутентификации можно использовать Nginx в качестве прокси-сервера, который будет запрашивать пароль. Настройка такой схемы описана здесь.

Для удобства hg serve запускается в виде сервиса с помощью winsw — утилиты, позволяющей запускать в виде сервиса любую команду (в данном случае это вообще bat-файл). Впрочем, для запуска можно обойтись и без сервиса, а, например, создать таск в Task Scheduler с триггером "At system startup" — такой таск будет запускаться при старте системы. Но это менее надёжный вариант: в случае сбоя таск не будет перезапущен, да и в логах информации об этом не будет.

Будьте внимательны с файлами конфигурации (hgweb.conf / hgrc):

  • Их содержимое чувствительно к регистру: если, например, написать [Web] вместо [web] — работать не будет.
  • Знак равенства обязательно должен быть окружен пробелами: если написать push_ssl=false — работать не будет, нужно писать push_ssl = false.

Ещё обратите внимание на то, что Mercurial делает push в репозиторий одним запросом, и если ваш проект не "Hello world", то этот запрос может быть объёмным и длительным. Пропишите в конфиге Nginx достаточно большие значения:

    client_max_body_size 500M;
    proxy_read_timeout 120s;

чтобы не оказаться в ситуации, когда вроде бы всё работает, а залить реальный проект на сервер не получается.

Проблема авторизации

В hgweb.conf задаётся общая для всех репозиториев конфигурация. Если нужны специфические настройки для каждого репозитория (а они нужны), то они указываются внутри репозитория: /.hg/hgrc Там можно, например, перечислить конкретных пользователей, имеющих доступ к данному репозиторию.

Чтобы это работало, Mercurial должен знать имя пользователя, который авторизовался в Nginx. И тут возникает проблема: на практике Mercurial видит всех пользователей, которых пропускает к нему Nginx одинаково безымянными. В различных источниках рекомендуют в конфигурации Nginx передавать имя пользователя в заголовках так:

proxy_set_header      REMOTE_USER $remote_user;

или так:

proxy_set_header      X-Forwarded-User $remote_user;

но у меня это не сработало :( Судя по коду HgWeb, имя пользователя берется из ENV('REMOTE_USER'), но вот как его туда поместить — загадка.

Если сложная авторизация не требуется, можно настроить её непосредственно в конфигурации Nginx либо создать 2 (или более) экземпляров hg serve, использующих различные файлы hgweb.conf. Недостаток в гибкости последнего метода в том, что настройки hgweb.conf глобальны и касаются всех (перечисленных в нем) репозиториев. Впрочем, комбинируя эти два метода можно добиться практически любых схем авторизации. В конце статьи я ещё коснусь этой темы.

HgWeb.cgi

Описанные выше манипуляции попахивают извращением, поэтому я решил попробовать официальный Python-скрипт HgWeb. В бинарном дистрибутиве Mercurial его нет, поэтому нужно скачать дистрибутив исходников и взять оттуда. Там, кроме основной (CGI) версии, имеются также более эффективные — WSGI и FCGI.

Поскольку Nginx не умеет запускать CGI, а для WSGI нужен WSGI-сервер (поставить который на Windows отдельная проблема), я решил использовать FCGI — то есть FastCGI. Для его запуска нужен Python, причём документация гласит, что Python должен быть такой же версии, какая используется в бинарном дистрибутиве Mercurial — то есть 2.7. Окей, установил 2.7. Для запуска HgWeb в Python также необходимо установить библиотеку Flup, причём не просто установить, а определённой версии (ибо последняя не поддерживает Python 2.7). Вот так работает:

pip install flup==1.0.3.dev-20110405

Теперь нужно отредактировать сам hgweb.fcgi — прописать путь к файлу конфигурации (что очевидно) а также указать параметры запуска, как минимум обязательный параметр используемого локального адреса (что совершенно неочевидно):

WSGIServer(application, bindAddress=("127.0.0.1",5500)).run()

Осталось добавить конфигурацию Nginx:

    location / {
        auth_basic "My Repos";
        auth_basic_user_file passwd;    # файл с паролями
        include fastcgi.conf;
        fastcgi_split_path_info ^()(.*)$;
        fastcgi_param  SCRIPT_NAME        "";
        fastcgi_param  PATH_INFO          $fastcgi_path_info;
        fastcgi_param  AUTH_USER          $remote_user;
        fastcgi_param  REMOTE_USER        $remote_user;
        fastcgi_pass 127.0.0.1:5500;
    }
    location /static/ {
        root e:/Mercurial/templates;
    }

Я специально не редактировал файл fastcgi.conf, а вынес все дополнительные настройки fastcgi в секцию location для наглядности. Обратите внимание на параметр PATH_INFO — он важен для работы скрипта. В моём случае имя скрипта в url вообще отсутствует, а весь оставшийся путь идёт в PATH_INFO.

Теперь запускаем сам скрипт:

python hgweb.fcgi

и всё работает!

Еще немножко настроек

Как и в случае с hg serve, разумно сделать запуск HgWeb в виде сервиса. Для этого использую всё тот же winsw. Он выполняет такой bat-файл:

    @echo off
    if "%1"=="start" (goto :start)
:stop
    taskkill /F /IM python-hg.exe /T
    goto :end
:start
    "c:Python27python-hg.exe" e:Mercurial-5.3hgweb.fcgi
:end

Обратите внимание, что я скопировал python.exe в python-hg.exe чтобы taskkill легко нашел единственный нужный процесс для остановки.

Не стоит забывать и о безопасности: выполнять сервисы для публичного доступа под юзером "Local System" — плохая идея! Нужно создать отдельного юзера, дать ему доступ только к нужным папкам и запускать сервис под ним.

Публичный доступ к репозиториям

Чтобы организовать публичный беспарольный доступ к некоторым репозиториям, документация рекомендует запускать два экземпляра HgWeb (или hg serve) с разными конфигурациями. Но можно сделать проще: для каждого репозитория, к которому нужен публичный доступ, добавить в конфигурацию Nginx отдельную секцию location без защиты паролем. Примерно так:

    location /Base/ {   # Base - имя репозитория, к которому нужен публичный доступ
       include fastcgi.conf;
       fastcgi_split_path_info ^()(.*)$;
       fastcgi_param  SCRIPT_NAME        "";
       fastcgi_param  PATH_INFO          $fastcgi_path_info;
       fastcgi_param  AUTH_USER          "pub";   # любое подходящее имя пользователя для hg (можно пустое)
       fastcgi_param  REMOTE_USER        "pub";
       fastcgi_pass 127.0.0.1:5500;
    }

Все — репозиторий Base доступен без пароля.

Надеюсь, эта статья поможет вам сэкономить время. Лично у меня на всё это ушло примерно полтора дня (с учётом того, что Python я увидел, можно сказать, впервые в жизни :-)

Автор: Cooler2

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js