Installation of Redmine 3.4.6 on CentOS 7.5 + MySQL 8, Apache 2.4, GIT, SVN, LDAP

This guide presents an installation procedure for Redmine 3.4.6 on a CentOS 7. I wrote it as I was painfully going through multiple HOW-TOs and forums, fixing one error after the other. As a result, there was much back-and-forth before coming up with the right procedure. I tried to detail things as much as I felt necessary.

The procedure results of a mix of many different sources for which I give the source URLs in text below.

Basics like installing CentOS, Apache, or configuring a certificate for Apache are not covered.

Here is the full configuration I got after this installation:
  • CentOS Linux release 7.5.1804 (Core)
  • Apache 2.4
  • MySQL 8.0
  • Redmine 3.4.6
  • Ruby 2.4.4
  • Apache Passenger 5.3.4
  • Integration with Git and Svn
  • Integration with LDAP

IMPORTANT NOTE:
This installation uses Apache 2.4 in which there are quite some configuration syntax changes compared to 2.2. Directives Allow, Deny, Order are obsolete as of Apache 2.4, but you can still use them for compatibility reasons. However, mixing the new Require directives with the former ones has unexpected results. So, do not blindly copy/paste examples you shall find on the Web, most of them are deprecated and will screw your configuration! See details.

Install MySQL 8

Adapted from this page.

Why MySQL 8?

Basically, I first mistakenly installed MySQL 8. Then I uninstalled it and installed 5.7, but then for some reason I could not complete the installation of Redmine. So I reinstalled MySQL 8, and it works fine so far.

[As root/sudo]:
  yum update -y
  yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
  yum --disablerepo=mysql80-community install mysql-community-server mysql-devel
  systemctl start mysqld.service
  systemctl enable mysqld.service

If you prefer to stick to MySQL 5.7, replace the yum install line as follows:

  yum --disablerepo=mysql80-community --enablerepo=mysql57-community install mysql-community-server mysql-devel

Get the password created at installation and run the secure procedure

  grep 'A temporary password is generated for root@localhost' /var/log/mysqld.log | tail -1
  /usr/bin/mysql_secure_installation

Bind-address

By default, MySQL 8 has a bind-address set to 127.0.0.1. If you want to use another hostname, or if it is on a different server than Redmine, then set property bind-address appropriately in MySQL config file (likely /etc/my.cnf).

Default password policy

By default, MySQL 8 sets the default password policy to "caching_sha2_password". Redmine can deal with it but not the Apache module that authenticated git/svn users against the database, and this will generate an error when accessing the repository. Therefore, change the default policy in MySQL config file (likely /etc/my.cnf) by uncommenting the line:

  default-authentication-plugin=mysql_native_password

Install Ruby 2.4

Adapted from this page.

[As root/sudo]:
  yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel \
       libyaml-devel libffi-devel openssl-devel make \
       bzip2 autoconf automake libtool bison iconv-devel sqlite-devel
  curl -sSL https://rvm.io/mpapis.asc | gpg --import -
  curl -L get.rvm.io | bash -s stable
  source /etc/profile.d/rvm.sh
  rvm reload
  rvm requirements run
  rvm install 2.4
  rvm list
  ruby --version

Find the Gem installation path and set the $GEMS variable accordingly, we will use it in later steps, e.g. in my case that was:

  export GEMS=/usr/local/rvm/gems/ruby-2.4.4/gems

Install Redmine

Adapted from this page.

Below, Redmine is installed in directory /home/username/

Download and untar Redmine, install Ruby packages:

[As non-root]:
  cd /home/username
  wget https://www.redmine.org/releases/redmine-3.4.6.tar.gz
  tar xvfz redmine-3.4.6.tar.gz
  export REDMINE=/home/username/redmine-3.4.6
  cd $REDMINE
  cp config/database.yml.example config/database.yml

Customize $REDMINE/config/database.yml as explained in the procedure.

Install GEMS dependencies:

[As root/sudo]:
  cd $REDMINE
  gem install bundler
  bundle install --without development test
  bundle exec rake generate_secret_token
  RAILS_ENV=production REDMINE_LANG=en bundle exec rake redmine:load_default_data

Let's now make sure that Redmine can start using Webricks (later on we shall Apache instead, but that helps checks if there are any issues so far):

[As root/sudo]:
  cd $REDMINE
  bundle exec rails server webrick -e production

and browse to http://localhost:3000/. Alternatively, this other command should work exactly the same:
[As root/sudo]:
  ruby bin/rails server webrick -e production

Note that at this point some HOW-TOs recommand the installation of FastCGI. In this configuation described on this page, I found out that was not necessary.

Install Apache Passenger (an Apache plugin for Ruby environments)

Adapted from this page.

[As root/sudo]:
  yum install -y httpd-devel libcurl-devel apr-devel apr-util-devel mod_ssl
  cd $REDMINE
  gem install passenger

Find out the installation path of Passenger, and call the Apache module installer, e.g. in my case:

  $GEMS/passenger-5.3.4/bin/passenger-install-apache2-module

Follow the steps indicated by the tool. This should include creating an Apache module configuration file. I created /etc/httpd/conf.modules.d/pasenger.conf, you should adapt the Ruby and Passenger paths according to the versions installed above:

  LoadModule passenger_module /usr/local/rvm/gems/ruby-2.4.4/gems/passenger-5.3.4/buildout/apache2/mod_passenger.so
  <IfModule mod_passenger.c>
    PassengerRoot /usr/local/rvm/gems/ruby-2.4.4/gems/passenger-5.3.4
    PassengerDefaultRuby /usr/local/rvm/gems/ruby-2.4.4/wrappers/ruby
  </IfModule>

Configure Apache as the front end for Redmine

As root, create a virtual host entry in /etc/httpd/conf.d/redmine.conf (adapt the paths to your installation):

    <VirtualHost *:80>
        ServerName youserver.domain.org
        DocumentRoot "/home/username/redmine-3.4.6/public" 

        ErrorLog logs/redmine_error_log
        LogLevel warn

        <Directory "/home/username/redmine-3.4.6/public">
            Options Indexes ExecCGI FollowSymLinks
            Require all granted
            AllowOverride all
        </Directory>
    </VirtualHost>

Give Apache write access to some directories. Check the Apache user by looking for propertues User and Group in the Apache main configuraton file (in /etc/httpd/conf or /etc/apache/conf). This is "apache" in my case, but could also be "www-data" in different packagings.

  cd $REDMINE
  chown -R apache:apache files log tmp vendor

Restart apache:
  systemctl restart httpd

There you go: browse to http://youserver.domain.org, you should get the login page. Hurah!
Login with default login "admin" and password "admin", and of course, change the admin password immediatly.

Email config with sendmail

As non-root, edit $REDMINE/config/configuration.yml and uncomment the 2 lines concerning sendmail:

    email_delivery:
      delivery_method:sendmail

And install sendmail if it is not yet installed:

[As root/sudo]:
  yum install -y sendmail sendmail-cf

LDAP authentication

Ask the LDAP details to your administrator and set them in the Web interface: Admin > LDAP authentication

SCM Repos integration - Preliminary

Enable the Web Service for repository management: go to "Administration -> Settings -> Repository" and check "Enable WS for repository management", then clik on "Generate a key" to create a new WS key and save the key that you will use below.

The reposman.rb script needs activeresource to work, run this (if this is already installed, this will just do nothing):

[As root]:
    cd $REDMINE
    gem install activeresource

To restrict the access to the WS, secure the Apache configuration by adding this to the
VirtualHost configuration (/etc/httpd/conf.d/redmine.conf above). Reminder: this is an Apache 2.4 syntax.

   <Location /sys>
      Require host youserver.domain.org sparks-vm5.i3s.unice.fr localhost
      Require all denied
   </Location>

Install Apache packages needed to use the database for authentication (with Git as well as Svn):

[As root/sudo]:
    yum install -y mod_perl perl-DBI perl-DBD-MySQL perl-Digest-SHA1 perl-LDAP

Unlike in other HOW-TOs, there should be no need to use CPAN to install the DBI Perl module for Apache, it must have been installed with perl-DBI. Check where DBI.pm was installed. In my case, it was in /usr/lib64/perl5/vendor_perl, not in /usr/lib/perl5/Apache as often mentioned in other HOW-TOs. Therefore, in the config below, I simply changed the "PerlLoadModule Apache::DBI" (that I found in many examples) into "PerlLoadModule DBI".

Also, link $REDMINE/extra/svn/Redmine.pm into Apache's PERL scripts. You should check the appropriate path, in my case this is /usr/lib64/perl5/vendor_perl/.

[As root/sudo]:
    mkdir /usr/lib64/perl5/vendor_perl/Apache/Authn
    ln -s $REDMINE/extra/svn/Redmine.pm /usr/lib64/perl5/vendor_perl/Authn/Apache

Optional: to allow for LDAP authentication, install the Simple LDAP modules with CPAN. In my cases, it required dependencies Modules::Build and Params::Validate:

[As root/sudo]:
    cpan
    install Modules::Build
    install Params::Validate
    install Authen::Simple::LDAP

Update the Apache Redmine configuration (redmine.conf) as shown below:

    PerlLoadModule Apache::Authn::Redmine
    PerlLoadModule Redmine

    # Enable connection pooling (useful for checkouts with many files)
    PerlModule DBI
    PerlOptions +GlobalRequest

    # Enable LDAP(S) authentication (optional)
    PerlLoadModule Authen::Simple::LDAP
    PerlLoadModule IO::Socket::SSL

Possible authentication issue: MySQL 8 sets the default password policy to "caching_sha2_password". Redmine.pm is not compatible with that and will generate a weird error in the Apache redmine_error_log, like this:

    Can't call method "prepare" on an undefined value at /usr/lib64/perl5/vendor_perl/Apache/Redmine.pm line 364, <DATA> line 747.\n

A closer look at the regular Apache error_log shows another error:
    DBI connect('database=redmine;host=127.0.0.1','redmine',...) failed: Authentication plugin 'caching_sha2_password' cannot be loaded: /usr/lib64/mysql/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory at /usr/lib64/perl5/vendor_perl/Apache/Redmine.pm line 557.

To fix this, change MySQL's password policy for user redmine (found here):

    mysql -uroot -p
    mysql> show variables like 'default_authentication_plugin';
    +-------------------------------+-----------------------+
    | Variable_name                 | Value                 |
    +-------------------------------+-----------------------+
    | default_authentication_plugin | caching_sha2_password |
    +-------------------------------+-----------------------+

    mysql> select host,user,plugin from mysql.user;
    +-----------+------------------+-----------------------+
    | host      | user             | plugin                |
    +-----------+------------------+-----------------------+
    | localhost | mysql.infoschema | caching_sha2_password |
    | localhost | mysql.session    | caching_sha2_password |
    | localhost | mysql.sys        | caching_sha2_password |
    | localhost | redmine          | caching_sha2_password |
    | localhost | root             | caching_sha2_password |
    +-----------+------------------+-----------------------+

    mysql> ALTER USER 'redmine'@'localhost' IDENTIFIED WITH mysql_native_password BY '<YOUR REDMINE PASSWORD>';
    Query OK, 0 rows affected (0,10 sec)

    mysql> select host,user,plugin from mysql.user;
    +-----------+------------------+-----------------------+
    | host      | user             | plugin                |
    +-----------+------------------+-----------------------+
    | localhost | mysql.infoschema | caching_sha2_password |
    | localhost | mysql.session    | caching_sha2_password |
    | localhost | mysql.sys        | caching_sha2_password |
    | localhost | redmine          | mysql_native_password |
    | localhost | root             | caching_sha2_password |
    +-----------+------------------+-----------------------+

SVN integration

Repositories access control with Apache 2.4 with mod_dav_svn and mod_perl

Adapted from this page, section "Using apache/mod_dav_svn/mod_perl".

Make sure SVN CLI packages are already installed.

    yum -y install svn

Create the directory where all SVN repos will/have to be stored:

[As root/sudo]:
    export SVN=/var/lib/svn
    mkdir $SVN
    chown apache:apache $SVN
    chmod 0750 $SVN

Then, there are two options to attach an SVN repository to a project:

1. Explicitly attach a repo to a project

Copy an existing repository to $SVN, or create an new empty repository, and change its owner, like this:

    svnadmin create $SVN/<repo_name>
    chown -R apache:apache $SVN/<repo_name>

Then, on the web interface, select the project you want to attach the repo to, go to Settings > Repositories > New repository. The repo URL should be /var/lib/svn/<repo_name> in the example above.

2. Automatically create SVN repos for declared projects
You can also ask Redmine to automatically create SVN repos for each declared project: the new repo will be name after the project name.

[As root/sudo]:
    cd $REDMINE/extra/svn
    ruby reposman.rb --redmine https://youserver.domain.org --svn-dir $SVN \
                     --owner apache --url http://youserver.domain.org/svn/ \
                     --verbose --key=<ws_key>

In my case, I had declared a single test project, here is the result:

    querying Redmine for active projects with repository module enabled...
    retrieved  projects
    processing project test (test)
        repository /var/lib/svn/test created
        repository /var/lib/svn/test registered in Redmine with url http://youserver.domain.org/svn/test

SVN access from Apache

At this point, we have Redmine configured to create repositories for existing projects. We now have to configure Apache to allow browsing the repos from outside Redmine (typically with an SVN client but also simply with a web browser).

Install Apache needed packages:

[As root/sudo]:
    yum install -y mod_dav_svn

The installation should add loading of DAV and SVN modules to the Apache configuration. Check and fix this if necessary:

    $ cat /etc/httpd/conf.modules.d/00-dav.conf 
    LoadModule dav_module modules/mod_dav.so
    LoadModule dav_fs_module modules/mod_dav_fs.so
    LoadModule dav_lock_module modules/mod_dav_lock.so

    $ cat /etc/httpd/conf.modules.d/10-subversion.conf 
    LoadModule dav_svn_module     modules/mod_dav_svn.so
    LoadModule authz_svn_module   modules/mod_authz_svn.so
    LoadModule dontdothat_module  modules/mod_dontdothat.so

Update the Apache Redmine configuration (redmine.conf) as shown below. Note that there is no need for a <Location /svn-private> section as shown in older HOW-TOs.

    # Enable SVN access from outside Redmine (web browser, SVN client)
    <Location /svn>
        DAV svn
        SVNParentPath "/var/lib/svn" 
        SVNReposName "Subversion Repository" 
        Require all denied

        # Uncomment the following line when using subversion 1.8 or newer
        # (see http://subversion.apache.org/docs/release-notes/1.8.html#serf-skelta-default)
        # SVNAllowBulkUpdates Prefer

        # If a client tries to svn update which involves updating many files,
        # the update request might result in an error Server sent unexpected
        # return value (413 Request Entity Too Large) in response to REPORT
        # request, because the size of the update request exceeds the limit
        # allowed by the server. You can avoid this error by disabling the
        # request size limit by adding the line LimitXMLRequestBody 0
        LimitXMLRequestBody 0

        # Only check Authentication for root path, nor again for recursive folder.
        # Redmine core does only permit access on repository level, so this
        # doesn't hurt security. On the other hand it does boost performance a lot!
        SVNPathAuthz off

        PerlAccessHandler Apache::Authn::Redmine::access_handler
        PerlAuthenHandler Apache::Authn::Redmine::authen_handler
        AuthType Basic
        AuthName "Redmine SVN Repository" 
        AuthUserFile /dev/null

        # Read-only access    
        <Limit GET PROPFIND OPTIONS REPORT>
            # Match either the valid user or local source conditions (equivalent to "Satisfy any" in Apache 2.2)
            <RequireAny>
                Require valid-user
                Require local
            </RequireAny>
        </Limit>

        # Write access (methods POST, PUT)
        <LimitExcept GET PROPFIND OPTIONS REPORT>
            Require valid-user
        </LimitExcept>

        # Mysql config. You may use localhost instead of <your.mysql.hostname> if MySQL is on the same server
        RedmineDSN "DBI:mysql:database=redmine;host=<your.mysql.hostname>" 
        RedmineDbUser "redmine" 
        RedmineDbPass "<password>" 
    </Location>

Git integration

Create the directory where all GIT repos will/have to be stored:

[As root/sudo]:
    export GIT=/var/lib/git
    mkdir $GIT
    chown apache:apache $GIT
    chmod 0750 $GIT

Note: Redmine can only deal with bare Git repositories (see http://www.saintsjd.com/2011/01/what-is-a-bare-git-repository/). You can get a bare Git repo in two ways: "git init --bare" or "git close --bare ...".

In the case of a fresh new local repository (git init --bare), Redmine will be able to display its content only after the first commit. Try this:

[As root/sudo]:
    # Create a local bare repo
    mkdir -p $GIT/test.git
    chown apache:apache $GIT/test.git/
    cd $GIT/test.git/
    git init --bare

    # Create another repo as a clone of the previous one, make one commit and push.
    cd ..
    mkdir -p $GIT/test.local.git
    cd $GIT/test.local.git/
    git init
    touch TEST.txt
    git add TEST.txt
    git commit -m "repository initalization" 
    git push $GIT/test.git/ master

Now that there is commit in your bare repo, you can browse it via Redmine: create a project and attach test.git as the main repo. Then go to the Repository tab, you should see the log of the first commit.

Update the Apache Redmine configuration (redmine.conf) as shown below. Note that there is no need for a <Location /git-private> section as shown in older HOW-TOs.

    #--- Enable Git access from outside Redmine
    ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/

    SetEnv GIT_PROJECT_ROOT /var/lib/git
    SetEnv GIT_HTTP_EXPORT_ALL

    <Location /git>
        SSLRequireSSL
        PerlAccessHandler Apache::Authn::Redmine::access_handler
        PerlAuthenHandler Apache::Authn::Redmine::authen_handler
        AuthType Basic
        AuthName "Redmine Git Repository" 
        AuthUserFile /dev/null
        Require all denied

        <Limit GET PROPFIND OPTIONS REPORT>
            Options Indexes FollowSymLinks MultiViews
            # Match either the valid user or local source conditions (equivalent to "Satisfy any" in Apache 2.2)
            <RequireAny>
                Require valid-user
                Require local
            </RequireAny>
        </Limit>

        # Mysql config. You may use localhost instead of <your.mysql.hostname> if MySQL is on the same server
        RedmineDSN "DBI:mysql:database=redmine;host=<your.mysql.hostname>" 
        RedmineDbUser "redmine" 
        RedmineDbPass "<password>" 
        RedmineGitSmartHttp yes
    </Location>