Radicale

Author: akeil
Date: 2013-10-20
Version: 1

radicale [1] is an open source server for calendars and contacts written in python.

Although radicale can run as a standalone solution, it is recommended to run it inside a HTTP server (especially if is made available through the internet).

This is a description of a radicale setup on Arch Linux using nginx [2] as a HTTP server and gunicorn [3] as a python application server.

When complete, we can run our own server to store calendar and contacts data and make them available via CalDAV and CardDAV. Access to the calendars and address books is protected by passwords and the calendar/contact data is backed up regularly.

Set up nginx

Install nginx

On Arch Linux nginx can be installed through the package manager:

# pacman -Sy nginx

To make it start on boot:

# systemctl enable nginx

More information can be found in the Arch Wiki for nginx [6].

Configure nginx

Configuration files are found in /etc/nginx/. The main configuration is /etc/nginx/nginx.conf.

To reload configuration changes while nginx is running:

# nginx -s reload

To use nginx as a proxy for gunicorn, use the following config:

http {
    upstream radicale_app {
        server 127.0.0.1:8000 fail_timeout=0;
    }

    server {
        listen 5232;
    }

    location @proxy_to_app {
        proxy_pass http://radicale_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

This assumes that gunicorn listens on its default port 8000. nginx will listen to requests on port 5232 and forward them to the gunicorn instance listening on localhost:8000.

Logging

The log files will be stored in /var/log/nginx which contains access.log and error.log:

/var/log/nginx/access.log
/var/log/nginx/error.log

gunicorn and radicale

Install gunicorn and radicale

virtualenv [4] is used to keep the gunicorn and radicale installation separate from the system-wide python installation. If virtualenv is not installed already, it should be available through the package manager.

Now create a virtualenv and install gunicorn and radicale inside it.

# cd /opt/venvs
# virtualenv radicale
# source radicale/bin/activate
# (radicale) pip install gunicorn radicale

This assumes that you would like to keep your virtualenv for radicale under /opt/venvs/radicale. Any other location will work as well.

Note

When installed with pip inside a virtualenv, the application is not updated through the package manager. To upgrade manually, type:

# /opt/venvs/radicale/bin/pip install --upgrade gunicorn
# /opt/venvs/radicale/bin/pip install --upgrade radicale

Create the WSGI-app for gunicorn:

# cd radicale
# touch radicale_app.py

and edit as follows:

# radicale_app.py
#-----------------------------------------------------
#-*- coding: utf-8 -*-
import radicale
radicale.log.start()
application = radicale.Application()

To manually start gunicorn with the radicale app type (inside the virtualenv):

# (radicale) bin/gunicorn -w 4 radicale_app

or (outside the virtualenv):

# /opt/venvs/radicale/bin/gunicorn -w 4 --chdir /opt/venvs/radicale radicale_app

This will start gunicorn listening on localhost:8000 and using 4 workers.

To test the setup type:

$ curl localhost:8000

You should see something like this:

<!DOCTYPE html>
<title>Radicale</title>Radicale Works!

User and Group

In order to control permissions for the radicale-app, create a user and group for radicale:

# groupadd radicale
# useradd -g=radicale --shell=/bin/false --home-dir / radicale

Service File

To control the radicale application with systemd create a .service file for it:

# /usr/lib/systemd/system/radicale.service
#----------------------------------------------
[Unit]
Description=radicale daemon

[Service]
User=radicale
WorkingDirectory=/opt/venvs/radicale
ExecStart=/opt/venvs/radicale/bin/gunicorn -w 2 radicale_app

[Install]
WantedBy=multi-user.target

Start the radicale service on boot:

# systemctl enable radicale

Configure radicale

Radicale will look for configuration files in /etc/radicale/. After installation, the configuration files does not exist and default settings are used. To adjust radicale's config, create the configuration file:

# mkdir /etc/radicale
# touch /etc/radicale/config
# nano /etc/radicale/config

Edit:

# /etc/radicale/config
#-----------------------------------------------------
[storage]
type = filesystem
filesystem_folder = /var/lib/radicale

[logging]
config = /etc/radicale/logging

[rights]
type = from_file
file = /etc/radicale/rights

This will store calendars and contacts in the file system in the directory var/lib/radicale/. Logging behavior and user permissions are configured in a separate files.

To configure logging, create /etc/radicale/logging which might look like this:

radicale.logging (Source)

# logging config for radicale


# Keys for loggers, handlers and formatters --------------

[loggers]
keys = root

[handlers]
keys = console, file

[formatters]
keys = simple, full


# Loggers and Handlers------------------------------------

[logger_root]
level = DEBUG
handlers = console, file

[handler_console]
class = StreamHandler
level = INFO
args = (sys.stdout, )
formatter = simple

[handler_file]
class = FileHandler
level = DEBUG
args = ('/var/log/radicale.log', )
formatter = full


# Formatters ---------------------------------------------

[formatter_simple]
format = %(message)s

[formatter_full]
format = %(asctime)s - %(levelname)s: %(message)s

Change the permissions on the files required by radicale:

# chown -R radicale:radicale /var/lib/radicale /var/log/radicale.log

See below for a authentication and permissions.

Backup

Assuming the following radicale configuration:

[storage]
type = filesystem
filesystem_folder = /var/lib/radicale

The backup is simply based on the file system using rdiff [5]. To install rdiff:

# pacman -Sy rdiff-backup

Create a simple cron-job to perform the backup:

#!/bin/sh
# /etc/cron.daily/radicale-backup
# ----------------------------------------------------
src="/var/lib/radicale"
dst="/mnt/storage-1/backup/radicale"
/usr/bin/rdiff-backup -v 0 $src $dst

Authentication

Although radicale supports its own authentication facility, it is recommended to use the web server (nginx) for this.

Using nginx basic auth [7] module, add the following to the nginx configuration:

# /etc/nginx/nginx.conf
#-------------------------------------------------------------------------
...
    location @proxy_to_app {
        auth_basic radicale;
        auth_basic_user_file htpasswd;
        ...
    }
...

The location of the user file is relative to the config file itself. That means it is expected at /etc/nginx/htpasswd.

The nginx error-log will contain an entry if the user file cannot be found.

Create the userfile and make it writable for root only, but readable for nginx (assuming that nginx is run with the default http user):

# cd /etc/nginx
# touch htpasswd
# chown root:http htpasswd
# chmod 640 htpasswd

The password file looks like this:

/etc/nginx/htpasswd
------------------------------------------------------
# comment
username:encrypted-password

To encrypt passwords, use openssl passwd command:

$ openssl passwd "the password"

Will output the encrypted password.

Given a user name and a password, this will create the necessary entry in the passwd file:

usergen.sh
------------------------------------------------------
username=$1
passwd=`openssl passwd $2`
echo $username:$passwd >> /etc/nginx/htpasswd

Use like this (supply the unencrypted plain-text password):

$ usergen.sh username password

SSL

To use an encrypted connection, a SSL-certificate is required. Normally, these are issued by (and bought from) a Certificate Authority which verifies the identity of the server. Since certificates are expensive, we will use a self-signed certificate. With a self-signed certificate the encryption itself is just as secure but there is no way for the client to verify the identity of the server. Still, good enough for non-professional use.

The certificate consists of two parts
  • the certificate that is sent to the client
  • the key which is kept private on the server side.

openssl generates them like this:

# cd /etc/nginx
# mkdir ssl
# cd ssl
# openssl req -days 365 -new -x509 -nodes -out server.cert -keyout server.key
# chown http server.key
# chmod 0600 server.key

openssl will ask some questions to populate the certificates fields and create two files in the current directory (/etc/nginx/ssl). Make sure that the keyfile is only readable by nginx.

The -days 365 parameter means that the certificate will be valid for 365 days.

Now tell nginx to use SSL with this certificate. Add the following to nginx.conf:

# /etc/nginx/nginx.conf
#-----------------------------------------------------
server{
    listen              5232;

    ssl                 on;
    ssl_certificate     ssl/server.cert;
    ssl_certificate_key ssl/server.key;
    ...
}

In this configuration, the service is only available through an encrypted connection. This may of course cause difficulties for clients that do not accept the self-signed certificate.

Permissions

While authentication is handled by the web server, permissions are managed within the application. radicale allows to set read and write permissions based on the authenticated user (the owner). The owner is derived from a collection's URL. If the URL is http://example.com/joe/calendar.ics, the collection "calendar.ics" belongs to the user "joe".

Therefore, we will allow (via nginx) access to the radicale-URLs to all authenticated users and rely on radicale to control access to the various calendars and contacts.

In the radicale config the [rights] section points to a file which controls permissions:

# /etc/radicale/config
#------------------------------------------------------
[rights]
type = from_file
file = /etc/radicale/rights

By default (that is: without creating the rights-file) read and write access is granted to the owner. Different permissions can be specified in the rights file.

For example:

# /etc/radicale/rights
#------------------------------------------------------
[joe/calendar.ics]
mary = r

Grants the user "mary" read-permission on "joe"s calendar.

Note

When access is denied for a specific user by radicale that user may receive an authentication prompt repeatedly, even if correct credentials are provided.

nginx will authenticate the user "OK" but radicale will deny access to the requested calendar and ask for authentication.

Public Calendars

Public calendars can be used to provide clients with public holidays or other shared events in a read-only way. You can do this with radicale if you have these calenders in CalDAV format.

To do this, we will create a radicale-user named everyone and copy the public calendars to this user:

# cd /var/lib/radicale
# mkdir everyone
# cp /your/public/calendars/*.ics everyone
# chown -R radicale:radicale everyone

Remember to grant read/write permissions to the radicale user/group.

Then, grant read permissions to the calendars that belong to everyone in radicale-rights:

# /etc/radicale/rights
#------------------------------------------------------
[everyone]
user1 = r

[everyone/holidays.ics]
user1 = r

Note

In radicale version 0.8, permissions are granted on a per user basis. There is no way(?) to grant permissions to all users with one directive.

Clients

Evolution

Evolution [8] is a mail-client for Linux which supports CalDAV and CardDAV as a backend.

If a self-signed SSL certificate is used, the "use a secure connection" and the "ignore invalid SSL-certificate" options must be used.

Thunderbird

Thunderbird also works well with CalDAV and CardDAV.