Проблемы при установке Gerrit на CentOS 6.3

Gerrit — это инструмент для code-review при совместной разработке командой девелоперов: http://code.google.com/p/gerrit/. Он тесно интегрирован с репозиторием Git. В нашей компании мы используем оба эти решения, и я как системный администратор отвечаю за их установку и поддержку. 

Как часто бывает у свободно распространяемых продуктов, процедура их установка не универсальна и не всегда проходит гладко. В данной статье я не стараюсь заменить официальную документацию, а лишь опишу  некоторые проблемы, которые возникли у меня при установке Gerrit версии 2.4 на машину с CentOS 6.3 x64. Официальная же документация по установке расположена здесь: http://gerrit-documentation.googlecode.com/svn/Documentation/2.4/index.html

Java

Пакет Java идущий в дистрибутиве CentOS’а java-1.5.0-gcj-1.5.0.0-29.1 работать не захотел. С ним при установке gerrit выдается ошибка:

$ java -jar gerrit-2.4.war init -d /home/gerrit2
fatal: Gerrit Code Review requires Java 6 or later                    
       (trying to run on Java 1.5
$
 

В дистрибутиве CentOS 6.3 отсутствует Java 1.6 поэтому пришлось установить пакет jre-6u35-linux-amd64.rpm с официального сайта Java: http://www.java.com/ru/download/index.jsp. С этой версией инсталлятор gerrit’а заработал.  Вы можете держать в системе оба этих пакета — пакет из дистрибутива СentOS и пакет с официального сайта, но вы должны выбрать «правильную» версию Java, которая будет использоваться по умолчанию:

# alternatives --config java                     
There are 2 programs which provide 'java'.
  Selection    Command
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.5.0-gcj/bin/java
   2           /usr/java/latest/bin/java         
Enter to keep the current selection[+], or type selection number: 2

MySQL Database

База данных MySQL требует небольшого тюнинга.

В документации рекомендуют создавать базу с кодировкой latin1. Но для нас это не годится, т.к. мы планируем использовать русский язык в комментариях к коду, посему нам нужна кодировка utf8. Но просто сменив кодировку на utf8, мы все равно получаем при установке gerrit’а ошибку:

Exception in thread "main" com.google.gwtorm.server.OrmException: Cannot apply SQL
CREATE TABLE account_project_watches (                                           
notify_new_changes CHAR(1) DEFAULT 'N' NOT NULL  CHECK (notify_new_changes IN ('Y','N')),
notify_all_comments CHAR(1) DEFAULT 'N' NOT NULL  CHECK (notify_all_comments IN ('Y','N')),
notify_submitted_changes CHAR(1) DEFAULT 'N' NOT NULL  CHECK (notify_submitted_changes IN ('Y','N')),
account_id INT DEFAULT 0 NOT NULL,                                                                  
project_name VARCHAR(255) BINARY DEFAULT '' NOT NULL,                                               
filter VARCHAR(255) BINARY DEFAULT '' NOT NULL                                                      
,PRIMARY KEY(account_id,project_name,filter)                                                        
)                                                                                                   
        at com.google.gwtorm.jdbc.JdbcExecutor.execute(JdbcExecutor.java:44)                        
        at com.google.gwtorm.jdbc.JdbcSchema.createRelations(JdbcSchema.java:84)                    
        at com.google.gwtorm.jdbc.JdbcSchema.updateSchema(JdbcSchema.java:54)                       
        at com.google.gerrit.server.schema.SchemaCreator.create(SchemaCreator.java:108)             
        at com.google.gerrit.server.schema.SchemaUpdater.update(SchemaUpdater.java:55)              
        at com.google.gerrit.pgm.Init$SiteRun.upgradeSchema(Init.java:181)                          
        at com.google.gerrit.pgm.Init.run(Init.java:79)                                             
        at com.google.gerrit.pgm.util.AbstractProgram.main(AbstractProgram.java:67)                 
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)                              
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)            
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)    
        at java.lang.reflect.Method.invoke(Method.java:616)                                         
        at com.google.gerrit.launcher.GerritLauncher.invokeProgram(GerritLauncher.java:167)         
        at com.google.gerrit.launcher.GerritLauncher.mainImpl(GerritLauncher.java:91)               
        at com.google.gerrit.launcher.GerritLauncher.main(GerritLauncher.java:49)                   
        at Main.main(Main.java:25)                                                                  
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Specified key was too long; max key length is 1000 bytes
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)                                             
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)                      
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)              
        at java.lang.reflect.Constructor.newInstance(Constructor.java:532)                                                   
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)                                                              
        at com.mysql.jdbc.Util.getInstance(Util.java:381)                                                                    
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1030)                                                    
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)                                                     
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3558)                                                        
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3490)                                                        
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1959)                                                             
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2109)                                                          
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2642)                                                   
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2571)                                                   
        at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:782)                                                      
        at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:625)                                                      
        at com.google.gwtorm.jdbc.JdbcExecutor.execute(JdbcExecutor.java:42)                                                 
        ... 15 more

Осмыслив описание проблемы в Java-эксепшене «Specified key was too long; max key length is 1000 bytes» и просуммировав в уме длины полей первичного ключа таблицы (255 + 255 + sizeof(INT)) видим несоответствие. Эксепшен возникает из-за физического ограничения типа хранилища MyISAM. А несоответствие в цифрах объясняется тем, что в типе VARCHAR при использовании кодировки utf-8 один символ кодируется не одним, а двумя байтами. Отсюда и получается превышение лимита. Так что лучше не экспериментируйте, а сразу создавайте таблицы в типе InnoDB. Сделать это проще всего с помощью включения в секцию [mysqld] конфига MySQL /etc/my.cnf следующей директивы:

default-storage-engine=InnoDB

а также добавления строчек с основными настройками InnoDB:

# Uncomment the following if you are using InnoDB tables
innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /var/lib/mysql

После рестарта mysql сервера уже можем создавать базу данных:

CREATE USER 'gerrit2'@'localhost' IDENTIFIED BY 'secret';
CREATE DATABASE reviewdb;
ALTER DATABASE reviewdb charset=utf8;
GRANT ALL ON reviewdb.* TO 'gerrit2'@'localhost';
FLUSH PRIVILEGES;

Установка веб-интерфейса

sudo adduser gerrit2
sudo su gerrit2

Небольшое отличие от настроек по умолчанию — мы используем HTTP-авторизацию, а также, как было указано выше, java с официального сайта, поэтому этот шаг инсталляции проходим следующим образом:

# java -jar gerrit-2.4.war init -d /home/gerrit2
*** Gerrit Code Review 2.4
***
 
*** Git Repositories
***
Location of Git repositories [git]:
*** SQL Database
***
Database server type [H2/?]: mysql
Server hostname [localhost]:
Server port [(MYSQL default)]:
Database name [reviewdb]:
Database username [gerrit2]:
gerrit2's password : secret
 confirm password : secret
*** User Authentication
***
Authentication method [OPENID/?]: http
Get username from custom HTTP header [y/N]?
SSO logout URL :
*** Email Delivery
***
SMTP server hostname [localhost]:
SMTP server port [(default)]:
SMTP encryption [NONE/?]:
SMTP username :
*** Container Process
***
Run as [gerrit2]:
Java runtime [/usr/lib/jvm/jre-1.5.0-gcj]: /usr/java/jre1.6.0_35
Copy gerrit.war to /home/gerrit2/bin/gerrit.war [Y/n]?
Copying gerrit.war to /home/gerrit2/bin/gerrit.war
*** SSH Daemon
***
Listen on address [*]:
Listen on port [29418]:
Gerrit Code Review is not shipped with Bouncy Castle Crypto v144
 If available, Gerrit can take advantage of features
 in the library, but will also function without it.
Download and install it now [Y/n]?
Downloading http://www.bouncycastle.org/download/bcprov-jdk16-144.jar ... OK
Checksum bcprov-jdk16-144.jar OK
Generating SSH host key ... rsa... dsa... done
*** HTTP Daemon ***
 
Behind reverse proxy [y/N]? y
Proxy uses SSL (https://) [y/N]? y
Subdirectory on proxy server [/]:
Listen on address [*]:
Listen on port [8081]:
Canonical URL [https://gerrit.mydomain.ru/]:
Initialized /home/gerrit2

Обязательный тюнинг конфигов

Вот здесь начинается самое интересное. 

Первым делом создаем файл /etc/default/gerritcodereview. В нем обязательно должна быть как минимум одна строка:

GERRIT_SITE=/home/gerrit2

Редактируем /home/gerrit2/etc/gerrit.config. Часть этих настроек уже может присутствовать.

# Путь к репозиториям git. В нашем случае это /home/gerrit2/git
# canonicalWebUrl - URL. В документации пишут что оно не обязательно, но без него у меня наблюдались проблемы.
[gerrit]
        basePath = git
        canonicalWebUrl = https://gerrit.mydomain.ru
# Информация о базе данных. Обратите внимание на запись url. 
# Без нее большие проблемы с отображением и хранением русских букв, т.е. с кодировкой. 
# url - настроки для доступа к базе данных. Я нашел эту информацию в документации по MySQL.
# Без такого url'а русский язык отображаться не будет.
[database]
        type = MYSQL
        hostname = localhost
        database = reviewdb
        username = gerrit2
        url = jdbc:mysql://localhost/reviewdb?useUnicode=true&characterEncoding=utf8&characterSetResult=utf8&connectionCollation=utf8_bin
# Тип аутентификации. У нас это HTTP
[auth]
        type = HTTP
# Почта
[sendemail]
        smtpServer = localhost
# Месторасположение JAVA
[container]
        javaHome = /usr/java/jre1.6.0_35/
# Параметры встроенного ssh-сервера. maxAuthTries - по умолчанию 6, в рабочих условиях мне было мало.
[sshd]
        listenAddress = *:29418
        maxAuthTries = 10
# Встроенный proxy-сервер. Apache описываемый далее, авторизует пользователей по HTTP и перенаправляет запросы на этот встроенный HTTP-сервер организованый на jetty.
[httpd]
        listenUrl = proxy-https://127.0.0.1:8081/
        requestLog = true
# Дисковый кэш. У нас это /home/gerrit2/cache
[cache]
        directory = cache
# Уже не помню для чего именно я это прописывал, но что-то без этого не работало.
[user]
        email = sysadm@mydomain.ru
# Мелкая красявость в review
[commentlink "changeid"]
        match = (I[0-9a-f]{8,40})
        link = "#q,$1,n,z"
# В release note к версии gerrit 2.4.2 категорически рекомендуется или обновиться или сделать нижеследущие настройки
[cache "permission_sort"]
        memoryLimit = 0
        maxAge = 1s
# Месторасположение gitweb'а в CentOS 6.3 отличается от настроек gerrit по умолчанию.
[gitweb]
        cgi = /var/www/git/gitweb.cgi

Редактируем /home/gerrit2/etc/secure.config. В нём тоже самое, что и в предыдущем конфиге. Но хранится он отдельно, для большей безопастности.

# Пароль к базе данных
[database]
        password = secret
# Хэш генерирующийся автоматически во время установки. Используется gerrit'ом для собственных нужд.
[auth]
        registerEmailPrivateKey = LONGHASHKEYXXXXXXXXXXXXXXX

Apache

Использую следующие настройки /etc/httpd/conf.d/gerrit_mydomain_ru.conf 

443>
          ServerName gerrit.mydomain.ru
 
          SetEnvIf User-Agent ".*MSIE.*" \
          nokeepalive ssl-unclean-shutdown \
          downgrade-1.0 force-response-1.0
 
 
          LogLevel warn
          SSLEngine on
          SSLProtocol all -SSLv2
          SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
 
 
          SSLCertificateFile sslcert/server.crt
          SSLCertificateKeyFile sslcert/server.key
          SSLCertificateChainFile sslcert/server.crt
          SSLVerifyClient none
 
 
          ProxyRequests Off
          ProxyVia Off
          ProxyPreserveHost On
 
 
          
                Order deny,allow
                Allow from all
          
 
 
          
            AuthType Basic
            AuthName "Gerrit Code Review"
            AuthUserFile /home/gerrit2/etc/.htpasswd
            Require valid-user
          
 
          # проксируемся к 8081 порту
          ProxyPass / http://127.0.0.1:8081/
 

При данных настройках не забываем включать модули апача: sslproxyproxy_http. А также добавлять пользователей с паролями в файл /home/gerrit2/etc/.htpasswd. Первый залогинившийся пользователь получает права администратора.