Недавно случайно узнал, что BitBucket, где лежат мои Mercurial-репозитории, прекращает поддержку Mercurial: новые репозитории создавать уже нельзя, а существующие будут удалелы с 1.06.2020. Возможные варианты действий: перейти на Git, выбрать один из других сервисов, или настроить хостинг Mercurial на своём сервере. Сервер у меня есть, отказываться от Mercurial и менять привычки как-то не хочется, альтернативы BitBucket мне тоже не приглянулись — поэтому выбрал последний вариант. Задача вроде несложная, вот только сервер у меня под Windows, и, кажется, в процессе настройки я умудрился наступить на максимум возможных граблей. Надеюсь, эта статья поможет кому-нибудь избежать этого и сэкономить время.
Различные варианты организации Mercurial-хостинга описаны в документации. Их можно разделить на 3 группы:
- hg serve — т.е. сам Mercurial запускается в режиме сервера.
- HgWeb.cgi — официальный скрипт, входящий в дистрибутив Mercurial.
- Решения от внешних разработчиков — более функциональные, но со своими ограничениями и зачастую платные.
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:\Python27\python-hg.exe" e:\Mercurial-5.3\hgweb.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 я увидел, можно сказать, впервые в жизни :-)