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
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
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:
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
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:
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
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.
Now is the big moment. The setup is finished, and we can enable and start the Radicale service via
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.