Lightweight groupware solution using nginx+php-fpm+mysql+baïkal+roundcube+agendav

The goal of this document is to set up a web-based solution providing mail, calendars and contacts management, along with mobile devices & desktop synchronization using dav* protocols.

This obviously needs an already running full mail stack (I use qmail/postfix/dovecot), which will not be covered there. This document also assume that nginx, fpm and mysql are also already up and running.

nginx

 server {

        # put listen directives & tls stuff here #

        server_name sub.domain.tld;

        access_log /var/log/nginx/groupware-access.log;
        error_log /var/log/nginx/groupware-error.log;

        location / {
                index index.php;
                root /path/to/roundcube;

                dav_ext_methods PROPFIND OPTIONS;
                
                # support for csrf token
                rewrite "^/[a-zA-Z0-9]{16}/(.*)" /$1 break;
                
                # good ol' favicon (adjust path if you use Larry skin)
                rewrite ^/favicon.ico /skins/classic/images/favicon.ico last;

                # caldav/carddav stuff
                rewrite ^/addressbooks/(.*) /dav/card.php/addressbooks/$1 redirect;
                rewrite ^/calendars/(.*) /dav/cal.php/calendars/$1 redirect;
                rewrite ^/.well-known/carddav /dav/dav.php redirect;
                rewrite ^/.well-known/caldav /dav/dav.php redirect;


                # maximum upload size for mail attachments
                client_max_body_size 30M;

                location ~ .php {
                        include fastcgi_params;
                        fastcgi_param HTTPS on;
                        fastcgi_split_path_info (.+.php)(/.*)$;
                        fastcgi_pass unix:/path/to/php-fpm.sock;
                        fastcgi_param SCRIPT_FILENAME $request_filename;
                }
        }

        location /dav {
                root /path/to/baikal;
                index index.php;

                dav_methods     PUT DELETE MKCOL COPY MOVE;
                dav_ext_methods PROPFIND OPTIONS;

                charset utf-8;

                location ~ /(\.ht|Core|Specific) {
                        deny all;
                        return 404;
                }

                location ~ ^(.+\.php)(.*)$ {
                        try_files $fastcgi_script_name =404;
                        include fastcgi_params;
                        fastcgi_split_path_info  ^(.+\.php)(.*)$;
                        fastcgi_pass   unix:/path/to/php-fpm.sock;
                        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                        fastcgi_param  PATH_INFO        $fastcgi_path_info;
                }

        }
}

Baïkal

curl -L -O https://github.com/fruux/Baikal/releases/download/0.4.6/baikal-0.4.6.zip
unzip baikal-0.4.6.zip && rm -f baikal-0.4.6.zip
ln -s baikal-0.4.6 baikal

Roundcube

  • First, you need to create a database. Here we created a mysql database named roundcubemail, hosted on “dbhost” and owned by user roundcubemail.
  • Install roundecube:

curl -L -O https://github.com/roundcube/roundcubemail/releases/download/1.3.0/roundcubemail-1.3.0-complete.tar.gz
tar xvf roundcubemail-1.3.0-complete.tar.gz && rm -f roundcubemail-1.3.0-complete.tar.gz
ln -s roundcubemail-1.3.0 roundcube
cd roundcube
mysql -h dbhost -u roundcubemail -p roundcubemail < SQL/mysql.initial.sql

AgenDAV plugin

  • First, you need to create a database. Here we created a mysql database named agendav, hosted on “dbhost” and owned by user agendav.
  • Install AgenDAV plugin:

cd roundcube/plugins
git clone https://github.com/stephanblanke/roundcube-agendav.git agendav
curl -L -O https://github.com/adobo/agendav/archive/1.2.6.2.tar.gz
tar xvf 1.2.6.2.tar.gz && rm 1.2.6.2.tar.gz
cp agendav-1.2.6.2.css agendav-1.2.6.2/web/public/css/agendav-1.2.6.2.css
cd agendav-1.2.6.2
mysql -h dbhost -u agendav -p agendav < sql/mysql.schema.sql

  • Adjust var in plugins/agendav/agendav-1.2.6.2/web/config/config.php
  • Adjust caldav parameters in plugins/agendav/agendav-1.2.6.2/web/config/caldav.php:

$config['caldav_http_auth_method'] = CURLAUTH_BASIC;
$config['caldav_principal_url'] = 'https://sub.domain.tld/dav/cal.php/%u/default';
$config['caldav_calendar_url'] = 'https://sub.domain.tld/.well-known/caldav';
$config['public_caldav_url'] = 'https://sub.domain.tld/dav/cal.php/%s/';

  • Adjust vars in plugins/agendav/agendav-1.2.6.2/web/config/database.php:

$db['default']['hostname'] = 'dbhost';
$db['default']['username'] = 'agendav';
$db['default']['password'] = 'changeme';
$db['default']['database'] = 'agendav';
$db['default']['dbdriver'] = 'mysql';

  • For french translation, create plugins/agendav/localization/fr_FR.inc:

<?php
 
/*
 +-----------------------------------------------------------------------+
 | plugins/help/localization/<lang>.inc                                  |
 |                                                                       |
 | Localization file of the Roundcube Webmail Help plugin                |
 | Copyright (C) 2012-2013, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 +-----------------------------------------------------------------------+
 
 For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-help/
*/
 
$labels = array();
$labels['agendav'] = 'Calendrier';
$labels['prefs'] = 'Préférences';
 
?>

  • If using classic skin, replace the strange “interrogation mark” button by a prettier one:

mv plugins/agendav/skins/classic/agendav.gif plugins/agendav/skins/classic/agendav.old.gif 
curl -o plugins/agendav/skins/classic/agendav.gif https://git.kolab.org/file/data/kamnlpzz2fmtbgygywso/PHID-FILE-2ozl7pm2enflqcndiowh/calendar.gif

PHP7

If using php7, you need to patch agendav 1.2.6.2
  • plugins/agendav/agendav-1.2.6.2/web/system/database/drivers/mysql/mysql_driver.php

--- agendav-1.2.6.2.old/web/system/database/drivers/mysql/mysql_driver.php  2012-10-15 09:54:01.000000000 +0200
+++ agendav-1.2.6.2/web/system/database/drivers/mysql/mysql_driver.php      2017-07-09 16:18:32.673770000 +0200
@@ -88,7 +88,7 @@
                        $this->hostname .= ':'.$this->port;
                }
 
-               return @mysql_pconnect($this->hostname, $this->username, $this->password);
+               return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database);
        }
 
        // --------------------------------------------------------------------
@@ -120,7 +120,8 @@
         */
        function db_select()
        {
-               return @mysql_select_db($this->database, $this->conn_id);
+               //return @mysqli_select_db($this->database, $this->conn_id);
+               return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database);
        }
 
        // --------------------------------------------------------------------
@@ -138,16 +139,16 @@
                if ( ! isset($this->use_set_names))
                {
                        // mysql_set_charset() requires PHP >= 5.2.3 and MySQL >= 5.0.7, use SET NAMES as fallback
-                       $this->use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysql_get_server_info(), '5.0.7', '>=')) ? FALSE : TRUE;
+                       $this->use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysqli_get_server_info($this->conn_id), '5.0.7', '>=')) ? FALSE : TRUE;
                }
 
                if ($this->use_set_names === TRUE)
                {
-                       return @mysql_query("SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'", $this->conn_id);
+                       return @mysqli_query($this->conn_id, "SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'");
                }
                else
                {
-                       return @mysql_set_charset($charset, $this->conn_id);
+                       return @mysqli_set_charset($charset, $this->conn_id);
                }
        }
 
@@ -176,7 +177,7 @@
        function _execute($sql)
        {
                $sql = $this->_prep_query($sql);
-               return @mysql_query($sql, $this->conn_id);
+               return @mysqli_query($this->conn_id, $sql);
        }
 
        // --------------------------------------------------------------------
@@ -342,7 +343,7 @@
         */
        function affected_rows()
        {
-               return @mysql_affected_rows($this->conn_id);
+               return @mysqli_affected_rows($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -355,7 +356,7 @@
         */
        function insert_id()
        {
-               return @mysql_insert_id($this->conn_id);
+               return @mysqli_insert_id($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -454,7 +455,7 @@
         */
        function _error_message()
        {
-               return mysql_error($this->conn_id);
+               return mysqli_error($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -467,7 +468,7 @@
         */
        function _error_number()
        {
-               return mysql_errno($this->conn_id);
+               return mysqli_errno($this->conn_id);
        }
+               return @mysqli_connect($this->hostname, $this->username, $this->password, $this->database);
        }
 
        // --------------------------------------------------------------------
@@ -138,16 +139,16 @@
                if ( ! isset($this->use_set_names))
                {
                        // mysql_set_charset() requires PHP >= 5.2.3 and MySQL >= 5.0.7, use SET NAMES as fallback
-                       $this->use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysql_get_server_info(), '5.0.7', '>=')) ? FALSE : TRUE;
+                       $this->use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysqli_get_server_info($this->conn_id), '5.0.7', '>=')) ? FALSE : TRUE;
                }
 
                if ($this->use_set_names === TRUE)
                {
-                       return @mysql_query("SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'", $this->conn_id);
+                       return @mysqli_query($this->conn_id, "SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'");
                }
                else
                {
-                       return @mysql_set_charset($charset, $this->conn_id);
+                       return @mysqli_set_charset($charset, $this->conn_id);
                }
        }
 
@@ -176,7 +177,7 @@
        function _execute($sql)
        {
                $sql = $this->_prep_query($sql);
-               return @mysql_query($sql, $this->conn_id);
+               return @mysqli_query($this->conn_id, $sql);
        }
 
        // --------------------------------------------------------------------
@@ -342,7 +343,7 @@
         */
        function affected_rows()
        {
-               return @mysql_affected_rows($this->conn_id);
+               return @mysqli_affected_rows($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -355,7 +356,7 @@
         */
        function insert_id()
        {
-               return @mysql_insert_id($this->conn_id);
+               return @mysqli_insert_id($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -454,7 +455,7 @@
         */
        function _error_message()
        {
-               return mysql_error($this->conn_id);
+               return mysqli_error($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -467,7 +468,7 @@
         */
        function _error_number()
        {
-               return mysql_errno($this->conn_id);
+               return mysqli_errno($this->conn_id);
        }
 
        // --------------------------------------------------------------------
@@ -769,11 +770,11 @@
         */
        function _close($conn_id)
        {
-               @mysql_close($conn_id);
+               @mysqli_close($conn_id);
        }
 
 }
 
 
 /* End of file mysql_driver.php */
-/* Location: ./system/database/drivers/mysql/mysql_driver.php */
\ No newline at end of file
+/* Location: ./system/database/drivers/mysql/mysql_driver.php */

  • plugins/agendav/agendav-1.2.6.2/web/system/database/drivers/mysql/mysql_result.php

--- agendav-1.2.6.2.old/web/system/database/drivers/mysql/mysql_result.php  2012-10-15 09:54:01.000000000 +0200
+++ agendav-1.2.6.2/web/system/database/drivers/mysql/mysql_result.php      2017-07-09 18:06:06.572461000 +0200
@@ -34,7 +34,7 @@
         */
        function num_rows()
        {
-               return @mysql_num_rows($this->result_id);
+               return @mysqli_num_rows($this->result_id);
        }
 
        // --------------------------------------------------------------------
@@ -134,7 +134,7 @@
         */
        function _data_seek($n = 0)
        {
-               return mysql_data_seek($this->result_id, $n);
+               return mysqli_data_seek($this->result_id, $n);
        }
 
        // --------------------------------------------------------------------
@@ -149,7 +149,7 @@
         */
        function _fetch_assoc()
        {
-               return mysql_fetch_assoc($this->result_id);
+               return mysqli_fetch_assoc($this->result_id);
        }
 
        // --------------------------------------------------------------------
@@ -164,11 +164,11 @@
         */
        function _fetch_object()
        {
-               return mysql_fetch_object($this->result_id);
+               return mysqli_fetch_object($this->result_id);
        }
 
 }
 
 
 /* End of file mysql_result.php */
-/* Location: ./system/database/drivers/mysql/mysql_result.php */
\ No newline at end of file
+/* Location: ./system/database/drivers/mysql/mysql_result.php */

Carddav plugin

cd roundcube
curl -s http://getcomposer.org/installer | php
php composer.phar require roundcube/carddav

Mobile plugin

At the time of writing (2017-07-09), this plugin is incompatible with Roundcube 1.3.0.

cd roundcube
git clone https://github.com/messagerie-melanie2/Roundcube-Skin-Melanie2-Larry-Mobile.git skins/melanie2_larry_mobile
php composer.phar require melanie2/mobile:dev-master