Forge Redmine sur Debian Jessie

Date du déploiement : 25 mai 2014

Objectif : Déployer une forge Redmine (2.5.1) et son greffon Redmine Git Hosting (0.7.2) sur un système GNU/Linux Debian Jessie en cloisonnant son environnement d'exécution Ruby via RVM afin que la forge ne soit pas affectée par des modifications effectuées au niveau système. La machine hôte hébergeant déjà des applications servies par le serveur HTTP Apache, la forge doit être accessible via ce serveur et un hôte virtuel dédié : https://forge.dinot.net/

Si la recette proposée ici vous semble perfectible ou si vous pensez que certains points mériteraient d'être étoffés, n'hésitez pas à me faire part de vos remarques, elles sont les bienvenues.

À ce sujet, merci à Sébastien Besombes et à Julien Malik pour leur retour d'expérience et leurs suggestions.

La forge repose sur quatre composants :

  • Le serveur HTTP Apache
  • La forge Redmine, application Ruby on Rails accessible au travers du serveur HTTP via le serveur d'application Phusion Passenger
  • Le système de gestion de bases de données PostgreSQL
  • Le système de gestion de version décentralisé Git dont les dépôts sont gérés via Gitolite, lui-même géré via l'interface web de la forge grâce au module Redmine Git Hosting.

Afin de cloisonner les environnements et de mieux gérer les droits, les processus de ces quatre composants sont exécutés par des comptes différents :

  • Serveur HTTP Apache : www-data
  • Forge Redmine : redmine
  • SGBDR PostgreSQL : postgres
  • Git / Gitolite : git

Concernant le déploiement et la configuration du serveur HTTP Apache et du SGBDR PostgreSQL, seuls les points spécifiques à la forge Redmine sont traités ici. Le reste fait l'objet d'une abondante documentation sur le net.

Création et configuration du compte système

On crée un compte système nommé redmine dont le répertoire d'accueil est /opt/redmine :

# adduser --system --shell /bin/bash --gecos 'Redmine Administrator' \
          --group --disabled-password --home /opt/redmine redmine

Le compte redmine sera par la suite déclaré administrateur de Gitolite. Gitolite étant lui-même piloté via un référentiel Git, le compte administrateur a besoin d'un accès complet sur ce référentiel et donc d'une clé SSH. Cette clé n'est pas protégée par un mot de passe afin qu'elle puisse être utilisée par des commandes non interactives :

# su - redmine
$ ssh-keygen -N '' -f ~/.ssh/redmine_gitolite_admin_id_rsa

Afin que le client SSH présente la clé adéquate au serveur SSH, on précise les paramètres de configuration de cet accès dans le fichier ~/.ssh/config :

Host forge.dinot.net
     Hostname forge.dinot.net
     Port 22
     User git
     IdentityFile ~/.ssh/redmine_gitolite_admin_id_rsa
     IdentitiesOnly yes
 
Host localhost
     Hostname forge.dinot.net
     Port 22
     User git
     IdentityFile ~/.ssh/redmine_gitolite_admin_id_rsa
     IdentitiesOnly yes

Les deux alias forge.dinot.net et localhost doivent être définis dans le fichier ~/.ssh/config car certaines commandes du greffon Redmine Git Hosting utilisent localhost même lorsqu'un hôte différent est déclaré dans la configuration.

Les connexions sur l'hôte localhost échouent si le serveur SSH a été configuré pour n'écouter que l'adresse IP publique (et donc pas l'adresse locale) du serveur. Le cas échéant, il faut modifier le paramètre ListenAddress du fichier /etc/ssh/sshd_config.

Le déploiement de RVM peut nécessiter l'installation préalable d'outils ou de paquets de développement via le système de gestion de paquets standard de Debian. Le cas échéant, RVM s'en charge à condition que le compte dans lequel on déploie Ruby soit momentanément autorisé à endosser l'identité root via sudo. On le déclare donc comme tel dans le fichier /etc/sudoers:

redmine  ALL=(ALL:ALL) NOPASSWD:ALL

Cette capacité et donc la ligne ci-dessus doivent être supprimées sitôt l'environnement Ruby et le serveur d'application Phusion Passenger déployés.

Déploiement de Ruby via RVM

RVM crée un environnement Ruby propre au compte dans lequel on le déploie, ce qui préserve dans une certaine mesure les applications lancées par ce compte des changements pouvant intervenir au niveau système. Cette pratique est d'autant plus recommandée que les applications Ruby s'avèrent très sensibles aux changements d'environnement et que les bibliothèques Ruby évoluent beaucoup. On lance le déploiement de RVM et de Ruby (ici en version 2.0.0) via la commande :

# su - redmine
$ curl -L https://get.rvm.io | bash -s stable --ruby=2.0.0

RVM et Ruby sont alors déployés dans le répertoire ~/.rvm/. Le script d'installation de RVM ajoute à la fin des scripts ~/.profile et ~/.bashrc la ligne suivante :

export PATH="$PATH:$HOME/.rvm/bin" # Add RVM to PATH for scripting

Mais elle n'est pas suffisante : Gitolite fonctionne mais les changements opérés dans les référentiels Git n'apparaissent pas dans l'interface web de Redmine car les « hooks » se synchronisation reposent sur l'interpréteur Ruby que Gitolite ne trouve pas.

On la remplace donc dans le script ~/.bashrc par les instructions suivantes :

# Add to PATH the rvm binaries directory if it exists
if [ -d "$HOME/.rvm/bin" ] ; then
  export PATH="$PATH:$HOME/.rvm/bin"
fi
 
# Source RVM script if it exists
if [ -s "$HOME/.rvm/scripts/rvm" ] ; then
  source "$HOME/.rvm/scripts/rvm"
fi

Les instructions ci-dessus sont à placer en tête de script afin qu'elles ne soient pas court-circuitées par des instructions qui provoquent la fin prématurée du script telles que celle-ci :

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

Et dans le script ~/.profile, on s'assure que le précédent est bien évalué :

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi
 
# Include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
fi

L'ajout du chemin ~/bin à la variable d'environnement PATH n'est pas requis par RVM et n'a rien de propre à la forge Redmine mais si la nécessité de créer des scripts quelconques apparaît, il est plus propre de les regrouper dans ce répertoire et commode de ne pas avoir à préciser leur chemin d'accès absolu.

Une fois cet environnement initialisé, il faut installer le gestionnaire de dépendances Ruby bundler :

# su - redmine
$ gem install bundler

Déploiement de Phusion Passenger

Une fois l'environnement Ruby spécifique à Redmine déployé, il faut générer une version adéquate du serveur d'application Phusion Passenger :

# su - redmine
$ gem install passenger
$ passenger-install-apache2-module

Parmi tous les langages proposés, seul le langage Ruby est ici nécessaire. On peut donc désactiver le support des autres pour ne pas alourdir inutilement cette bibliothèque.

  • Si un module Passenger a déjà été déployé (via un paquet Debian standard par exemple), il faut le désinstaller avant de lancer le script ci-dessus.
  • Si des sites HTTPS sont déjà configurés dans Apache, il faut les désactiver momentanément. Sans cette précaution, le script passenger-install-apache2-module échoue lors du contrôle de la configuration d'Apache car il n'arrive pas à accéder aux clés privées indiquées dans la configuration.

À partir de maintenant, le compte redmine n'a plus besoin d'agir sous l'identité root. Il faut donc supprimer l'entrée correspondante dans le fichier /etc/sudoers.

Une fois le script terminé, il faut créer les fichiers de configuration de ce module Apache :

  • /etc/apache2/mods-available/passenger.load
LoadModule passenger_module /opt/redmine/.rvm/gems/ruby-2.0.0-p481/gems/passenger-4.0.42/buildout/apache2/mod_passenger.so
  • /etc/apache2/mods-available/passenger.conf
<IfModule mod_passenger.c>
    PassengerRoot /opt/redmine/.rvm/gems/ruby-2.0.0-p481/gems/passenger-4.0.42
    PassengerDefaultRuby /opt/redmine/.rvm/gems/ruby-2.0.0-p481/wrappers/ruby
</IfModule>

Le contenu de ces fichiers peut légèrement varier en fonction de la version de Ruby et du répertoire d'accueil choisis. Afin d'éviter toute erreur, copiez dans les fichiers ci-dessus les lignes affichées par le script de déploiement du module de Phusion Passenger à l'issue de son exécution.

On active alors le module Phusion Passenger et on relance le serveur HTTP Apache afin qu'il le prenne en compte :

# a2enmod passenger
# service apache2 restart

Déploiement de l'application Redmine

Exécuter les commandes suivantes sous l'identité redmine :

# su - redmine
$ wget http://www.redmine.org/releases/redmine-2.5.1.tar.gz
$ tar xzf redmine-2.5.1.tar.gz
$ ln -s redmine-2.5.1 current
$ cd current
$ cd config
$ cp database.yml.example database.yml
$ cp configuration.yml.example configuration.yml
$ cp additional_environment.rb.example additional_environment.rb

Puis renseigner les paramètres qui suivent dans les fichiers :

  • database.yml
# PostgreSQL configuration
production:
  adapter:  postgresql
  host:     localhost
  database: redmine
  username: redmine
  password: "M02PassePaPhass!le"
  encoding: utf8
  schema_search_path: public
  • configuration.yml
[...]
production:
  email_delivery:
    delivery_method: :async_smtp
    async_smtp_settings:
      address: mail.dinot.net
      port: 25
      domain: dinot.net
      authentication: :none
[...]
attachments_storage_path: /opt/redmine/current/files
[...]
scm_git_command: /usr/bin/git
[...]
  • additional_environment.rb
#Logger.new(PATH,NUM_FILES_TO_ROTATE,FILE_SIZE)
config.logger = Logger.new('/opt/redmine/current/log/redmine.log', 7, 1000000)
config.logger.level = Logger::WARN

On crée ensuite la base de données redmine à laquelle on accède via un compte dédié redmine :

# su - postgres
$ psql
postgres=# CREATE ROLE redmine LOGIN ENCRYPTED PASSWORD 'M02PassePaPhass!le' NOINHERIT VALID UNTIL 'infinity';
postgres=# CREATE DATABASE redmine WITH ENCODING='UTF8' OWNER=redmine TEMPLATE template0;

On peut alors lancer les scripts de déploiement de Redmine :

# su - redmine
$ cd ~/current/
$ bundle install --without development test mysql mysql2 sqlite3
$ rake generate_secret_token
$ RAILS_ENV=production rake db:migrate
$ RAILS_ENV=production rake redmine:load_default_data
$ mkdir public/plugin_assets
$ cd /opt/redmine/current
$ find . -type d -exec chmod 755 {} \;
$ find . -type f -exec chmod 644 {} \;
$ chmod 755 script/about \
            script/rails \
            public/dispatch.fcgi.example \
            extra/svn/reposman.rb \
            extra/mail_handler/rdm-mailhandler.rb \
            lib/plugins/rfpdf/test/test_helper.rb \
            lib/plugins/rfpdf/lib/fpdf/makefont.rb
$ find . -name delete.me -exec rm -vf {} \;

De nombreux tutoriels invitent à attribuer au compte « www-data » sous lequel s'exécute le serveur Apache divers répertoires de la forge (files, log, tmp, public/plugin_assets). Il ne faut pas le faire lorsque Passenger et donc Redmine s'exécutent sous l'identité redmine.

Avant de configurer Apache et de rendre l'application publique, il faut la tester localement et en profiter pour modifier le mot de passe par défaut de l'administrateur (admin / admin). Pour ce faire, on lance Redmine via le serveur WEBrick fourni avec Ruby on Rails :

# ruby script/rails server webrick -e production

On peut alors se connecter à la forge via l'url locale http://localhost:3000 pour modifier le mot de passe par défaut du compte admin:

# elinks http://localhost:3000/

Si l'on souhaite que l'adresse électronique des utilisateurs soit masquée par défaut, il faut modifier dans la base de données la valeur par défaut de la colonne correspondante :

# su - postgres
$ psql redmine
redmine=# ALTER TABLE user_preferences ALTER COLUMN hide_mail SET DEFAULT true;

Configuration de l'hôte virtuel dédié dans Apache

Avant de configurer Apache, on prépare le terrain :

  • On crée dans la table DNS une entrée dédiée à la forge (forge.dinot.net).
  • Afin que le navigateur n'affiche pas de message alarmant (« certificat auto-signé » ou « autorité de certification inconnue ») lorsqu'on accède à la forge en HTTPS, il convient d'utiliser un certificat généré par une autorité ayant pignon sur rue. Pour ma part, j'utilise les services imparfaits mais gratuits de StartSSL dont l'autorité de certification est connue de tous les navigateurs que j'ai essayés. Gandi propose un service d'une qualité sensiblement meilleure à un prix raisonnable (12 euros HT par an et par certificat) mais je n'ai pas recours à Gandi à titre privé car j'ai besoin d'une douzaine de certificats SSL pour mes serveurs et ce nombre représente in fine une dépense annuelle non négligeable.

Ceci étant fait et le certificat SSL étant installé, on peut déclarer l'hôte virtuel dans la configuration d'Apache. Voici le contenu du fichier /etc/apache2/sites-available/forge.dinot.net.conf :

<VirtualHost *:80>
 
    ServerName  forge.dinot.net
    ServerAlias forge
    ServerAdmin webmaster@dinot.net
    ServerSignature Off
 
    LogLevel warn
    CustomLog /var/log/apache2/forge.dinot.net.access.log combined
    ErrorLog /var/log/apache2/forge.dinot.net.error.log
 
    # HTTP => HTTPS
    RewriteEngine on
    RewriteRule ^/(.*)$  https://%{HTTP_HOST}/$1 [L,R=301,NE]
 
</VirtualHost>
 
<VirtualHost *:443>
 
    ServerName  forge.dinot.net
    ServerAlias forge
    ServerAdmin webmaster@dinot.net
    ServerSignature Off
 
    DocumentRoot /opt/redmine/current/public
 
    # Forge configuration
    RailsEnv production
    RailsBaseURI /
    RailsSpawnMethod smart
    PassengerPoolIdleTime 900
    RailsAppSpawnerIdleTime 1800
 
    PassengerUserSwitching on
    PassengerUser  redmine
    PassengerGroup redmine
    PassengerPreStart https://forge.dinot.net/
 
    <Directory />
        Options FollowSymLinks
        AllowOverride None
    </Directory>
 
    <Directory /opt/redmine/current/public>
        AddHandler fcgid-script .fcgi
        AllowOverride All
        Allow from all
        Require all granted
        Options -MultiViews +SymLinksIfOwnerMatch +ExecCGI
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ dispatch.fcgi
    </Directory>
 
    LogLevel warn
    CustomLog /var/log/apache2/forge.dinot.net.access_ssl.log combined
    ErrorLog /var/log/apache2/forge.dinot.net.error_ssl.log
 
    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/forge.dinot.net.20131029.crt
    SSLCertificateKeyFile /etc/ssl/private/forge.dinot.net.20131029.key
 
</VirtualHost>

On active alors ce site et on relance le serveur HTTP Apache afin qu'il le prenne en compte :

# a2ensite forge.dinot.net.conf
# service apache2 restart

La forge est désormais accessible via l'adresse http://forge.dinot.net/ ; il faut la configurer. Une fois connecté en tant qu'administrateur, il faut se rendre dans le module d'administration de Redmine et modifier les paramètres suivants :

  • Onglet « General » :
    • Application title : Zebulon forge
    • Maximum attachment size : 20480 kB (la valeur par défaut s'avère inadaptée pour moi ~5 Mo)
    • Host name and path : forge.dinot.net
    • Protocol : HTTPS
    • Text formatting : Markdown
  • Onglet « Email notifications » :
  • Onglet « Repositories » :
    • N'activer que Git (enfin, ceci est un choix personnel)

NB : Les logs relatifs à la forge sont distribués comme suit :

  • /opt/redmine/current/log/ pour ce qui relève de l'application Redmine elle-même
  • /var/log/apache2/ pour ce qui relève du serveur Apache
  • /opt/git/.gitolite/logs/ pour ce qui relève de Gitolite

Sur Debian, Gitolite est déployé dans l'espace de travail d'un compte nommé git. Si un tel compte préexiste sur le système, le script de DebConf le supprime et en crée un nouveau, associé à un uid différent. Il faut donc installer Gitolite sans créer ce compte git au préalable et, s'il existe pour un besoin antérieur, il faut sauvegarder au préalable les données et paramètres de ce compte afin de ne rien perdre et de voir éventuellement comment fusionner ou au contraire dissocier les comptes par la suite.

L'installation du paquet Debian ne configure pas Gitolite ; il faut explicitement déclencher cette configuration dans un second temps :

# aptitude install gitolite
# dpkg-reconfigure gitolite
- System username for gitolite: git
- Repository path: /opt/gitolite
- Administrator's SSH key: <copier la clé publique - la clé elle-même et non son chemin d'accès -
                            générée pour le compte redmine>

Cette configuration aboutit à la création des fichiers et répertoires suivants :

  • /opt/gitolite : le répertoire d'accueil du compte git
  • /opt/gitolite/.gitolite.rc : fichier de configuration de Gitolite
  • /opt/gitolite/.gitolite : répertoire contenant les informations de gestion des référentiels et des accès. Ces informations sont elles-mêmes gérées via un référentiel Git situé dans le répertoire /opt/gitolite/repositories/gitolite-admin.git. Pour modifier ces données, il faut cloner, puis modifier, puis pousser le référentiel et non modifier directement le contenu du répertoire /opt/gitolite/.gitolite.
  • /opt/gitolite/.ssh : répertoire de configuration de SSH dans lequel Gitolite va notamment gérer les accès via les informations stockées dans le fichier authorized_keys.
  • /opt/gitolite/repositories : répertoire d'accueil des référentiels Git. Il contient initialement le dépôt de configuration de Gitolite (gitolite-admin.git) ainsi qu'un dépôt de test (testing.git) qui peut être supprimé.
  • /opt/gitolite/projects.list : Liste des projets consultables en ligne au travers de Gitweb.

Il faut ensuite modifier la configuration statique de Gitolite (fichier ~/.gitolite.rc du compte git) pour qu'il accepte tous les hooks (scripts lancés sur événement pré-établi) :

[...]
$GL_GITCONFIG_KEYS = ".*";
[...]

Le paramètre GL_GITCONFIG_KEYS change de nom dans la version 3 de Gitolite et devient GIT_CONFIG_KEYS. Ceci étant, une autre modification intervenue dans cette version fait qu'il faut patcher la version 3 de Gitolite pour qu'elle fonctionne avec le greffon Redmine Git Hosting. Cf. la documentation du greffon à ce sujet. J'ai donc décidé de me contenter de la version 2 de Gitolite.

Afin d'éviter un blocage ultérieur pour cause d'hôte inconnu, on s'assure du référencement de l'hôte dans le fichier ~/.ssh/known_hosts via l'exécution une banale commande SSH telle que :

# su - redmine
$ ssh forge.dinot.net info
The authenticity of host 'forge.dinot.net (82.229.230.122)' can't be established.
ECDSA key fingerprint is 83:8a:e8:44:fe:7b:b6:5d:8b:45:d0:aa:66:e0:97:aa.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'forge.palabritudes.net,82.229.230.122' (ECDSA) to the list of known hosts.
hello admin, this is gitolite 2.3-1 (Debian) running on git 2.0.0.rc2
the gitolite config gives you the following access:
     R   W   gitolite-admin
    @R_ @W_	testing

Si le fichier de configuration du serveur SSH (/etc/ssh/sshd_config) contient un paramètre AllowUsers listant les seuls comptes autorisés à se connecter, il faut ajouter à cette liste le compte git :

AllowUsers sebastien git

Et il relance le serveur SSH pour qu'il prenne en compte cette modification :

# service ssh restart

On note qu'initialement, le compte d'administration n'a accès qu'au projet gitolite-admin. Il faut y remédier car le greffon Redmine Git Hosting va avoir besoin d'un accès complet à tous les référentiels. On clone donc le référentiel d'administration de Gitolite pour modifier son fichier de configuration :

$ git config --global user.name "Redmine administrator"
$ git config --global user.email redmine@dinot.net
$ mkdir -p ~/admin
$ cd ~/admin
$ git clone ssh://git@forge.dinot.net/gitolite-admin
$ cd gitolite-admin/conf
$ editor gitolite.conf

Les lignes :

repo    gitolite-admin
        RW+     =   admin

Deviennent :

repo    @all
        RW+     =   admin

Puis on pousse la modification :

$ git add gitolite.conf
$ git commit -m "All rights granted to Gitolite admin on all repositories"
$ git push origin master

Déploiement de Ruby via RVM

On procède comme pour le compte redmine (cf. début d'article) mais sous l'identité git. Notez cependant que le premier déploiement de RVM effectué dans l'espace de travail du compte redmine a provoqué l'installation de tous les outils et paquets de développement Debian requis. Il est donc superflu de permettre au compte git d'agir sous l'identité root. On se retient donc de le faire. :)

Par contre, le compte git va avoir besoin du paquet Ruby redcarpet. On l'installe donc :

# su - git
$ gem install redcarpet

Les comptes git et redmine doivent chacun pouvoir exécuter des commandes sous l'identité de l'autre sans avoir à s'identifier. On ajoute donc les lignes suivantes au fichier /etc/sudoers :

redmine    ALL=(git)      NOPASSWD:ALL
git        ALL=(redmine)  NOPASSWD:ALL

Ce greffon est bien évidemment déployé sous l'identité redmine et dans l'espace de travail de ce compte :

# su - redmine
$ cd ~/current/plugins/
$ git clone https://github.com/ogom/redmine_sidekiq.git
$ git clone https://github.com/jbox-web/redmine_bootstrap_kit.git
$ git clone https://github.com/jbox-web/redmine_git_hosting.git
$ cd redmine_git_hosting/
$ git checkout 0.7.2
    Note: checking out '0.7.2'.
 
    You are in 'detached HEAD' state. You can look around, make experimental
    changes and commit them, and you can discard any commits you make in this
    state without impacting any branches by performing another checkout.
 
    If you want to create a new branch to retain commits you create, you may
    do so (now or later) by using -b with the checkout command again. Example:
 
      git checkout -b new_branch_name
 
    HEAD is now at bec62e3... Bump to version 0.7.2
$ git checkout -b 0.7.2.local
    Switched to a new branch '0.7.2.local'
$ cd ~/current
$ bundle install

L'installation peut échouer à cause de paquets de développement non installés. Par exemple, sur ma machine, j'ai du lancer la commande suivante suite à l'échec d'une première tentative :

# aptitude install libicu-dev

Le cas échéant, les paquets de développement étant installés, il suffit de relancer la commande :

$ bundle install

Puis on prend en compte les modifications requises :

$ RAILS_ENV=production rake redmine:plugins:migrate

On redémarre alors le serveur Apache :

# service apache2 restart

Configuration finale et OBLIGATOIRE du greffon avant utilisation :

[Menu principal « Administration », menu secondaire « Redmine Git Hosting »]

  • Onglet « SSH » :
    • Gitolite SSH private key : /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa
    • Gitolite SSH public key : /opt/redmine/.ssh/redmine_gitolite_admin_id_rsa.pub
  • Onglet « Global » :
    • Temporary dir for lock file and data : /opt/redmine/current/tmp/redmine_git_hosting/
    • Scripts dir : ./
    • Git author name : Redmine administrator
    • Git author email : redmine@dinot.net
  • Onglet « Access » :
    • SSH server domain : forge.dinot.net ⇐ Important
    • HTTP server domain : forge.dinot.net ⇐ Important
    • HTTPS server domain : forge.dinot.net ⇐ Important
    • Subdirectory for HTTP access : git
    • Enable Smart HTTP mode for new repositories by default? : HTTPS Only
    • Show checkout url's ? : true
  • Onglet « Notifications » :
    • Git mailing list global sender address : redmine@dinot.net
  • Onglet « Config Test » :
    • Vérifier dans cet onglet que tous les paramètres sont valides (ils sont alors indiqués sur fond vert).

Si je n'ai rien oublié, la forge est prête à fonctionner. :)