搭建私人CardDAV/CalDAV服务_通讯录/日历同步服务_debian11_radicale3_nginx_DAVx5
转载注明来源: 本文链接 来自osnosn的博客,写于 2022-04-25.
参考
- 【radicale 官网】
- 【radicale3 的文档】
- 【radicale GitHub】
- 【DAVx5-ose GitHub】【DAVx5 官网】
- 【ICSx5 GitHub】【ICSx5 官网】
安装环境
- debian-11,
apt install radicale, debian11 stable 中,实际安装的版本是 radicale-3.0.6,
我没有使用 pip 安装,也没有去安装最新版 3.1.7,- nginx 用的是
apt install nginx-full, debian-11 stable 中的版本是 nginx-1.18.0,
安装
- radicale 的官网说,3.x 版本是开箱即用的。
- 用
apt install radicale安装的版本,
已经装好 radicale.service 服务启动脚本,
已经有了 /etc/radicale/ 配置目录。
已经有了 /var/lib/radicale/collections/ 数据目录。
并且还有了,用 uwsgi 启动的配置目录 /etc/uwsgi/ (我没有使用uwsgi) - 因为用 apt 安装的, 方便齐全, 并且够用。所以我没有使用 pip 安装。
配置
修改 radicale 配置
- 修改 /etc/radicale/config 文件
- 在
[server]中, 添加host = localhost:5232。
计划用 nginx 做反向代理,没必要绑定到0.0.0.0:5232或[::]:5232。- 用
localhost:5232,启动服务后,内网用http://192.168.x.x:5232/不能访问。 - 用
0.0.0.0:5232,启动服务后,内网用http://192.168.x.x:5232/可以访问。
- 用
- 在
[auth]中,
修改/添加type = htpasswd,
和htpasswd_filename = /etc/radicale/users,
和htpasswd_encryption = plain,
自己个人使用,账号文件,我选择不加密。自己忘了密码,还能查看。
如果你想更安全,可以选择 md5 加密。 - 改
[web]中,type = none, 关闭web登录页面, 只使用客户端访问。 - 关于 radicale 其他的配置项,用默认值。
- 在
- 创建 /etc/radicale/users 文件。
(用户名写成 email 格式,是为了在 DAVx5 中显示的账号,看起来更有辨识度),
plain的格式是,user01@myhost:pass123 user02@myhost:pass456 -
- 如果你选用 md5 加密,则需要用 htpasswd 命令去创建 users 文件。
- 为了安全,不让users文件全局可读,
chown radicale:radicale /etc/radicale/users; chmod 640 /etc/radicale/users;
- 我的 nginx 已经配置好 ssl,而 nginx 和 radicale 又在同一台机器。
所以,我就不再配置 radicale 的 ssl 证书了。 - radicale 服务, 将会以 radicale 用户身份执行。而安装时,
/var/lib/radicale/collections目录的 owner 是 root。会导致无法写入。
需要chown radicale:radicale /var/lib/radicale/collections修改 collections 目录的权限。 systemctl enable radicale激活服务。
systemctl start radicale启动服务。- 如果修改了 users 账号文件,无需重启 radicale 即生效。
- 用户(比如用户名:gst)的数据,将会放在
/var/lib/radicale/collections/collection-root/gst/中。
如果gst用户不再使用,则简单的删除gst/目录即可。
修改 nginx 配置
- 我的 nginx 是配置了 ssl 的。是使用 https:// 访问的。
- 在 nginx 的配置文件中,合适的地方,加入,(抄自radicale官方文档),
location /card/ { # The trailing / is important!
allow all;
proxy_pass http://127.0.0.1:5232/; # The trailing / is important!
proxy_set_header X-Script-Name /card; #与location中的目录名相同,去掉最后的/号
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass_header Authorization;
}
service nginx reload重启 nginx。
使用
- 用浏览器访问
https://xxxx.mydomain.com:8888/card/(这里用的是,我的 nginx 对外的 ddns 域名和端口)
登录账号,- 可以创建 addressbook 空集合 和 calendar 空集合,
- 可以修改 集合的名称,描述。(名称,描述都能在DAVx5中能看到,描述中的中文显示正常).
- 可以上传 ics 或 vcf 文件, 生成一个新集合。(vcf对应通讯录,ics对应日历)。
- 可以下载已有集合中的所有数据,成为一个单独文件。
- 下载的vcf版本格式,与你之前上传的一样。
你之前上传的是vcf-2.1格式的,下载时也是vcf-2.1格式的。
- 下载的vcf版本格式,与你之前上传的一样。
- 3.0.6版中,web不能编辑集合中的内容(增,删,改)。
- 在 Android 手机中安装 DAVx5,添加账号,"使用 URL 和用户名登录"。
"根地址" 填https://xxxx.mydomain.com:8888/card/即可。- 然后点击刚才加入的账号。选择"CARDDAV", 点右上角的"三个点", 选"刷新通讯录列表"。
勾选上需要同步的集合,点击右下角"同步"图标。
就能双向同步 对应的集合了。 - 一个radicale账号中,可以有多个集合, DAVx5也支持。方便你对通讯录分类保存,同步。
- CalDAV创建了集合后,在手机日历中, 新创建的事件,就可以同步到服务器。
- DAVx5对于CalDAV默认只同步90天内的事件。可以在设置中"旧日程时间限制"设置为空,表示"同步所有日程"。
或者, 根据需要改为 180天,365天。
- DAVx5对于CalDAV默认只同步90天内的事件。可以在设置中"旧日程时间限制"设置为空,表示"同步所有日程"。
- 然后点击刚才加入的账号。选择"CARDDAV", 点右上角的"三个点", 选"刷新通讯录列表"。
- 手机中的通讯录,默认是存在 "本机"。如果要变成 cardDAV 的。
需要从手机中导出通讯录,然后在浏览器中,上传通讯录(支持vcf,ics格式)- 上传vcf文件后,会在当前用户中生成一个没有名字的新的通讯录集合。
在DAVx5中,要在对应账号中,点击 "刷新通讯录列表",才能看到。
导入的无名称的新集合,只能在web网页端修改名称。
在DAVx5中,有创建新集合,删除已有集合。没有修改已有集合名称的地方。 - 在浏览器中,删除一个集合。
系统目录/var/lib/radicale/collections/collection-root/用户名/中,
对应集合的目录也删除了。没有数据残留。 - 上传 ics 文件,是导入日历事件。
- 上传vcf文件后,会在当前用户中生成一个没有名字的新的通讯录集合。
- 手机中的通讯录,默认存在 "本机" 的联系人,转存到 cardDAV 的不通过电脑的其他办法。
首先,在DAVx5中,carddav中创建一个通讯录集合,用于后续的导入操作。- 如果手机通讯录中有 "复制联系人"功能,则选择从"本机"复制到"DAVx5"账号的通讯录集合中。
- 或者,把手机通讯录,导出到"存储设备"中。然后从"存储设备"中导入到"DAVx5"账号的通讯录集合中。
最后在手机的目录中, 删除刚才导出的"00001.vcf"文件。
- DAVx5 可以去 F-Droid 下载, 目前的最新版是 4.2.0.3-ose,10MB。
- DAVx5 中的 mount "WebDAV 文件系统" 功能,是只读的,比较鸡肋。没什么用处。
如果要查看 WebDAV 中的文件,只能通过分享到第三方应用打开查看。
其他
- 如果不想开放 radicale 的 web 页面登陆。仅使用 DAVx5.
改/etc/radicale/config文件,[web]中,type = none, 然后重启 radicale 即可。 - 如果想限制 radicale 的 web 页面登陆的IP范围。
- web访问,会访问
/card/.web/, 有GET, PROPFIND,PUT,...请求, 没有POST。 - DAVx5 没有
GET,POST请求, 不访问/card/.web/目录。 - 可以配置 nginx , 限制
/.web/目录的访问, 或者限制GET请求。
- web访问,会访问
- radicale 有机制, 记录所有通讯录的改变。比如,每当通讯录有改变, 就自动调用 git 命令提交一次记录。
看官方文档中,关于[storage]中,hook =的描述。 - DAVx5 支持服务自动发现。需要把两个链接临时重定向(302)到 radicale 的目录。
这样, 在DAVx5中添加账号时,根地址(URL)就不用写全路径了。/.well-known/caldav→ CalDAV service path (302 Found), →/card//.well-known/carddav→ CardDAV service path (302 Found), →/card/
这个可以通过 nginx 的配置return 302 xxx或者rewrite xxx xxx redirect, 来实现。
- 服务器更换域名。手机DAVx5中,删除原账号,用新域名添加账号。
所有的数据集都在,勾选即可。都会恢复。
错误
- 如果通过手机"复制联系人", 或者在手机中"导出"再"导入", 基本不会出现错误。
- 如果通过浏览器,上传 vcf 文件失败。
在服务器上执行tail -f /var/log/syslog,
再次上传试试,看看 syslog 中输出了什么错误。
方便您定位错误。 - 我发现 3.0.6 版,对 vcf-3.0 版的格式,支持很好。
- 我发现 3.0.6 版,上传 华为手机 鸿蒙系统 导出的 vcf-2.1 版,支持不好。
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=
=95=E6=B5=8B=E8=AF=95=E6=B5=8B;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=
=95=E6=B5=8B=E8=AF=95=E6=B5=8B
TEL;CELL:12 345 678 90
PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQS....
2wBDAQcHBwo....
END:VCARD
上面的文件,上传会失败。
- 把 Quoted-Printable 编码,整理成一行。
Quoted-Printable编码的基本方法是:输入数据在33-60、62-126范围内的,直接输出;其它的需编码为“=”加两个字节的HEX码(大写)。为保证输出行不超过规定长度,可在行尾加“=/r/n”序列作为软回车。- PHOTO 项中(如果有头像),要把 ENCODING=BASE64 改为 ENCODING=B 才行。
修改为下面的内容,才行,
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B=E8=AF=95=E6=B5=8B
TEL;CELL:12 345 678 90
PHOTO;ENCODING=B;JPEG:/9j/4AAQS....
2wBDAQcHBwo....
END:VCARD
才能成功上传。
完成
-
其他CardDAV安装,请看【搭建私人通讯录/日历同步服务_使用cardDAV/calDAV服务】
-
WebDAV 的搭建,请看【搭建私人的云笔记_使用webdav服务】
修改 Radicale 显示 WebCal 订阅集合
成功了,但似乎没什么用。不知道还缺了什么。
- 我用的是debian的默认安装。 Radicale-3.0.6-3
- 修改
/usr/lib/python3/dist-packages/radicale/app/propfind.py
--- propfind.py_org 2022-05-01 12:46:29.744932752 +0800
+++ propfind.py 2022-05-01 12:50:38.153823346 +0800
@@ -78,7 +78,7 @@
raise ValueError("Only use one of props, propname and allprops")
is_collection = isinstance(item, storage.BaseCollection)
if is_collection:
- is_leaf = item.get_meta("tag") in ("VADDRESSBOOK", "VCALENDAR")
+ is_leaf = item.get_meta("tag") in ("VADDRESSBOOK", "VCALENDAR","VSUBSCRIBED")
collection = item
else:
collection = item.collection
@@ -251,6 +251,10 @@
child_element = ET.Element(
xmlutils.make_clark("C:calendar"))
element.append(child_element)
+ elif item.get_meta("tag") == "VSUBSCRIBED":
+ child_element = ET.Element(
+ xmlutils.make_clark("CS:subscribed"))
+ element.append(child_element)
child_element = ET.Element(xmlutils.make_clark("D:collection"))
element.append(child_element)
elif tag == xmlutils.make_clark("RADICALE:displayname"):
- 修改
/usr/lib/python3/dist-packages/radicale/item/__init__.py
--- __init__.py_org 2022-05-01 12:56:01.865771350 +0800
+++ __init__.py 2022-05-01 13:00:07.658694689 +0800
@@ -67,7 +67,7 @@
The ``tag`` of the collection.
"""
- if tag and tag not in ("VCALENDAR", "VADDRESSBOOK"):
+ if tag and tag not in ("VCALENDAR", "VADDRESSBOOK","VSUBSCRIBED"):
raise ValueError("Unsupported collection tag: %r" % tag)
if not is_collection and len(vobject_items) != 1:
raise ValueError("Item contains %d components" % len(vobject_items))
@@ -185,7 +185,7 @@
if not v:
del props[k]
continue
- if v not in ("VCALENDAR", "VADDRESSBOOK"):
+ if v not in ("VCALENDAR", "VADDRESSBOOK","VSUBSCRIBED"):
raise ValueError("Unsupported collection tag: %r" % v)
- 然后,重启 radicale。
- 然后,用浏览器访问 radicale的后台,创建一个 calendar 的集合。
- 然后,登录服务器,进入目录,
cd /var/lib/radicale/collections/collection-root/你的用户名/刚刚创建的日历集合/,- 修改文件
.Radicale.props(建议先备份)。删除原来的所有内容,改为。
{"tag": "VSUBSCRIBED", "D:displayname": "Online Calendar", "ICAL:calendar-color": "#00BBEEFF", "CS:source": "https://xxxx.mydomain.com:8888/myics/my_webcal.php"}
- 修改文件
- 这时候,手机端的 DAVx5 就能刷出这个webcal订阅集合了。
![]()
可是,点击这个订阅集合,DAVx5 说,找不到支持 webcal的应用。就算装了 ICSx5,也还是说找不到。只好放弃。 - 这时候,浏览器登陆radicale后台,被修改的日历集合,就不见了。
- 在 DAVx5 的 WebCal 中,点击对应的订阅集合,选择"删除集合",勾上"从服务器上删除",可以成功删除这个集合。
去后台的文件系统查看,对应的目录也被删除了。
- 在 DAVx5 的 WebCal 中,点击对应的订阅集合,选择"删除集合",勾上"从服务器上删除",可以成功删除这个集合。
- 其实,要发布 WebCal,不需要 Radicale 支持。
要使用 WebCal,ICSx5 能直接添加 URL 订阅,支持用户认证。
注: 小米手机日历自带的 URL 订阅功能,不支持用户认证。 -
请看:【创建自己的 WebCal 日历订阅链接】
- 可以考虑写个php程序,扫描某个特定的 radicale 的日历集合目录中的 ics文件。输出一个订阅链接。
这样,就可以用手机登陆 radicale 账号。手工维护一个 日历订阅。
关于php读取radicale目录的权限。可以通过usermode www-data -G radicale; service php74-fpm restart;解决。 - 没必要自己写程序了,看下文"把某个日历集合,作为 WebCal 订阅URL的只读分享"。(2025-05)
- 可以考虑写个php程序,扫描某个特定的 radicale 的日历集合目录中的 ics文件。输出一个订阅链接。
把 Radicale 中某账号的某个日历集合,作为 WebCal 订阅URL的只读分享
方法1
- 参考官方文档【Sharing a collections read-only to public as WebCAL】
- 随着Debian从11升级到12,版本变为: radicale-3.1.8,nginx-1.22.1。(2025-05记录)
- 修改 /etc/radicale/config 中,[web] type = internal,然后重启radicale服务。
用浏览器登录,找到账号下,某个日历集合的下载链接,通常是这个样子的,
https://xxxx.mydomain.com:8888/card/<用户名>/12345678-1234-1234-1234-1234567890ab/
然后改 [web] type = none,重启radicale服务,恢复原来的配置。 - 修改 nginx 中的反代配置为:(2025-05记录)
location /card/ { # The trailing / is important! allow all; proxy_pass http://127.0.0.1:5232/; # The trailing / is important! proxy_set_header X-Script-Name /card; #与location中的目录名相同,去掉最后的/号 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass_header Authorization; # 以上的配置没有变化。以下的是新增的配置。 # 内层的location中的路径, 要包含外层location的路径,"/card/" location = /card/cal/myshare_calendar.ics { #这个路径自己定义,这个文件路径无需真实存在 proxy_pass http://127.0.0.1:5232/<用户名>/12345678-1234-1234-1234-1234567890ab/; #对应日历集合的URL # "Basic <base64 encodede USER:PASS>" example generated with 'echo -n "ANON1:ANON1" | base64' proxy_set_header Authorization "Basic QU5PTjE6QU5PTjE="; #对应的账号和密码 if ($request_method !~ ^GET$ ) { #限制只能GET; 如果不限制,可以用PUT方法更改集合内容 return 403; } } location = /card/cal/other_share.ics { #另一个日历集合的分享 proxy_pass http://127.0.0.1:5232/<用户名>/87654321-abcd-abcd-abcd-1234567890ab/; proxy_set_header Authorization "Basic QU5PTjE6QU5PTjE="; #对应的账号和密码 if ($request_method !~ ^GET$ ) { return 403; } #限制只能GET } } - 然后,把
https://xxxx.mydomain.com:8888/card/cal/myshare_calendar.ics作为WebCal的URL分享给别人使用。
此日历集合的内容,由配置了CalDAV账号的人来维护(比如使用DAVx5,增/删/改),其他人通过WebCal订阅来获取。
这个做法是,用nginx来限制仅允许GET方法。(2025-05记录)
官方文档说,可能不够安全。我觉得个人使用应该OK。 - 另外,官方文档中,描述的做法,
创建一个独立用户,在right中限制这个用户的权限,创建软连接文件。
只读分享的内容似乎是一个静态的ics文件,和上文实现的分享内容,不一样。
方法2
- 还有一个更简单的方法,生成WebCal只读分享。
- 在服务器上,定时执行脚本,比如每10分钟执行一次,
curl -s "https://<用户名>:<密码>@xxxx.mydomain.com:8888/card/<用户名>/12345678-1234-1234-1234-1234567890ab/" --output myshare_test1.ics #或者 curl -s "http://<用户名>:<密码>@127.0.0.1:5232/<用户名>/12345678-1234-1234-1234-1234567890ab/" --output myshare_test1.ics - 把对应的日历集合,下载成为一个静态的ics文件,放在nginx的某个目录中,
直接使用这个静态ics文件的链接,作为WebCal的URL分享给别人使用。 - 缺点是,非实时更新,更新频率受到定时任务间隔限制。
- 好处是,如果用户数很多时,nginx静态文件性能非常好,radicale服务的负荷很小。安全性好。
其他方案,使用公共服务
- 由配置了Exchange账号,或者CalDAV账号的人来维护日历的内容,其他人通过WebCal订阅来获取日历。
不想自己建立系统,也可以使用现成的,具有webcal分享功能的服务。 - Google日历,国内使用受限制。
打开Google日历,在日历设置->集成目录->以iCal格式显示不公开网址,得到WebCal链接。
支持Exchange协议。 - iCloud日历
打开苹果日历Mac客户端,在右侧账号上点鼠标右键,选择共享日历,勾选。重新点击鼠标右键,查看共享日历的链接。
支持CalDav协议。 - Outlook日历
打开Outlook邮箱设置,日历->共享日历->发布日历,发布后,会看到ICS链接地址。
支持Exchange协议。 - 得到WebCal链接后,
无论是,直接分享此链接,
还是,用脚本下载后放入某个nginx的静态文件目录,再分享静态文件链接。
都可以。 - 以下的服务,不确定是否支持WebCal分享。
Synology群晖日历, 支持CalDav.
ownCloud日历
Nextcloud日历
WPS日历
滴答清单, 支持CalDav, WebCal分享
----end----
转载注明来源: 本文链接 https://www.cnblogs.com/osnosn/p/16184705.html
来自 osnosn的博客 https://www.cnblogs.com/osnosn/ .


浙公网安备 33010602011771号