Setting up Radicale, a lightweight contact and calendar server

Posted on Tuesday, 22 May 2018 in IT

Recently I felt the urge to move my contact data to a place that belongs to me and not to some cloud provider.

Since I already use a Raspberry Pi as a small home server, it makes perfect sense to also run a piece of CalDAV and CardDAV software on it. I wanted a real lightweight solution and - after some research - decided to go for Radicale.

My reasons for Radicale are:

  • purpose-oriented: users, calendars and contacts - no more, no less
  • limited dependencies: no DB, no web server
  • flat-file format for data storage: easy backups and even versioning

Installing Radicale

The Radicale simple but solid setup is very well done. Following it made the installation rather easy.

On my Raspbian Stretch, I needed to install the following packages to bootstrap pip, the Python 3 package manager:

apt-get install python3-pip
apt-get install python3-pkg-resources

The rest is then installed via pip:

python3 -m pip install --upgrade radicale
python3 -m pip install --upgrade passlib bcrypt

Setting up a Linux user and group

A good practice is to create a dedicated Linux user and group for Radicale and to run the process under that user.

Let's also create the directory /var/lib/radicale/collections for storing the Radicale data.

And while we're on it, let's also create an (initially empty) configuration file for Radicale. I will use the default, global location: /etc/radicale/config.

useradd --system --home-dir / --shell /sbin/nologin radicale
mkdir -p /var/lib/radicale/collections && chown -R radicale:radicale /var/lib/radicale/collections
chmod o-x /var/lib/radicale/collections/
mkdir /etc/radicale && touch /etc/radicale/config

Autostart via systemd

This is a server, so we want Radicale to be a system service. And since Raspbian uses systemd, we need to create and enable a service descriptor file.

We can safely copy and paste it from the Radicale documentation:

[Unit]
Description=A simple CalDAV (calendar) and CardDAV (contact) server
After=network.target
Requires=network.target

[Service]
ExecStart=/usr/bin/env python3 -m radicale
Restart=on-failure
User=radicale
# Deny other users access to the calendar data
UMask=0027
# Optional security settings
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
NoNewPrivileges=true
ReadWritePaths=/var/lib/radicale/collections

[Install]
WantedBy=multi-user.target

We save it as /etc/systemd/system/radicale.service.

Defining Radicale users

We now need to define some Radicale users. Each user can then have her own set of calendars and/or contacts.

We'll use a global user-password hash file under the default location, /etc/radicale/users. Only the radicale Linux user should have permissions on that file.

touch /etc/radicale/users
chown radicale:radicale /etc/radicale/users
chmod 400 /etc/radicale/users

According to the documentation, bcrypt is the best (= most secure) option for hashing Radicale passwords, so let's go for that algorithm.

The easiest way of generating bcrypt password hashes seem to me a couple of commands on the Python 3 console:

import bcrypt
password = b"THE_PASSWORD"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
print(hashed)

The output is the hash of THE_PASSWORD, which we can then copy-and-paste the output into the right place of /etc/radicale/users. That file uses the htpasswd format, where each line has the following structure:

USER_NAME:PASSWORD_HASH

Thus, edit the users file with appropriate privileges, pick your favourite user name, and paste the output of the Python script above after the : and save the file.

Optional: tracking changes with Git

The option of tracking all changes to the Radicale data store with Git as described on the Radicale versioning page is very appealing to me. It makes backups really easy by simply cloning the repo and keeping a copy elsewhere. You can also rollback easily after a failed upgrade, etc. I want that. If you feel you don't, you can skip this section.

Let's follow the documentation and create an empty Git repo in the collections directory. We also set up the Git user metadata used for the commits to meaningful values. We define a sensible .gitignore file to exclude temporary files. Finally, we add the system user git that is used for all remote Git actions to the radicale system group, so that remote Git actions don't fail on this repo due to missing privileges.

cd /var/lib/radicale/collections
git init
chown -R radicale:radicale .git

git config --local user.email "radicale@localhost"
git config --local user.name "Radicale"

touch .gitignore
# content of .gitignore:
  .Radicale.cache
  .Radicale.lock
  .Radicale.tmp-*

chown radicale:radicale .gitignore

usermod -a -G radicale git

Note: the Radicale config file option that will actually enable the Git hook will be described later on.

Fine-tuning the Let's Encrypt permissions for TLS

I want to use https and only https for Radicale. As for the certificate, I will go for Let's Encrypt. We will skip the details of Let's Encrypt here and simply assume that we've already obtained a valid certificate. This blog post provides more details on Let's Encrypt.

The Let's Encrypt certificate directories are only visible to root. I will therefore create a dedicated letsenc system group, make the radicale user member of that group and relax permissions on those directories to allow read access for the letsencgroup.

groupadd --system letsenc
usermod -a -G letsenc radicale
cd /etc/letsencrypt/
chown -R root.letsenc archive/ live/
chmod 750 archive/ live/

Putting it all together: the Radicale config file

[server]
hosts = 0.0.0.0:RADICALE_PORT
ssl = True
certificate = /etc/letsencrypt/live/MY_DOMAIN/cert.pem
key = /etc/letsencrypt/live/MY_DOMAIN/privkey.pem

[auth]
type = htpasswd
htpasswd_encryption = bcrypt
delay = 5

[storage]
hook = git add -A && (git diff --cached --quiet || git commit -m "Changes by "%(user)s)

The [server] block defines the settings of the http server. We've enabled TLS with a Let's Encrypt certificate on a custom port.

The [auth] block contains settings related to authentication. We use the htpasswd file with the bcrypt algo for the password hashes. On failed authentications, we use a 5 second delay to impede brute force attacks.

The [storage] block contains a hook for performing a Git commit on every change to the Radicale data. If you don't care about versioning the data storage, you don't need to put it.

Done!

Now is the big moment. The setup is finished, and we can enable and start the Radicale service via systemd.

systemctl enable radicale.service
systemctl start radicale.service

If everything went well, the Radicale admin interface should be available on https://RADICALE_HOST:RADICALE_PORT. And we should be able to log in with the user credentials we've set up previously.