null

Массово плодим индивидуальные svn репозитории

subversion -- тема древняя и заезженная, но гугл молчал как партизан, когда речь зашла о поднятии нескольких svn серверов на freebsd 10. Задача состояла в том, что к уже имеющемуся svnserve понадобилось добавить независимый процесс svn сервера, который впоследствии придётся выпустить неприкрытой пятой точкой в интернет. Сервер должен иметь несколько администраторов, имеющих доступ ко всем репозиториям и непересекающихся пользователей, каждому из которых выдаётся отдельный репозиторий. Состав пользователей обещал динамично меняться, поэтому процесс неплохо бы худо-бедно автоматизировать. 

Идеальным решением представлялась встроенная функциональность svnserve. Multiple svnserve в документации не описан. Поэтому первым делом, конечно же, /usr/local/etc/rc.d/svnserve, используемый для строго внутренних репозиториев, был клонирован и доработан до svnserve_ext. Попытка успехом не увенчалась. Трассировка подсказала, что /usr/local/bin/svnserve.bin при запуске ищет себя в таблице процессов и напрочь отказывается запускать два инстанса. 

Вместе с тем в голову пришла давно забытая мысль: svnbook когда-то (и до сих пор) упоминал, что svnserve -- это продукт "на поиграться", а настоящие мужики используют webdav (имеющий ряд преимуществ и существенный performance degradation). Тем не менее, статьи по совокуплению svn и apache на freebsd среди первых ссылок гугла найдено не было, поэтому в этой статье также коротко рассмотрим проделанные шаги сразу на примере скрипта, создающего репозитории.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/sh -e
#made by: KorG
 
USAGE="$0 repo password"
 
DIR="/usr/svn_ext"
SRC="/usr/svn_ext_admin"
REPO="${1?Please specify repository name: $USAGE}"
DST="$DIR/$REPO"
USER="$REPO"
PASS="${2?Please specify password: $USAGE}"
 
panic(){
   echo "$*" >&2
   exit 1
}
 
fgrep -ix "$USER" "$SRC/admins" >/dev/null && panic user is admin
[ -d "$DST" ] && panic "$DST exists"
 
svnadmin create "$DST"
 
cd "$SRC/template/"
find . -type f -exec cp {} "$DST"/{} \;
 
htpasswd -bm "$SRC/svn-authz.htpasswd" "$USER" "$PASS"
 
cat >> "$SRC/svn-authz" <<@
 
[$REPO:/]
$USER = rw
@

Для работы решения потребуются пакеты:

1
# portmaster -D www/mod_dav_svn devel/subversion www/apache24

Пусть имеется каталог для репозиториев /usr/svn_ext ($DIR) и вспомогательный каталог /usr/svn_ext_admin ($SRC). В качестве имени репозитория выступает имя пользователя, которому этот репозиторий предоставляется. Расположим список имён администраторов в файле /usr/svn_ext_admin/admins. Это нужно для того, чтобы впоследствии не изменить пароль одному из администраторов. 

Кроме того, в /usr/svn_ext/admin расположим каталог template, который будет использоваться в качестве патча для создаваемых репозиториев. Если планируется использовать svnserve, туда можно положить config/passwd, config/authz и config/svnserve.conf; нас же интересует каталог /usr/svn_ext_admin/template/hooks/. Там я расположил два хука. Один из них -- это копия стандартного примера pre-revprop-change.tmpl, который позволяет изменять svn log и дополнительно проверяет формат коммит сообщения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/sh
 
REPOS="$1"
REV="$2"
USER="$3"
PROPNAME="$4"
ACTION="$5"
 
panic(){ echo "$*" >&2 ;exit 1 ;}
 
if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then
   perl -e 'my $in; eval{local$SIG{ALRM} = sub{die}; alarm(2); $in=<>};
   exit 1 unless $in =~ /\b(ref|fix|close|reopen|open|start|take)s?\s+
   #[0-9]+\s+@[0-9]*([1-9]($|\s)|[0-9]\.[1-9][0-9]*\b)/x; exit 0' ||
   panic taskid or timetrack not found in the log message
 
   exit 0;
fi
 
exit 1

Второй же -- pre-commit, скрипт выполняющийся после создания на svn сервере транзакции, но до её коммита. В нём я проверяю размер загружаемых файлов (ограничивая их четырьмя мегабайтами) и снова формат коммит-сообщения. Содержимое хука ниже:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh -e
 
REPO="$1"
TXN="$2"
SIZEMAX="4194304"
KEYS="ref|fix|close|reopen|open|start|take"
SVNLOOK=/usr/local/bin/svnlook
 
panic() {
   echo "$*" >&2
   exit 1
}
 
# Check commit message format
$SVNLOOK log -t "$TXN" "$REPO" |
egrep -q "\\<($KEYS"')s? +#[0-9]+ +@[0-9]*([1-9]($| )|[0-9]\.[1-9][0-9]*\>)' ||
   panic taskid or timetrack not found in the log message
 
# Check file sizes
( $SVNLOOK changed -t "$TXN" "$REPOS" | while read ACT FILE ;do
[ "$SIZEMAX" -ge "`$SVNLOOK filesize -t "$TXN" "$REPOS" "$FILE"`" ] || exit 1
done ) || panic filesize exceeded limit
 
# All checks passed, so allow the commit
exit 0

В моей инсталляции используется Redmine, который настроен на отлавливание определённых ключевых слов, перечисленных в переменной KEYS, форсирован учёт трудозатрат и номера задачи. 

Теперь, чтобы это всё заработало, нам понадобится создать начальные версии файлов svn-authz и svn-authz.htpasswd. В первом файле нужно для каждого администратора указать права:

1
2
3
4
[/]
admin1 = rw
admin2 = rw
...

Впрочем, его можно создать и скриптом:

1
2
# cd /usr/svn_ext_admin
# ( echo '[/]' ;while read adm ;do echo "$adm = rw" ;done < admins ) > svn-authz

Второй файл -- это база данных паролей. Её можно создать с помощью утилиты htpasswd с ключом "-c". Каждый раз при его указании база данных пересоздаётся, поэтому этот ключ нужен только однажды. 

1
2
3
# htpasswd -mc /usr/svn_ext_admin/svn-authz.htpasswd admin1
# htpasswd -m /usr/svn_ext_admin/svn-authz.htpasswd admin2
...

Наконец, приложу файл, который я Include-ю в настройках apache, в секцию VirtualHost, предназначенную для default SSL сервера, поскольку подключение будем шифровать (для упрощения работы с proxy и для хоть какого-то шифрования):

1
2
3
4
5
6
7
8
9
<Location /svn>
   DAV svn
   SVNParentPath /usr/svn_ext
   AuthType Basic
   AuthName "zhmylove svn"
   AuthUserFile /usr/svn_ext_admin/svn-authz.htpasswd
   AuthzSVNAccessFile /usr/svn_ext_admin/svn-authz
   Require valid-user
</Location>

Также не забываем включить необходимые модули в httpd.conf:

1
2
3
4
5
LoadModule dav_module libexec/apache24/mod_dav.so
LoadModule dav_svn_module libexec/apache24/mod_dav_svn.so
LoadModule auth_basic_module libexec/apache24/mod_auth_basic.so
LoadModule authz_core_module libexec/apache24/mod_authz_core.so
LoadModule authz_svn_module libexec/apache24/mod_authz_svn.so

Кроме того, у меня перед apache стоит reverse proxy nginx, средствами которого также можно/нужно контролировать размер загружаемых файлов. В моём случае я просто добавил в нужный location директиву:

1
client_max_body_size 4m;

После этого перезапускаем apache, релоадим nginx и создаем первый репозиторий:

1
2
3
# service apache24 restart
# service nginx reload
# ./create_repo.sh student1 password

В Redmine эти репозитории можно подключать через схему file://, а конечный пользователь может их чекаутить по схеме https://. 

korg

 

Коротко о себе

Работаю в компании Tune-IT, администрирую инфраструктуру компании и вычислительную сеть кафедры Вычислительной ТехникиСПбНИУ ИТМО.

Интересы: администрирование UNIX и UNIX-like систем и активного сетевого оборудования, написание shell- и perl-скриптов, изучение технологий глобальных сетей.
Люблю собирать GNU/Linux и FreeBSD, использовать тайлинговые оконные менеджеры и писать системный софт.