воскресенье, 14 июля 2019 г.

Установка и настройка cgit

cgit - это веб-интерфейс для доступа к git-репозиториям,​ который написан на Си и в целом аналогичен приложению Gitweb, написанному на Perl. Поскольку оба приложения очень похожи друг на друга, не стану притворяться и скажу прямо: эта статья является почти дословным повтором статьи про Gitweb, с необходимой поправкой на специфику cgit. Как и в случае с Gitweb, для эксперимента решил попробовать настроить cgit поверх связки nginx и uWSGI.

Установка пакетов

Установим необходимые пакеты:​
# apt-get install nginx-light uwsgi cgit

Настройка uwsgi

Первым делом настроим сервер приложений uWSGI для запуска CGI-сценария. Для этого создадим файл /​etc/​uwsgi/​apps-available/​cgit.ini со следующим содержимым:​
[uwsgi]

procname = uwsgi-cgit
procname-master = uwsgi-cgit-master

plugins = cgi
processes = 1
threads = 4
cgi = /cgit/=/usr/lib/cgit/cgit.cgi
cgi-timeout = 5
Теперь нужно подключить использование этого файла:
# cd /​etc/​uwsgi/​apps-enabled/​
# ln -s /​etc/​uwsgi/​apps-available/​cgit.ini .
Перезапустим uWSGI, чтобы его новые настройки вступили в силу:
# systemctl restart uwsgi.service

Настройка cgit

Теперь настроим файл конфигурации самого приложения cgit. Я буду настраивать приложение в подкаталоге веб-сервера /cgit/, поэтому у меня файл конфигурации /​etc/​cgitrc принял следующий вид:
favicon=/cgit/static/favicon.ico
css=/cgit/static/cgit.css
logo=/cgit/static/cgit.png

snapshots=tar.gz tar.bz2 zip
remove-suffix=1
enable-index-links=1
enable-index-owner=0

scan-path=/var/lib/cgit/
Будьте внимательны при редактировании файла конфигурации: порядок некоторых опций имеет значение. В частности, опция snapshots работает только при указании перед опцией scan-path.

Кроме указания пути к каталогу с проектами при помощи опции scan-path, имеется возможность описать каждый проект индивидуально и даже сгруппировать проекты. За подробностями обращайтесь к странице руководства cgitrc.

Настройка nginx

Наконец,​ теперь пришла очередь настраивать веб-сервер nginx. Для настройки откроем файл в каталоге /​etc/​nginx/​sites-enabled/,​ найдём секцию сервера и впишем в неё следующие настройки:​
location /cgit {
  return 301 $scheme://$host$request_uri/;
}

location /cgit/static/ {
  autoindex off;
  alias /usr/share/cgit/;
}

location /cgit/ {
  uwsgi_pass unix:/run/uwsgi/app/cgit/socket;
  include uwsgi_params;
  uwsgi_modifier1 9;
}
Перезагрузим nginx, чтобы добавленные нами настройки вступили в силу:
# systemctl reload nginx.service

Публикация репозиториев

Как можно увидеть в файле конфигурации /etc/cgitrc, созданном нами ранее, репозитории git должны размещаться в каталоге /​var/​lib/​cgit/​. Создадим этот каталог и назначим права на него:
# mkdir /var/lib/cgit
# chown www-data:www-data /var/lib/cgit
# chmod o= /var/lib/cgit
В этом каталоге нужно создать подкаталоги с расширением .git, содержащие так называемые «голые» (bare) репозитории. Обычный репозиторий, в котором ведётся разработка, содержит внутри себя текущие версии файлов, над которыми идёт работа, и каталог .git. Каталог .git, по-сути, и является «голым» репозиторием. Для создания «голого» репозитория из обычного можно воспользоваться такими командами:
# cd /var/lib/cgit
# git clone --bare /home/stupin/git/view3d view3d.git
И не забудьте поменять права доступа на «голый» репозиторий:
# cd /var/lib/cgit
# chown -R www-data:www-data view3d
# chmod -R o= view3d.git

Тестирование cgit

Теперь можно запустить браузер и проверить доступность приложения. У меня стартовая страница приложения выглядела следующим образом:​

Как видно из снимка экрана,​ ссылки на странице не дружественны для человека,​ что, впрочем,​ не мешает выполнять основную задачу,​ для которой это веб-приложение и было создано:​ просматривать git-репозитории через браузер.

Использованные материалы

воскресенье, 7 июля 2019 г.

Установка и настройка Gitweb

Gitweb - это веб-интерфейс для доступа к git-репозиториям, который написан на Perl и поставляется вместе с исходными текстами git. Gitweb рассчитан на запуск через веб-сервер Apache, но я Apache не использую и для эксперимента решил попробовать настроить Gitweb поверх связки nginx и uWSGI.

Установка пакетов

Несмотря на то, что в репозитории Debian имеется отдельный пакет с именем gitweb, всё необходимое для запуска gitweb есть в пакете git. Единственное, чего может не хватить - это модуля CGI.pm. Его можно установить из пакета libcgi-pm-perl. Установим необходимые пакеты:
# apt-get install nginx-light uwsgi git libcgi-pm-perl

Настройка uwsgi

Первым делом настроим сервер приложений uWSGI для запуска CGI-сценария. Для этого создадим файл /etc/uwsgi/apps-available/gitweb.ini со следующим содержимым:
[uwsgi]

procname = uwsgi-gitweb
procname-master = uwsgi-gitweb-master

plugins = cgi
processes = 1
threads = 4
cgi = /usr/share/gitweb/gitweb.cgi
cgi-timeout = 5
Теперь нужно подключить использование этого файла:
# cd /etc/uwsgi/apps-enabled/
# ln -s /etc/uwsgi/apps-available/gitweb.ini .
Перезапустим uWSGI, чтобы его новые настройки вступили в силу:
# systemctl restart uwsgi.service

Настройка Gitweb

Теперь настроим файл конфигурации самого приложения Gitweb. Я буду настраивать приложение в подкаталоге веб-сервера /gitweb/, поэтому у меня файл конфигурации /etc/gitweb.conf принял следующий вид:
# path to git projects (.git)
$projectroot = "/var/lib/git";

# directory to use for temp files
$git_temp = "/tmp";

# target of the home link on top of all pages
$home_link = $my_uri || "/gitweb/";

# html text to include at home page
$home_text = "indextext.html";

# file with project list; by default, simply scan the projectroot dir.
$projects_list = $projectroot;

# stylesheet to use
@stylesheets = ("/gitweb/static/gitweb.css");

# javascript code for gitweb
$javascript = "/gitweb/static/gitweb.js";

# logo to use
$logo = "/gitweb/static/git-logo.png";

# the 'favicon'
$favicon = "/gitweb/static/git-favicon.png";

# git-diff-tree(1) options to use for generated patches
#@diff_opts = ("-M");
@diff_opts = ();

Настройка nginx

Наконец, теперь пришла очередь настраивать веб-сервер nginx. Для настройки откроем файл в каталоге /etc/nginx/sites-enabled/, найдём секцию сервера и впишем в неё следующие настройки:
# Gitweb
location /gitweb/static/ {
  autoindex off;
  alias /usr/share/gitweb/static/;
}

location /gitweb/ {
  uwsgi_pass unix:/run/uwsgi/app/gitweb/socket;
  include uwsgi_params;
  uwsgi_modifier1 9;
  uwsgi_param PATH_INFO $1;
  uwsgi_param GITWEB_CONFIG /etc/gitweb.conf;
}
Перезагрузим nginx, чтобы добавленные нами настройки вступили в силу:
# systemctl reload nginx.service

Размещение репозиториев

Репозитории git, доступ к которым обеспечивает Gitweb, должны располагаться в каталоге /var/lib/git/. Я поместил туда несколько репозиториев для того, чтобы проверить работу приложения. Не забудьте дать права доступа к файлам репозиториев пользователю www-data и группе www-data, например, при помощи следующих команд:
# cd /var/lib/git/
# chown -R www-data:www-data *
В этом каталоге нужно создать подкаталоги с расширением .git, содержащие так называемые "голые" (bare) репозитории. Обычный репозиторий, в котором ведётся разработка, содержит внутри себя текущие версии файлов, над которыми идёт работа, и каталог .git. Каталог .git, по-сути, и является "голым" репозиторием. Для создания "голого" репозитория из обычного можно воспользоваться такими командами:
# cd /var/lib/git
# git clone --bare /home/stupin/git/view3d view3d.git
И не забудьте поменять права доступа на "голый" репозиторий:
# cd /var/lib/git
# chown -R www-data:www-data view3d.git
# chmod -R o= view3d

Тестирование Gitweb

Теперь можно запустить браузер и проверить доступность приложения. У меня стартовая страница приложения выглядела следующим образом:

Как видно из снимка экрана, ссылки на странице не дружественны для человека, что, впрочем, не мешает выполнять основную задачу, для которой это веб-приложение и было создано: просматривать git-репозитории через браузер.

Использованные материалы

воскресенье, 30 июня 2019 г.

Exim и Dovecot quota-status

5 лет назад я написал заметку Exim и Dovecot без SQL, в конце которой имелся такой вот абзац:
Для проверки квот на этапе RCPT сеанса ESMTP можно было бы воспользоваться не только самописным скриптом, но и сервисом проверки квот quota-status, который появился в Dovecot 2.2 (в Debian Wheezy поставляется Dovecot версии 2.1.7), благо Exim позволяет отправлять запросы в юникс-сокеты и читать из них ответ.
С момента написания той заметки я по-прежнему пользуюсь тем почтовым сервером, настройка которого была описана в статье. Сервер пережил несколько обновлений операционной системы, и Dovecot сейчас обновлён до версии 2.2.27.

Не так давно некто Slavko в комментариях к той заметке спросил, удалось ли мне прикрутить quota-satus к Exim. Я попробовал и у меня получилось. В ходе дальнейшей переписки в комментариях найденное решение было улучшено, а также была подтверждена его пригодность для промышленной эксплуатации на серверах с большим потоком входящих писем.

Объясню вкратце, зачем нужен quota-status и в чём сложности его интеграции с Exim.

Когда Dovecot и Exim настраиваются с использованием базы данных SQL, имеется возможность хранить информацию о текущем использовании квот почтового ящика в базе данных. Эту информацию можно использовать в почтовом сервере Exim на этапе приёма писем, чтобы не принимать к доставке письма на переполненные почтовые ящики.

При настройке связки Dovecot и Exim без использования базы данных SQL такой простой способ проверки квот пропадает. Exim не может проверить квоту почтового ящика получателя и принимает письмо к доставке. Если места в ящике нет, Dovecot не принимает письмо от Exim и Exim вынужден слать отправителю письма ответное письмо с сообщением об ошибке - рикошет.

В Dovecot имеется плагин quota-status, который реализует сервис для проверки превышения квоты получателем письма. Небольшая проблема заключается в том, что этот сервис рассчитан на работу с Postfix, т.к. сервис реализует протокол, используемый Postfix.

Однако, в Exim - это не простой почтовый сервер. Т.к. он обладает богатыми возможностями по фильтрации писем, его можно называть своего рода фреймворком для реализации SMTP-серверов. В частности, Exim позволяет передавать запросы в TCP- и Unix-сокеты и читать ответы из них. Этим можно воспользоваться для того, чтобы попытаться воспользоваться сервисом quota-status, который предоставляется Dovecot.

Настройка Dovecot

В файл /etc/dovecot/conf.d/90-quota.conf вписываем:
plugin {
  quota_status_success = OK 
  quota_status_nouser = NOUSER
  quota_status_overquota = OVER
}

service quota-status {
  executable = quota-status -p postfix
  
  unix_listener exim-quota-status {
    mode = 0660
    user = Debian-exim
    group = Debian-exim
  }
  
  client_limit = 1
}
Осталось перезапустить Dovecot:
# systemctl restart dovecot.service
Проверить работу сервиса можно при помощи утилиты socat. В примере ниже проиллюстрирована проверка статуса двух почтовых ящиков:
# socat STDIO UNIX:/var/run/dovecot/exim-quota-status 
recipient=xxx@stupin.su

action=OK

recipient=yyy@stupin.su

action=OVER
Как видно, с почтовым ящиком xxx@stupin.su всё в порядке, а вот у почтового ящика yyy@stupin.su квота превышена.

Настройка Exim

В файл конфигурации /etc/exim4/exim4.conf перед ACL грейлистинга вставляем такую проверку:
defer message = 422 Mailbox $local_part@$domain is over quota
         domains = +local_domains
         condition = ${if eq{${extract{action}\
                                      {${readsocket{/var/run/dovecot/exim-quota-status}\
                                                   {size=$message_size\nrecipient=$local_part@$domain\n\n}\
                                                   {5s}\
                                                   { }\
                                                   {action=FAIL}}}}}\
                            {OVER}\
                            {yes}\
                            {no}}
Указанное выше условие отправляет в сокет /var/run/dovecot/exim-quota-status три строки. В первой строке указан размер письма, во второй - его получатель, третья строка - пустая. Пустая строка сигнализирует о завершении запроса. Дальше в течение 5 секунд ожидается ответ. Если ответ поступил, то все переводы строк в ответе заменяются на пробелы. Если ответ не поступил, то вместо ответа дальше будет использоваться строка "action=FAIL". В результате должна получиться строка, в которой содержится ассоциативный массив, в котором разделителями элементов являются пробелы, а разделителями ключей и значений - знаки "равно", вот такая:
key1=value1 key2=value2 key3=value3
Выражение extract извлекает из этого словаря значение ключа action. Если значение равно OVER, то считается, что ящик переполнен и отправителю сообщается, что он должен отложить письмо в очередь, т.к. в данный момент почтовый ящик одного из получателей переполнен.

Осталось перезапустить Exim:
# systemctl restart exim4.service
Письма на переполненный ящик отбиваются с таким сообщением в журнале:
2019-06-11 22:48:31 H=forward100p.mail.yandex.net [77.88.28.100] X=TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256 CV=no F= temporarily rejected RCPT : 422 Mailbox yyy@stupin.su is over quota

Пригодность решения к промышленному применению

У Slavko возникли опасения, что Exim будет закрывать подключение к Dovecot по истечение 5 секунд даже в тех случаях, когда Dovecot уже ответил на запрос. Такая задержка может привести к проблемам при приёме большого потока входящих писем.

Судя по описанию readsocket на странице Chapter 11 - String expansions, Exim пишет запрос и сразу закрывает ту половинку сокета, которая используется для передачи данных в направлении Dovecot. После этого он читает ответ из оставшейся половинки сокета, в которую Dovecot пишет ответ для Exim. Чтобы Exim не закрывал свою половинку сокета, через которую отправляет данные в Dovecot, нужно после таймаута явным образом указать опцию shutdown=no, вот так: {5s:shutdown=no}. Поведение по умолчанию в нашем случае как раз подходящее, поэтому эту опцию писать не нужно.

Если посмотреть со стороны Dovecot, то он может отреагировать на поведение Exim одним из двух способов:
  1. либо сразу обнаружить закрытие половинки сокета и закрыть вторую половинку, не отправляя ответ,
  2. либо сначала отправить ответ, а потом закрыть свою половинку сокета.
Во-первых, я попробовал сымитировать ситуацию при помощи printf и socat:
# printf "recipient=yyy@stupin.su\n\n" | socat STDIO UNIX:/var/run/dovecot/exim-quota-status 
action=OVER
Как видно, ответ пришёл.

Во-вторых, я попробовал оттрассировать процесс dovecot/quota-status -p postfix при помощи strace и увидел, что сразу после ответа клиентское подключение через Unix-сокет закрывается. "Невооружённым взглядом" заметно, что таймаута в 5 секунд нет, т.к. вся проверка отрабатывает меньше чем за секунду.

Так что за образование очередей из писем, ожидающих проверки квоты ящиков адресатов можно не беспокоиться.

Отклонение адресата вместо отказа от письма

Я пробовал указывать выражение discard, чтобы отбрасывать только тех получателей, ящики которых переполнены. Но если вместо defer написать discard, то в журналах почтового сервера появляются ошибки следующего вида:
2019-06-11 22:35:32 configured error code starts with incorrect digit (expected 2) in "422 Mailbox yyy@stupin.su is over quota"
2019-06-11 22:35:32 H=forward104j.mail.yandex.net [5.45.198.247] F= RCPT : discarded by RCPT ACL: 422 Mailbox ууу@stupin.su is over quota
Exim считает, что его неправильно настроили, т.к. ответ должен начинаться с цифры 2.

Описанная конфигурация тестировалось на Debian Stretch. Готов выслушать замечания или дополнения к описанной конфигурации.

Использованные материалы