{"id":1403,"date":"2017-07-23T22:00:16","date_gmt":"2017-07-24T05:00:16","guid":{"rendered":"https:\/\/partofthething.com\/thoughts\/?p=1403"},"modified":"2023-10-29T09:34:19","modified_gmt":"2023-10-29T16:34:19","slug":"host-your-own-contacts-and-calendars-and-share-them-across-devices","status":"publish","type":"post","link":"https:\/\/partofthething.com\/thoughts\/host-your-own-contacts-and-calendars-and-share-them-across-devices\/","title":{"rendered":"Host your own contacts and calendars and share them across devices"},"content":{"rendered":"<p>I&#8217;m trying to learn ways to minimize my reliance upon large companies for handling my day-to-day personal data. So I figured calendar and contacts should be on my list of things to self-host. This post is about how I migrated all my Google calendars and phone contacts to my own server without losing any features I was using. I&#8217;m doing this mostly for fun.<\/p>\n<figure id=\"attachment_1404\" aria-describedby=\"caption-attachment-1404\" style=\"width: 629px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot-from-2017-07-23-20-12-47.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1404\" src=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot-from-2017-07-23-20-12-47.jpg\" alt=\"\" width=\"629\" height=\"296\" srcset=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot-from-2017-07-23-20-12-47.jpg 629w, https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot-from-2017-07-23-20-12-47-300x141.jpg 300w\" sizes=\"auto, (max-width: 629px) 100vw, 629px\" \/><\/a><figcaption id=\"caption-attachment-1404\" class=\"wp-caption-text\">My self-hosted calendar in the web client<\/figcaption><\/figure>\n<figure id=\"attachment_1405\" aria-describedby=\"caption-attachment-1405\" style=\"width: 432px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot_2017-07-23-20-17-21.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1405 size-full\" src=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot_2017-07-23-20-17-21.jpg\" alt=\"\" width=\"432\" height=\"768\" srcset=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot_2017-07-23-20-17-21.jpg 432w, https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/Screenshot_2017-07-23-20-17-21-169x300.jpg 169w\" sizes=\"auto, (max-width: 432px) 100vw, 432px\" \/><\/a><figcaption id=\"caption-attachment-1405\" class=\"wp-caption-text\">My self-hosted calendar on the phone<\/figcaption><\/figure>\n<p><!--more--><\/p>\n<h1>Installing Radicale CalDAV\/CardDAV server behind an Apache Reverse Proxy<\/h1>\n<p>First step is to choose a <a href=\"https:\/\/en.wikipedia.org\/wiki\/CalDAV\">CalDAV<\/a>\/<a href=\"https:\/\/en.wikipedia.org\/wiki\/CardDAV\">CardDAV<\/a> server. These are standard file formats that hold calendar and contact info, and there are lots of choices. I found <a href=\"http:\/\/radicale.org\/\">Radicale<\/a>, which I liked because it was small, simple, open source, and written in Python, my favorite programming language! In its simplest form, it just runs on a port of your server, and you can test it out like that.<\/p>\n<p>I wanted the extra security of running it behind the more robust web server I already had running (Apache) with authentication, so I had to set up a Reverse Proxy, where Apache passes traffic to the Radicale instance and back. The <a href=\"http:\/\/radicale.org\/proxy\/\">instructions work<\/a> but are specific to nginx, so I had to figure out what to do for Apache. Here&#8217;s what I got:<\/p>\n<pre class=\"lang:sh highlight:0 decode:true\" title=\"Apache config for Radicale reverse proxy\">ProxyPass \/radicale\/ http:\/\/127.0.0.1:8822\/\n&lt;Location \/radicale\/&gt;\n    ProxyPassReverse \/radicale\/\n    RewriteRule ^\/radicale\/(.*)$ http:\/\/127.0.0.1:8822\/$1 [QSA,L,E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n    RequestHeader set X-Remote-User %{REMOTE_USER}s\n    RequestHeader set X-Script-Name \/radicale\n    AuthType Basic\n    AuthName \"Secret Self-Hosted Calendar\"\n    require valid-user\n    AuthUserFile \"\/path\/to\/my\/htpasswd\"\n&lt;\/Location&gt;<\/pre>\n<p>This authenticates users that hit the \/radicale\/ folder and then sends everything to the Radicale server chilling on port 8822. I put two users in my htpasswd file, one for me and the other to be shared across the family.<\/p>\n<p>I also have an older server that&#8217;s still running <code>upstart<\/code> so I had to come up with the upstart version of the startup script for Radicale:<\/p>\n<pre class=\"lang:sh highlight:0 decode:true\" title=\"Radicale startup script for servers with upstart instead of systemd\">description \"Radicale CalDAV and CardDAV server\"\nsetuid radicaleuser\nstart on net-device-up\nrespawn\nexec \/usr\/bin\/env python3 -m radicale --config \/etc\/radicale\/config<\/pre>\n<p>With that all in place, I could get to the server&#8217;s web interface, log in with the htpasswd info, and add some empty calendars and addressbooks.<\/p>\n<figure id=\"attachment_1407\" aria-describedby=\"caption-attachment-1407\" style=\"width: 627px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/blog-interface.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1407 size-full\" src=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/blog-interface.jpg\" alt=\"\" width=\"627\" height=\"578\" srcset=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/blog-interface.jpg 627w, https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/blog-interface-300x277.jpg 300w\" sizes=\"auto, (max-width: 627px) 100vw, 627px\" \/><\/a><figcaption id=\"caption-attachment-1407\" class=\"wp-caption-text\">Radicale web interface working<\/figcaption><\/figure>\n<p>I created a one calendar in my personal folder and another in the family account and then symbolically linked the family one into my folder in the Radicale storage area so I could share it easily.<\/p>\n<h1>Importing, Organizing, and Merging Contacts<\/h1>\n<p>Now to populate the server! I had several overlapping contact lists on various devices:<\/p>\n<ul>\n<li>Some old phone numbers on my SIM card on my phone. I decided to ignore most of these because the majority of them are duplicated elsewhere.<\/li>\n<li>More recent phone numbers and a few email addresses in my <a href=\"https:\/\/www.google.com\/contacts\">Google Contacts<\/a>, gathered from my phone. I exported these all with the Export function in vCard format to my PC.<\/li>\n<li>A bunch of email addresses on my Thunderbird email client on my laptop. If you use a web-based email, just export your contacts from there to a file.<\/li>\n<\/ul>\n<p>I cleaned these all up and merged them by first connecting <a href=\"https:\/\/addons.mozilla.org\/en-US\/thunderbird\/addon\/cardbook\/\">CardBook<\/a> to my contact URL shown on the Radicale web interface and then right-clicking the address book and selecting &#8220;Import contacts from a file.&#8221; Import however many files you exported. Then I ran the CardBook merge tool to combine all duplicates and do other cleanups. It was pretty satisfying.<\/p>\n<p>I had a few configuration things wrong and just ran a <code>tail -f<\/code>\u00a0 on the Radicale log. When syncing wasn&#8217;t working, I saw exceptions there when blank FN (Family Name) entries weren&#8217;t allowed by Radicale and any contact that was missing FN was crashing the sync.<\/p>\n<figure id=\"attachment_1409\" aria-describedby=\"caption-attachment-1409\" style=\"width: 755px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/cardbook.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1409 size-full\" src=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/cardbook.jpg\" alt=\"Cardbook screenshot with contacts listed\" width=\"755\" height=\"415\" srcset=\"https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/cardbook.jpg 755w, https:\/\/partofthething.com\/thoughts\/wp-content\/uploads\/cardbook-300x165.jpg 300w\" sizes=\"auto, (max-width: 755px) 100vw, 755px\" \/><\/a><figcaption id=\"caption-attachment-1409\" class=\"wp-caption-text\">Cardbook with contacts loaded up show in Thunderbird<\/figcaption><\/figure>\n<h1>Transferring calendars to Radicale<\/h1>\n<p>Moving my calendars was straightforward using <a href=\"https:\/\/addons.mozilla.org\/en-US\/thunderbird\/addon\/lightning\/\">the Lightning Thunderbird plugin<\/a>. I just exported them from where they were (Google Calendar) and imported them in Lightning into my new calendar. To get access to the family account as well as my personal one from Thunderbird, I had to activate the <code>calendar.network.multirealm<\/code> setting in Thunderbird advanced config so it would ask me for multiple credentials.<\/p>\n<h1>Syncing to Android phones and iPhones<\/h1>\n<p>Obviously the calendars and contacts need to be shared with the phone. For Android, I installed the open-source <a href=\"https:\/\/davdroid.bitfire.at\/\">DAVDroid<\/a> which you can get for a few bucks on the Play Store or for free on <a href=\"https:\/\/f-droid.org\/\">F-Droid<\/a>. Just type in the URL from the Radicale web interface and you&#8217;ll be off and running with Calendar and Contact Sync. The calendar shows up in any calendar app (including Google Calendar) and the contacts show up in any contacts app. I then filtered out and\/or deleted the contacts and calendars stored on other people&#8217;s servers.<\/p>\n<p>For the my wife&#8217;s iPhone, I just synced the family calendar. It was as easy as going to Settings-&gt;Accounts and adding a new CalDAV account pointing to the URL from the Radicale web interface and typing in the htpasswd credentials. Then the calendar showed up in any\/all calendar apps.<\/p>\n<h1>Web calendar client<\/h1>\n<p>I wasn&#8217;t sure if I was ready to just use Thunderbird Lightning as my calendar client because I&#8217;m so used to a web interface. Quick searching led me to <a href=\"https:\/\/github.com\/agendav\/agendav\">AgenDAV<\/a>, an open-source CalDAV web client. One more quick Apache config and we were off and running. (You could also just put this in its own subdomain).<\/p>\n<pre class=\"lang:sh highlight:0 decode:true\" title=\"One possible apache config for agendav\">Alias \/agendav  \/opt\/agendav-2.0.0\/web\/public\n&lt;Location \/agendav&gt;\n           Require all granted\n           RewriteEngine On\n           RewriteCond %{REQUEST_FILENAME} !-f \n           RewriteRule ^ index.php [QSA,L]\n&lt;\/Location&gt;\n<\/pre>\n<p>Looks pretty nice! (Screenshot at top of page)<\/p>\n<p>You can&#8217;t directly add an external link to an external calendar (like a public rec soccer league one) so in those scenarios just import it into the server first and it will show up here.<\/p>\n<h1>Summary<\/h1>\n<p>All in, this was less than a day of work. Not bad for me since I had been really wanting to do some cleanup on my contacts for a long time anyway. Granted, doing all this means I have to maintain it and try to secure it myself (something I&#8217;ll surely suffer the consequences of someday). At least I saved backups of everything in the process. Normal people might want to just consider a more in-the-box solution such as <a href=\"https:\/\/nextcloud.com\/\">Nextcloud<\/a> but I personally just enjoy learning and doing these kinds of things.<\/p>\n<p><a href=\"https:\/\/news.ycombinator.com\/item?id=14836265\"><em>Discuss on Hacker News<\/em><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m trying to learn ways to minimize my reliance upon large companies for handling my day-to-day personal data. So I figured calendar and contacts should be on my list of things to self-host. This post is about how I migrated all my Google calendars and phone contacts to my own server without losing any features &hellip; <a href=\"https:\/\/partofthething.com\/thoughts\/host-your-own-contacts-and-calendars-and-share-them-across-devices\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Host your own contacts and calendars and share them across devices<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":""},"categories":[3],"tags":[],"class_list":["post-1403","post","type-post","status-publish","format-standard","hentry","category-computers"],"_links":{"self":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts\/1403","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/comments?post=1403"}],"version-history":[{"count":10,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts\/1403\/revisions"}],"predecessor-version":[{"id":2409,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/posts\/1403\/revisions\/2409"}],"wp:attachment":[{"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/media?parent=1403"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/categories?post=1403"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/partofthething.com\/thoughts\/wp-json\/wp\/v2\/tags?post=1403"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}