Hund

How to sync and manage your CalDAV-calendars and your CardDAV-addressbooks via the terminal

August 12, 2020

If you want to sync and manage both your CalDAV-calendars and your CardDAV-addressbooks via the command-line you’re in luck! There’s three pieces of software called vdirsyncer, Khal and Khard that does just that for you. It happen to be how I’ve been doing it for several years now and I think it’s time to write about it.

A screenshot of Khal
A screenshot of Khal's interactive mode.
A screenshot of an example with Khard and one made up entry.

Setting it all up is pretty easy and straightforward, as long as you know what to do. You can either read the documentation or just be happily lazy and copy my pretty generic setup that I use with both my Nextcloud account and a public read-only calendar from Google, so I can keep myself à jour with our holidays here.

How to get started with vdirsyncer

It’s wise to start with the backend. The file file you need to edit is ~/.config/vdirsyncer and this is what my configuration file looks like (It should be safe to copy this and the other configuration files and just make minor obvious adjustments to them):

[general]

# A folder where vdirsyncer can store some metadata about each pair.
status_path = "~/.config/vdirsyncer/status/"

# CardDAV
# =======

# Operation Tulip
# ---------------

[pair nextcloud_contacts]
# A `[pair <name>]` block defines two storages `a` and `b` that should be
# synchronized. The definition of these storages follows in `[storage <name>]`
# blocks. This is similar to accounts in OfflineIMAP.
a = "nextcloud_contacts_local"
b = "nextcloud_contacts_remote"

# Synchronize all collections that can be found.
# You need to run `vdirsyncer discover` if new calendars/addressbooks are added
# on the server.
collections = ["from a", "from b"]

# Synchronize the "display name" property into a local file (~/.contacts/displayname).
metadata = ["displayname"]

# To resolve a conflict the following values are possible:
#   `null` - abort when collisions occur (default)
#   `"a wins"` - assume a's items to be more up-to-date
#   `"b wins"` - assume b's items to be more up-to-date
#conflict_resolution = null

[storage nextcloud_contacts_local]
# A storage references actual data on a remote server or on the local disk.
# Similar to repositories in OfflineIMAP.
type = "filesystem"
path = "~/.config/vdirsyncer/contacts/"
fileext = ".vcf"

[storage nextcloud_contacts_remote]
type = "carddav"
url = "https://cloud.operationtulip.com/remote.php/dav/addressbooks/users/<USERNAME>/contacts/"
username = "<USERNAME>"
password = "<PASSWORD>"

# CalDAV
# ======

# Operation Tulip
# ---------------

[pair nextcloud_calendar]
a = "nextcloud_calendar_local"
b = "nextcloud_calendar_remote"
collections = ["personligt", "fdelsedagar", "temadagar"]

# Calendars also have a color property
metadata = ["displayname", "color"]

[storage nextcloud_calendar_local]
type = "filesystem"
path = "~/.config/vdirsyncer/calendars/"
fileext = ".ics"

[storage nextcloud_calendar_remote]
type = "caldav"
url = "https://cloud.operationtulip.com/remote.php/dav/"
username = "<USERNAME>"
password = "<PASSWORD>"

# Helgdagar
# ---------

[pair helgdagar]
a = "helgdagar_local"
b = "helgdagar_remote"
collections = null

[storage helgdagar_local]
type = "filesystem"
path = "~/.config/vdirsyncer/calendars/helgdagar/"
fileext = ".ics"

[storage helgdagar_remote]
type = "http"
url = "https://calendar.google.com/calendar/ical/sv.swedish%23holiday%40group.v.calendar.google.com/public/basic.ics"
read_only = "true"

(My calendar “fdelsedagar” is actually named “Födelsedagar”, vdirsyncer just removed the letter “ö” for some reason.)

When you’re done setting up vdirsyncer you need to ‘discover’ both the calendars and the addressbooks with the command:

$ vdirsyncer discover

You can then synchronize everything with the command:

$ vdirsyncer sync

And you’re done! I highly recommend you scheduling the command using Cron. I have this in my configuration file for it to sync every three hours:

*/180 * * * * vdirsyncer sync

How to get started with Khal

The config is located at ~/.config/khal/config and my configuration file looks like this:

[default]

default_calendar = Personligt
highlight_event_days = true

[view]

dynamic_days= false

[locale]

unicode_symbols = false
local_timezone = Europe/Stockholm
default_timezone = Europe/Stockholm
timeformat = %H:%M
dateformat = %d.%m
longdateformat = %d.%m.%Y

[calendars]

[[Personligt]]
  path = ~/.config/vdirsyncer/calendars/personligt/
  color = light cyan

[[Födelsedagar]]
  path = ~/.config/vdirsyncer/calendars/fdelsedagar/
  color = dark blue

[[Helgdagar]]
  path = ~/.config/vdirsyncer/calendars/helgdagar
  color = yellow

Once it’s setup you can start using Khal. It comes with both a text-based user interface as well as a command-line interface. The syntax for the commands can be a bit tricky to figure out if you’re new and clueless, but the interactive mode is really simple.

When I was new I had issues remembering the syntax for the time and date. For some reason you format the syntax like this: <DD>.<MM>. <HH>:<MM> <HH>:<MM>, for an example: 12.08. 18:00 22:00. Which doesn’t make much sense to me. Something more sensible would be something like 12.08 18:00-22:00.

Anyway. Here’s a few examples of what you can do via the command-line:

$ khal printcalendars

List all events between today and 30 days forward:

$ khal list today 30d
Tuesday, 25.08.2020
 Linux födelsedag (R)

Adding a new entry

On a specific date (in this example it’s October 12 between the clock 18:00 and 22:00):

$ khal new 12.08. 18:00 22:00 "Laundry"

You can also specify dynamic dates, alarms, a specific calendar and a description:

$ khal new tomorrow 18:00 22:00 -alarm 10m -a <CALENDAR> "<TITLE> :: <DESCRIPTION>"

If you want it to be a recurring event:

$ khal new 12.08. "<EVENT>" -r <yearly/monthly/weekly/daily>

There’s also the flag -u, –until if you want it to be for a limited time only.

Searching for an event

$ khal search <KEYWORD>

Editing an event

When you want to edit an event you simply use a search keyword (like “Linus”):

$ khal edit --show-past "Linus"
28.12-28.12 Linus Torvalds födelsedag (R)
Edit?  [n]o  [q]uit  [s]ummary  [d]escription  da[t]etime range  re[p]eat
[l]ocation  [c]ategories  [a]larm  [D]elete: 

Importing a calendar from a file

It’s also possible to import calendars from ICS-files:

$ khal import -a <CALENDAR NAME> <FILE> 

The interactive mode

And if you want to use the interactive mode:

$ khal interactive

You can add a new event using the interactive mode without first running Khal, even though this method can be a bit annoying if you do it wrong:

$ khal new -i
summary: Test
datetime range: 12.08 13.08 13:00 15:00
critical: Could not parse "12.08 13.08 13:00 15:00".
critical: Please check your configuration or run `khal printformats` to see if this does match your configured [long](date|time|datetime)format.
critical: If you suspect a bug, please file an issue at https://github.com/pimutils/khal/issues/ 

To be honest I have no idea how the “datetime” syntax work here. I only use the shell commands myself, but I highly recommend using the proper interactive interface with khal interactive and then pressing the key N.

I also highly recommend you reading the documentation for Khal regarding all the available commands and how they all work.

How to get started with Khard

For some reason the name scheme doesn’t follow its sibling. For Khard the configuration file is ~/.config/khard/khard.config. Here’s my configuration file:

[general]

debug = no
default_action = list
editor = nvim
merge_editor = colordiff

[addressbooks]

[[Personal]]
path = ~/.config/vdirsyncer/contacts/contacts

[contact table]

# display names by first or last name: first_name / last_name
display = first_name

# group by address book: yes / no
group_by_addressbook = no

# reverse table ordering: yes / no
reverse = no

# append nicknames to name column: yes / no
show_nicknames = no

# show uid table column: yes / no
show_uids = yes

# sort by first or last name: first_name / last_name
sort = first_name

[vcard]

# extend contacts with your own private objects
# these objects are stored with a leading "X-" before the object name in the vcard files
# every object label may only contain letters, digits and the - character
# example:
#   private_objects = Jabber, Skype, Twitter
private_objects = Jabber

# preferred vcard version: 3.0 / 4.0
preferred_version = 3.0

# Look into source vcf files to speed up search queries: yes / no
search_in_source_files = no

# skip unparsable vcard files: yes / no
skip_unparsable = no

Running just khard prints all the contacts like this:

$ khard
<INDEX NUMBER>   <NAME>       cell: <PHONE NUMBER>        home: <E-MAIL ADRESS>    <ID>

You can show an contact with the argument show:

$ khard show alex
Name: Alexander
Address book: Personal
Phone
    cell: <PHONE NUMBER>
E-Mail
    home: <E-MAIL ADRESSS>
Address
    home: 
        <ADRESS>
Miscellaneous
    UID: <UUID>

If you want to edit a contact using your editor:

$ khard edit alex

The file is to large to show here, but it contains a lot of sane syntax and comments to help you understand how it works. Here’s the part with “important dates”:

# important dates
# Formats:
#   vcard 3.0 and 4.0: yyyy-mm-dd or yyyy-mm-ddTHH:MM:SS
#   vcard 4.0 only: --mm-dd or text= string value
# anniversary
Anniversary : 
# birthday
Birthday : 1970-01-01

I once again recommend you reading the documentation for Khard regarding all the available commands and how they all work.

I didn’t go thought every possible bit, but it should be enough to get you started and going! I hope you enjoy this software as much as I do.

Meta

No Comments

Use the e-mail form, if you wish to leave feedback for this post. Markdown is supported. [Terms of service]