Gestion d'agendas avec SabreDAV, PHP-FPM et Nginx

Jean Baptiste FAVRE

Décembre 2012

English version is available here: Online calendars with SabreDAV, PHP-FPM and Nginx.

Introduction

En bon adepte de l'auto-hébergement, je gère mon propre serveur web. Parmi les services que l'on souhaite pouvoir auto-héberger, les agendas arrivent en bonne place.

J'ai décidé d'utiliser SabreDAV, librairie PHP qui implémente WebDAV et ses extensions CalDAV et CardDAV.

Je vais donc décrire la configuration choisie, qui permet le bon fonctionnement de l'application tout en ne sacrifiant pas la sécurité.

Préparation

Je me base, sans plus les détailler, sur mes publications antérieures sur le sujet et notamment:

En résumé, voici à quoi devra ressembler l'arborescence de notre application calendar:

tree /var/www/
calendar/
|--config
|   `-- fpm-pool.conf
|--docroot
|   `-- index.php
|-- logs
|   |-- access.log
|   |-- error.log
|   |-- php_err.log
|   `-- php_slow.log
|-- docroot
|   `-- index.php
|-- private
|   |-- data
|   `-- SabreDAV
|-- sessions
`-- tmp

Le répertoire private va accueillir les data (la base SQLite) ainsi que le code PHP de SabreDAV. Il s'agit bien entendu de mettre ces fichiers en dehors de l'arborescence web afin de renforcer la sécurité de l'ensemble.

Le répertoire docroot va, quant à lui, accueillir le seul et unique fichier PHP exposé dans l'arborescence web. Il agira comme point d'entrée, c'est-à-dire que toutes les requêtes lui seront destinées.

Enfin, le pool PHP-FPM dédié à la gestion des agendas fonctionnera dans un chroot.

Afin de renforcer la sécurité, nous allons crééer un utilisateur particulier qui sera utiliser par PHP-FPM:

Création du compte utilisateur pour l'application calendar et de l'environnement
mkdir -p /var/www/apps/calendar/{config,docroot,logs,private,sessions,tmp}
addgroup --force-badname --system --gid 10001 calendar

adduser --home /var/www/apps/calendar --shel /bin/false --no-create-home \
     --uid 10001 --gid 10001 --force-badname --disabled-password --disabled-login calendar

mkdir -p /var/www/apps/calendar/private/{data,SabreDAV}
Mise en place des permissions du système de fichier
chown -R admin: /var/www/apps/calendar && \
chmod -R u=rwX,go=rX /var/www/apps/calendar

chown calendars:admin /var/www/apps/calendar/private/data && \
chmod ug=rwX,o= /var/www/apps/calendar/private/data

chown calendars:admin /var/www/apps/calendar/sessions && \
chmod -R u=rwX,g=rX,o= /var/www/apps/calendar/sessions

chown calendars:admin /var/www/apps/calendar/tmp && \
chmod -R 7750 /var/www/apps/calendar/tmp

Une fois l'environnement préparé, on peut passer à l'installation de la solution SabreDAV.

Installation de SabreDAV

L'installation de SabreDAV se résume au téléchargement de l'archive et à sa décompression dans le répertoire /var/www/apps/calendar/private/

La mise en place du point d'entrée unique n'est guère plus compliquée:

cat /var/www/apps/calendar/docroot/index.php
<?php
// settings
date_default_timezone_set('Europe/Paris');

/* Database */
$pdo = new \PDO('sqlite:/private/data/calendars.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);

//Mapping PHP errors to exceptions
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
    throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");

// Files we need
require_once 'vendor/autoload.php';

// Backends
$authBackend = new \Sabre\DAV\Auth\Backend\PDO($pdo);
$calendarBackend = new \Sabre\CalDAV\Backend\PDO($pdo);
$principalBackend = new \Sabre\DAVACL\PrincipalBackend\PDO($pdo);

// Directory structure 
$tree = array(
    new \Sabre\CalDAV\Principal\Collection($principalBackend),
    new \Sabre\CalDAV\CalendarRootNode($principalBackend, $calendarBackend),
);

$server = new \Sabre\DAV\Server($tree);
$server->setBaseUri('/');

/* Server Plugins */
$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend,'SabreDAV');
$server->addPlugin($authPlugin);

$aclPlugin = new \Sabre\DAVACL\Plugin();
$server->addPlugin($aclPlugin);

$caldavPlugin = new \Sabre\CalDAV\Plugin();
$server->addPlugin($caldavPlugin);

// Support for html frontend
$browser = new \Sabre\DAV\Browser\Plugin();
$server->addPlugin($browser);

// And off we go!
$server->exec();

Après l'installation de SabreDAV, il faut initialiser la base SQLite:

Initialisation de la base SQLite
sqlite3 /var/www/apps/calendar/private/data/calendar.sqlite < \
        /var/www/apps/calendar/private/SabreDAV/examples/sql/sqlite.addressbooks.sql

sqlite3 /var/www/apps/calendar/private/data/calendar.sqlite < \
        /var/www/apps/calendar/private/SabreDAV/examples/sql/sqlite.calendars.sql

sqlite3 /var/www/apps/calendar/private/data/calendar.sqlite < \
        /var/www/apps/calendar/private/SabreDAV/examples/sql/sqlite.locks.sql

sqlite3 /var/www/apps/calendar/private/data/calendar.sqlite < \
        /var/www/apps/calendar/private/SabreDAV/examples/sql/sqlite.principals.sql

sqlite3 /var/www/apps/calendar/private/data/calendar.sqlite < \
        /var/www/apps/calendar/private/SabreDAV/examples/sql/sqlite.users.sql

Cela fait, on peut maintenant passer à PHP-FPM.

Configuration de PHP-FPM

cat /var/www/apps/calendar/config/fpm-pool.conf

[calendars]
    listen                 = /var/www/php_socks/$pool.sock
    listen.backlog         = -1
    listen.owner           = www-data
    listen.group           = www-data
    listen.mode            = 0640

    user  = $pool
    group = $pool

    pm                   = dynamic
    pm.max_requests      = 0
    pm.max_children      = 2
    pm.start_servers     = 1
    pm.min_spare_servers = 1
    pm.max_spare_servers = 2

    pm.status_path       = /php_pool_$pool_status
    ping.path            = /$pool_ping
    ping.response        = $pool_pong
 
    request_terminate_timeout = 50
    request_slowlog_timeout   = 5
    slowlog                   = /var/www/apps/$pool/logs/php_slow.log
 
    chroot = /var/www/apps/$pool/
    chdir = /

    catch_workers_output = yes
 
    env[HOSTNAME] = $HOSTNAME
    env[PATH]     = /usr/local/bin:/usr/bin:/bin
    env[TMP]      = /tmp
    env[TMPDIR]   = /tmp
    env[TEMP]     = /tmp

    php_admin_value[sendmail_path]      = /usr/sbin/sendmail -t -i -f webmaster@jbfavre.org
    php_flag[display_errors]            = off
    php_admin_value[error_reporting]    = E_ALL | E_STRICT
    php_admin_value[error_log]          = /logs/php_err.log
    php_admin_flag[log_errors]          = on
    php_admin_value[memory_limit]       = 128M
    php_value[max_execution_time]       = 5
    php_admin_value[session.save_handler] = files
    php_admin_value[session.save_path] = "/sessions"
    php_value[include_path] = ".:/private/SabreDAV"

Bien sûr, vous n'oublierez pas de redémarrer PHP-FPM avant de vous attaquer à nginx.

Configuration de Nginx

La configuration de nginx ne présente pas de surprise particulière à une exception près: PHP-FPM étant emprisonné dans un chroot, il faudra adapter la configuration de nginx afin de passer les bonnes infos à PHP.

Configuration de Nginx
server {
    # Uncomment for maintenance mode
	#return 503;
        server_name calendar.domain.tld;
        listen   [::]:80;

        ########## Log definition ##########
        access_log /var/www/apps/calendar/logs/access.log vhosts;
        error_log /var/www/apps/calendar/logs/error.log info;

        root /var/www/apps/calendar/docroot/;

        # Here's the surprise :)
        rewrite (.*) /docroot/index.php$uri break;

        location ~ ^(.+\.php)(.*)$ {
                fastcgi_connect_timeout           2;
                fastcgi_send_timeout              2;
                fastcgi_read_timeout              2;
                fastcgi_buffer_size               128k;
                fastcgi_buffers                   4 256k;
                fastcgi_split_path_info           ^(.+\.php)([^?]*).*$;
                fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
                fastcgi_param  SERVER_SOFTWARE    nginx;
                fastcgi_param  QUERY_STRING       $query_string;
                fastcgi_param  REQUEST_METHOD     $request_method;
                fastcgi_param  CONTENT_TYPE       $content_type;
                fastcgi_param  CONTENT_LENGTH     $content_length;
                fastcgi_param  SCRIPT_FILENAME    $fastcgi_script_name;
                fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
                fastcgi_param  REQUEST_URI        $request_uri;
                fastcgi_param  DOCUMENT_URI       $document_uri;
                fastcgi_param  DOCUMENT_ROOT      $document_root;
                fastcgi_param  SERVER_PROTOCOL    $server_protocol;
                fastcgi_param  SERVER_ADDR        $server_addr;
                fastcgi_param  SERVER_PORT        $server_port;
                fastcgi_param  SERVER_NAME        $server_name;
                fastcgi_param  REMOTE_ADDR        $remote_addr;
                fastcgi_param  REMOTE_PORT        $remote_port;
                fastcgi_param  PATH_INFO          $fastcgi_path_info;

                fastcgi_pass   unix:/var/www/php_socks/calendar.sock;
        }
}

Rien de bien difficile, donc.

Tests

Une fois tout ceci mis en place, il est temps de tester. Pour cela, nous avons besoin d'initialiser la base SQLite, de créer un utilisateur ainsi qu'un agenda:

Gestion des utilisateurs et agendas
echo -n 'foo:SabreDAV:foopassword' | md5sum
INSERT INTO users (username,digesta1)
       VALUES('foo', 'hash from above command');

INSERT INTO principals (uri,email,displayname)
       VALUES ('principals/foo', 'foo@domain.tld','My name is foo');

INSERT INTO principals (uri,email,displayname)
       VALUES ('principals/foo/calendar-proxy-read', null, null);

INSERT INTO principals (uri,email,displayname)
       VALUES ('principals/foo/calendar-proxy-write', null, null);
INSERT INTO calendars (principaluri, displayname, uri, description, components, ctag, transparent)
       VALUES ('principals/foo','Foo calendar','foocaluri','','VEVENT,VTODO','1', '0');
echo -n 'bar:SabreDAV:barpassword' | md5sum
INSERT INTO users (username,digesta1)
       VALUES('bar', 'hash from above command');

INSERT INTO principals (uri,email,displayname)
       VALUES ('principals/bar', 'bar@domain.tld','My name is bar');

INSERT INTO principals (uri,email,displayname)
       VALUES ('principals/bar/calendar-proxy-read', null, null);

INSERT INTO principals (uri,email,displayname)
       VALUES ('principals/bar/calendar-proxy-write', null, null);
INSERT INTO calendars (principaluri, displayname, uri, description, components, ctag, transparent)
       VALUES ('principals/bar','Bar calendar','barcaluri','','VEVENT,VTODO','1', '0');
SELECT id from principals where uri='principals/bar/calendar-proxy-read');

SELECT id from users where username='foo');
INSERT INTO groupmembers (principal_id, member_id) VALUES (principals.id from above select, users.id from above select);
SELECT id from principals where uri='principals/foo/calendar-proxy-write');

SELECT id from users where username='bar');
INSERT INTO groupmembers (principal_id, member_id) VALUES (principals.id from above select, users.id from above select);

En ouvrant l'URL //calendar.domain.tld/, vous devez voir apparaître la pop-up d'authentification. Authentifiez-vous comme un des utilisateurs et naviguez dans l'arborescence.

Conclusion

Voilà comment vous pourrez, à peu de frais, auto-héberger vos agendas.

Il ne vous reste plus qu'à paramétrer les agendas dans votre logiciel préféré. Personnellement, j'utilise:

Les 2 dernières applis gèrent la découverte des agendas.

À ma connaissance Thunderbird ne le supporte pas. Vous devrez donc lui préciser l'URL complète, par exemple //calendar.domain.tld/calendars/username/calendaruri.

Sources et références

SabreDAV

Website
  • //code.google.com/p/sabredav/
  • //code.google.com/p/sabredav/downloads/list
Getting Started
  • //code.google.com/p/sabredav/wiki/Installation
  • //code.google.com/p/sabredav/wiki/GettingStarted
CalDAV & CardDAV
  • //code.google.com/p/sabredav/wiki/CalDAV
  • //code.google.com/p/sabredav/wiki/CardDAV
Client configuration
  • //code.google.com/p/sabredav/wiki/Lightning
  • //code.google.com/p/sabredav/wiki/Evolution
  • //dmfs.org/wiki/index.php?title=Main_Page

Format

Ce document est disponible aux formats suivants:

À propos de Jean Baptiste FAVRE

Ingénieur système Linux, je travaille, entre autres, sur la virtualisation et l'amélioration des performances web. De temps en temps, j'arrive à décrocher de mon clavier pour lire un bon bouquin en écoutant de la musique.

License

Creative Commons License Cette publication est publiée sous contrat Creative Common by-nc-sa

Valid XHTML 1.0 Strict |  Valid CSS |  contrat Creative Common by-nc-sa

Table des matières

  1. Introduction
  2. Préparation
  3. Installation de SabreDAV
  4. Configuration de PHP-FPM
  5. Configuration de Nginx
  6. Tests
  7. Conclusion
  8. Format
  9. Sources et références
  10. À propos ...
  11. License