Ubuntu 24.04 安装 DAViCal 1.1.13 指南

Ubuntu 24.04.5 实测安装 DAViCal 双方案:apt 在线装 1.1.12-2 与官方 tar.xz 部署 1.1.13(DB schema 1.3.6),覆盖 PostgreSQL 角色与 schema 初始化、pg_hba 认证顺序、Apache/PHP 挂接、CalDAV/CardDAV/freebusy 全链路验证、升级降级与 8 条排障实录。

DAViCalDAViCalCalDAVCardDAV日历PHPPostgreSQLApacheUbuntu自托管2026/11/01

Ubuntu 24.04 安装 DAViCal 1.1.13 指南

目标:在 Ubuntu 24.04.5 LTS 服务器上以两种方式安装 DAViCal——Ubuntu universe 仓库 apt install davical(1.1.12-2)与官方 tar.xz 源码包部署(1.1.13,主线),覆盖 PostgreSQL 16 角色与 schema 初始化、Apache 2.4 + PHP 8.3 配置、CalDAV/CardDAV 端到端验证与升级/降级/卸载方法。两种方案均已在真实服务器路径下实测通过。

一、环境信息

  • 操作系统:Ubuntu 24.04.5 LTS(x86_64)
  • Web 服务器:Apache/2.4.58(Ubuntu),libapache2-mod-php8.3 模式运行
  • PHP:PHP 8.3.6 (cli)(built: Oct 19 2026),扩展 pgsql、xml/expat
  • 数据库:PostgreSQL 16.15(Ubuntu 16.15-0ubuntu0.24.04.1)
  • 软件版本:
    • 方案 A:davical 1.1.12-2 + libawl-php 0.64-1(Ubuntu universe 仓库)
    • 方案 B:DAViCal 1.1.13 + AWL 0.65(官网 tar.xz,DB schema 1.3.6)
  • 域名与路径:davical.example.com(测试环境以本机 hosts 解析到 127.0.0.1);代码位于 /usr/share/davical、/usr/share/awl;配置位于 /etc/davical/;监听 80 端口
  • 数据库对象:库 davical,角色 davical_dba(DDL 属主)/ davical_app(应用运行),演示口令统一 q1w2e3r4
  • 演示账号:管理员 admin,口令 q1w2e3r4;测试用户 q1

二、前置条件

安装前确认以下环境就绪:

# PostgreSQL 与 Apache 服务可用
systemctl is-active postgresql apache2
active
active
# PHP 版本与关键扩展
php -v | head -1
php -m | grep -iE "^(pgsql|xml|pdo)$"
PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)
pgsql
PDO
xml
  • 方案 A 需可访问 Ubuntu 软件源;方案 B 需可访问 www.davical.org 下载源码包。
  • 具备 root/sudo 权限;psql 客户端可用(psql --version → psql (PostgreSQL) 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1))。
  • PostgreSQL 配置文件路径为 /etc/postgresql/16/main/pg_hba.conf(Ubuntu 发行版布局,非数据目录内)。

三、版本选型说明

渠道版本配套 AWLDB schema说明
Ubuntu 24.04 universe 仓库1.1.12-2libawl-php 0.64-11.3.5依赖全自动解析,版本落后一个发行
官网 tar.xz(主线)1.1.13(2026-05-12 发布)AWL 0.65(同日发布)1.3.6含 2023–2025 年 PHP 8.2/8.3 适配、LDAP 增强、iSchedule 修复

两个版本的差异可通过响应头直接验证(实测见第五、六章):apt 版返回 X-DAViCal-Version: DAViCal/1.1.12; DB/1.3.5,tarball 版返回 X-DAViCal-Version: DAViCal/1.1.13; DB/1.3.6。生产环境建议采用官方 1.1.13;快速试用或内网只能用发行版源时选 apt 版。

四、方案 A:apt 在线安装(Ubuntu universe,1.1.12-2)

4.1 方案说明

Ubuntu 24.04 的 universe 组件收录了 davical 包,安装时自动拉齐 Apache、PHP、PostgreSQL、Perl 依赖,并预置 Apache 站点与 /etc/davical/config.php 模板。适合快速部署,代价是版本停留在 1.1.12-2。

4.2 确认并安装软件包

apt-cache policy davical
davical:
  Installed: (none)
  Candidate: 1.1.12-2
  Version table:
DEBIAN_FRONTEND=noninteractive apt-get install -y davical

输出节选(实测首尾):

Setting up postgresql-16 (16.15-0ubuntu0.24.04.1) ...
Creating new PostgreSQL cluster 16/main ...
...
Setting up davical (1.1.12-2) ...
Setting up postgresql (16+257build1.1) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Processing triggers for php8.3-cli (8.3.6-0ubuntu0.24.04.13) ...
Processing triggers for libapache2-mod-php8.3 (8.3.6-0ubuntu0.24.04.13) ...

⚠️ 注意:安装过程若出现 invoke-rc.d: policy-rc.d denied execution of start.,表示包已装好但 PostgreSQL/Apache 未自启(常见于初始化策略受限环境),需手工 systemctl start postgresql apache2 后再继续。

验证依赖组件版本:

dpkg -l | grep -E "davical|libawl-php|php8.3-pgsql|libdbd-pg-perl|libyaml-perl" | awk '{print $2, $3}'
davical 1.1.12-2
libawl-php 0.64-1
libdbd-pg-perl 3.18.0-1build3
libyaml-perl 1.31-1ubuntu0.24.04.1
php8.3-pgsql 8.3.6-0ubuntu0.24.04.13

4.3 包提供的关键文件

dpkg -L davical | grep "^/etc"
/etc/apache2/sites-available/davical.conf
/etc/davical/config.php

包同时预置了 /usr/share/davical/dba/ 全套数据库脚本(create-database.sh、davical.sql、base-data.sql、patches/ 等),与源码包一致。

4.4 PostgreSQL 访问控制与角色

DAViCal 通过本机 Unix socket 以 davical_dba/davical_app 连接数据库,需在 pg_hba.conf 中为其声明 md5 规则,且必须位于通用 local all all peer 行之前(PostgreSQL 按第一条匹配规则认证):

cd /etc/postgresql/16/main
python3 - <<'EOF'
p = "pg_hba.conf"
s = open(p).read()
ins = "local   davical         davical_dba         md5\nlocal   davical         davical_app         md5\n"
s = s.replace("local   all             all                                     peer", ins + "local   all             all                                     peer", 1)
open(p, "w").write(s)
EOF
grep -n davical pg_hba.conf
123:local   davical         davical_dba         md5
124:local   davical         davical_app         md5

预创建两个角色并设置口令(口令即文档约定的 q1w2e3r4):

su postgres -c "psql -c \"CREATE ROLE davical_dba LOGIN PASSWORD 'q1w2e3r4' NOCREATEDB NOCREATEROLE;\" -c \"CREATE ROLE davical_app LOGIN PASSWORD 'q1w2e3r4' NOCREATEDB NOCREATEROLE;\""
CREATE ROLE
CREATE ROLE
systemctl reload postgresql

4.5 数据库 schema 初始化

以 postgres 系统用户执行包内脚本。首次直接执行会失败(输出节选,实测首尾):

su postgres -c "/usr/share/davical/dba/create-database.sh"
DBI connect('dbname=davical','davical_dba',...) failed: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: fe_sendauth: no password supplied at /usr/share/davical/dba/update-davical-database line 244.
Can't connect to database davical at /usr/share/davical/dba/update-davical-database line 244.
* * * * ERROR * * * *
The database administration utility failed. ...

原因:脚本内部以 davical_dba 身份二次连接数据库执行权限设置,而 md5 认证需要口令。先清理半成品库,再通过 PGPASSWORD(或 ~postgres/.pgpass,格式 *:*:davical:davical_dba:q1w2e3r4,权限 600)重跑:

su postgres -c "dropdb --if-exists davical; PGPASSWORD=q1w2e3r4 /usr/share/davical/dba/create-database.sh"
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.
NOTE
====
*  The password for the 'admin' user has been set to 'LRbJ8E7o'

Thanks for trying DAViCal! Check the configuration in /etc/davical/config.php.
For help, look at our website and wiki, or visit #davical on irc.oftc.net.

💡 说明:脚本用 pwgen 随机生成 admin 初始口令并打印。按本文演示规范将其重置为固定口令(明文 ** 前缀仅初始配置阶段可读,改过之后即转哈希存储):

su postgres -c "psql davical -c \"UPDATE usr SET password = '**q1w2e3r4' WHERE username = 'admin';\""
UPDATE 1

4.6 应用配置

编辑 /etc/davical/config.php,为数据库连接串补上口令(第 17 行):

sed -i 's|dbname=davical user=davical_app";|dbname=davical user=davical_app password=q1w2e3r4";|' /etc/davical/config.php
grep -n 'pg_connect\[\] =' /etc/davical/config.php | head -1
17:$c->pg_connect[] = "dbname=davical user=davical_app password=q1w2e3r4";

4.7 Apache 配置

a2ensite davical.conf
apache2ctl configtest
Enabling site davical.
To activate the new configuration, you need to run:
  systemctl reload apache2
AH00526: Syntax error on line 55 of /etc/apache2/sites-enabled/davical.conf:
Invalid command 'Header', perhaps misspelled or defined by a module not included in the server configuration

⚠️ 注意:包内站点配置用 Header set Content-Security-Policy 保护管理页面,需启用 mod_headers 后配置才合法——这是 apt 路线必踩的一脚坑:

a2enmod headers
apache2ctl configtest && systemctl reload apache2
Enabling module headers.
To activate the new configuration, you need to run:
  systemctl restart apache2
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 10.89.66.3. Set the 'ServerName' directive globally to suppress this message
Syntax OK

4.8 验证

curl -sI http://davical.example.com/davical/index.php | head -6
HTTP/1.1 200 OK
Date: Sun, 01 Nov 2026 12:34:17 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Security-Policy: default-src 'none'; img-src 'self' data:; media-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' data:; font-src 'self' data:; object-src 'self'; base-uri 'self'; connect-src 'self'; form-action 'self' sis.redsys.es; frame-ancestors 'self'
Content-Type: text/html; charset="utf-8"
curl -s -X OPTIONS -u admin:q1w2e3r4 http://davical.example.com/davical/caldav.php/admin/ -o /dev/null -D - | grep -iE "^HTTP|X-DAViCal|DAV:"
HTTP/1.1 200 OK
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
X-DAViCal-Version: DAViCal/1.1.12; DB/1.3.5

💡 说明:1.1.12 的 CSP 头里带有 form-action 'self' sis.redsys.es,2023-10 的修复("Remove accidental inclusion of sis.redsys.es in CSP")已包含在 1.1.13 中,实测方案 B 的 CSP 无此外部域。

日历读写链路验证(MKCALENDAR 首次因命名空间写错返回 406,详见"常见问题"Q7;修正后全链 201):

curl -s -u admin:q1w2e3r4 -X MKCALENDAR http://davical.example.com/davical/caldav.php/admin/work \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="utf-8"?><c:mkcalendar xmlns:D="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><D:set><D:prop><D:displayname>Work</D:displayname></D:prop></D:set></c:mkcalendar>' \
  -o /dev/null -w "MKCALENDAR=%{http_code}\n"
MKCALENDAR=201
curl -s -u admin:q1w2e3r4 -X PUT http://davical.example.com/davical/caldav.php/admin/work/e1.ics \
  -H "Content-Type: text/calendar" \
  --data-binary 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//test//EN
BEGIN:VEVENT
UID:e1@davical
DTSTAMP:20261101T120000Z
DTSTART:20261120T090000Z
DTEND:20261120T100000Z
SUMMARY:apt method check
END:VEVENT
END:VCALENDAR' \
  -o /dev/null -w "PUT=%{http_code}\n"
curl -s -u admin:q1w2e3r4 http://davical.example.com/davical/caldav.php/admin/work/e1.ics | grep -E "SUMMARY|UID"
PUT=201
UID:e1@davical
SUMMARY:apt method check

五、方案 B:官方 tar.xz 源码包安装(1.1.13,主线)

5.1 方案说明

官网发布 davical_1.1.13.orig.tar.xz 与配套库 awl_0.65.orig.tar.xz(均为无顶层目录的裸包,需解压到指定目录)。代码为纯 PHP,无需编译;"编译源码"在此即"解包 + 数据库 schema 初始化 + Apache/PHP 挂接"。适合需要最新版本、多域名实例或不便使用发行版源的环境。

5.2 官方下载与校验

wget https://www.davical.org/downloads/davical_1.1.13.orig.tar.xz
wget https://www.davical.org/downloads/davical_1.1.13.orig.tar.xz.sha512
wget https://www.davical.org/downloads/awl_0.65.orig.tar.xz
sha512sum -c davical_1.1.13.orig.tar.xz.sha512
f221647a15b565743b501b00377a202ed4a8f08110b960314e252c52922edf6893f96de5b8c27b081f366a9d9854feaa61c662188b3d57dbfba5f893ed410312  ./davical_1.1.13.orig.tar.xz

AWL 包校验值(官网提供 .sha512):

681e4de386af95d294c604e47a18d20b41f9b451cecafa4dfe6e668e2f6067df23215ef364789e038ad6070db832d98fef8f51c4a618eec5f2720767db302c35  awl_0.65.orig.tar.xz

5.3 安装运行依赖

apt-get install -y apache2 libapache2-mod-php8.3 php8.3-pgsql php8.3-xml \
  postgresql-16 postgresql-client perl libdbi-perl libdbd-pg-perl libyaml-perl pwgen

安装后核对(实测回显):

dpkg -l | grep -E "libapache2-mod-php8.3 |php8.3-pgsql|php8.3-xml|postgresql-16|libdbd-pg-perl|libdbi-perl|libyaml-perl|pwgen" | awk '{print $2, $3}'
libapache2-mod-php8.3 8.3.6-0ubuntu0.24.04.13
libdbd-pg-perl 3.18.0-1build3
libdbi-perl:amd64 1.643-4ubuntu0.4
libyaml-libyaml-perl 0.89+ds-1ubuntu0.24.04.1
libyaml-perl 1.31-1ubuntu0.24.04.1
php8.3-pgsql 8.3.6-0ubuntu0.24.04.13
php8.3-xml 8.3.6-0ubuntu0.24.04.13
postgresql-16 16.15-0ubuntu0.24.04.1
pwgen 2.08-2build2

⚠️ 注意:php8.3-xml 必须安装。缺它时 MKCALENDAR 返回 500,错误日志为 Call to undefined function xml_parser_create_ns()(实测见"常见问题"Q4)。libdbi-perl/libdbd-pg-perl/libyaml-perl 是 update-davical-database(Perl 脚本)的硬依赖。

5.4 解压部署

官方 INSTALL 建议解到 /usr/share 预期位置。因 tar 包无顶层目录,先建目录再解压:

mkdir -p /usr/share/davical /usr/share/awl
tar -xJf davical_1.1.13.orig.tar.xz -C /usr/share/davical
tar -xJf awl_0.65.orig.tar.xz -C /usr/share/awl
cat /usr/share/davical/VERSION /usr/share/awl/VERSION
1.1.13
0.65

5.5 PostgreSQL 访问控制与角色

与 4.4 节完全一致:在 /etc/postgresql/16/main/pg_hba.conf 的通用 local all all peer 行之前插入两条 md5 规则(实测同样落在 123、124 行),预建 davical_dba/davical_app 角色(口令 q1w2e3r4),systemctl reload postgresql。

⚠️ 注意:若 md5 规则误追加在文件末尾,会先匹配到通用 peer 行,初始化报 connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: Peer authentication failed for user "davical_dba"(实测复现见"常见问题"Q1)。

5.6 数据库 schema 初始化

su postgres -c "PGPASSWORD=q1w2e3r4 /usr/share/davical/dba/create-database.sh"

实测回显(1.1.13 版脚本在初始化后会自动把 schema 从 1.3.5 补到 1.3.6):

Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.
The database is version 16.0 currently at revision 1.3.5.
Applying patch 1.3.6.sql ... succeeded.
Successfully applied 1 patches.
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
RRULE functions updated.
Database permissions updated.
NOTE
====
*  The password for the 'admin' user has been set to 'Ap7peo^r'

Thanks for trying DAViCal! Check the configuration in /etc/davical/config.php.
For help, look at our website and wiki, or visit #davical on irc.oftc.net.

随后按演示规范重置 admin 口令:

su postgres -c "psql davical -c \"UPDATE usr SET password = '**q1w2e3r4' WHERE username = 'admin';\""
UPDATE 1

create-database.sh 内部已调用 update-davical-database 打补丁并设置应用权限;schema 演进记录可直接查证:

su postgres -c "psql davical -c \"select schema_major, schema_minor, schema_patch, schema_name from awl_db_revision order by schema_id;\""
 schema_major | schema_minor | schema_patch | schema_name 
--------------+--------------+--------------+-------------
            1 |            1 |            0 | Dawn
            1 |            3 |            5 | Maj
            1 |            3 |            6 | Czerwiec
(3 rows)

5.7 应用配置(按域名)

多实例配置文件命名为 /etc/davical/<SERVER_NAME>-conf.php。创建 /etc/davical/davical.example.com-conf.php(实测生效内容):

<?php
// DAViCal configuration for davical.example.com
$c->pg_connect[] = "dbname=davical user=davical_app password=q1w2e3r4";
$c->domain_name = "davical.example.com";
$c->system_name = "DAViCal CalDAV Server";
$c->admin_email = "admin@davical.example.com";

逐项说明:pg_connect 为必填项(应用数据库连接串,可多条做故障转移);domain_name/system_name/admin_email 分别决定 URL 构造域、认证 realm 与管理员信箱。配置文件属主必须让 www-data 可读(640 root:www-data),否则报数据库连接错误(见"常见问题"Q5)。

管理界面执行数据库管理操作所需的凭据放 /etc/davical/administration.yml(实测内容):

---
admin_db_user: davical_dba
admin_db_pass: q1w2e3r4
app_db_user: davical_app

⚠️ 注意:另需一份 /etc/davical/config.php 兜底(内容同上),供 SERVER_NAME 不匹配域名的场景(如 CLI 脚本以 localhost 运行)加载,否则命令行维护脚本会输出 "configuration missing" 页面(见第九章 C 组脚本说明)。

5.8 Apache 与 PHP 挂接

复用官方源码包 config/apache-davical.conf 作为 Apache 配置片段(与 apt 包内站点文件同源):

cp /usr/share/davical/config/apache-davical.conf /etc/apache2/conf-available/davical.conf
a2enconf davical
a2enmod rewrite headers

配置核心为 Alias /davical /usr/share/davical/htdocs 与 Directory 授权、CSP 头、.well-known 重写(实测文件首行起:# DAViCal Calendar Server / Alias /davical /usr/share/davical/htdocs)。

PHP 需能找到 DAViCal 与 AWL 的库目录,创建 /etc/php/8.3/apache2/conf.d/99-davical.ini(实测内容):

include_path = ".:/usr/share/php:/usr/share/davical/inc:/usr/share/awl/inc"
systemctl restart apache2

5.9 验证

curl -sI http://davical.example.com/davical/index.php | head -6
HTTP/1.1 200 OK
Date: Sun, 01 Nov 2026 12:35:19 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Security-Policy: default-src 'none'; img-src 'self' data:; media-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' data:; font-src 'self' data:; object-src 'self'; base-uri 'self'; connect-src 'self'; form-action 'self'; frame-ancestors 'self'
Content-Type: text/html; charset=UTF-8
curl -s -X OPTIONS -u admin:q1w2e3r4 http://davical.example.com/davical/caldav.php/admin/ -D - -o /dev/null | head -10
HTTP/1.1 200 OK
Date: Sun, 01 Nov 2026 12:36:20 GMT
Server: Apache/2.4.58 (Ubuntu)
Vary: Accept-Encoding
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Allow: OPTIONS, PROPFIND, REPORT, DELETE, LOCK, UNLOCK, MOVE, GET, HEAD, MKCOL, MKCALENDAR, PROPPATCH, BIND, ACL
X-DAViCal-Version: DAViCal/1.1.13; DB/1.3.6
Content-Length: 0
Content-Type: text/plain; charset="utf-8"

创建测试用户 q1:浏览器打开 http://davical.example.com/davical/,以 admin/q1w2e3r4 登录管理界面,进入 Users → New User,用户名 q1、口令 q1w2e3r4、邮箱 q1@davical.example.com,保存。新用户会自动获得默认日历 calendar 与通讯录 addresses(collections_always_exist 默认 true)。

CalDAV 全链路实测(建集合 → 存事件 → 建通讯录集合 → 存联系人):

curl -s -u q1:q1w2e3r4 -X MKCALENDAR http://davical.example.com/davical/caldav.php/q1/verify-calendar/ \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="utf-8"?><c:mkcalendar xmlns:D="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><D:set><D:prop><D:displayname>Verify Calendar</D:displayname></D:prop></D:set></c:mkcalendar>' \
  -o /dev/null -w "MKCALENDAR=%{http_code}\n"
curl -s -u q1:q1w2e3r4 -X PUT http://davical.example.com/davical/caldav.php/q1/verify-calendar/evt-001.ics \
  -H "Content-Type: text/calendar" \
  --data-binary 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//quzuo.net//davical-test//EN
BEGIN:VEVENT
UID:evt-20261101-002@davical.example.com
DTSTAMP:20261101T124000Z
DTSTART:20261115T090000Z
DTEND:20261115T100000Z
SUMMARY:DAViCal Install Verify
END:VEVENT
END:VCALENDAR' \
  -o /dev/null -w "PUT=%{http_code}\n"
curl -s -u q1:q1w2e3r4 -X MKCOL http://davical.example.com/davical/caldav.php/q1/verify-contacts/ \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="utf-8"?><d:mkcol xmlns:d="DAV:" xmlns:card="urn:ietf:params:xml:ns:carddav"><d:set><d:prop><d:resourcetype><d:collection/><card:addressbook/></d:resourcetype><d:displayname>Verify Contacts</d:displayname></d:prop></d:set></d:mkcol>' \
  -o /dev/null -w "MKCOL=%{http_code}\n"
curl -s -u q1:q1w2e3r4 -X PUT http://davical.example.com/davical/caldav.php/q1/verify-contacts/alice.vcf \
  -H "Content-Type: text/vcard" \
  --data-binary 'BEGIN:VCARD
VERSION:3.0
UID:alice-001
FN:Alice Zhang
EMAIL:alice@example.com
END:VCARD' \
  -o /dev/null -w "PUT_VCF=%{http_code}\n"
MKCALENDAR=201
PUT=201
MKCOL=201
PUT_VCF=201

PROPFIND 列举用户资源(207 Multi-Status):

curl -s -u q1:q1w2e3r4 -X PROPFIND http://davical.example.com/davical/caldav.php/q1/ -H "Depth: 1" \
  -d '<?xml version="1.0"?><d:propfind xmlns:d="DAV:"><d:prop><d:displayname/><d:resourcetype/></d:prop></d:propfind>'

输出节选(实测首尾):

<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:C1="http://calendarserver.org/ns/">
 <response>
  <href>/davical/caldav.php/q1/</href>
  <propstat>
   <prop>
    <displayname>Test User Q1</displayname>
    <resourcetype>
     <collection/>
     <principal/>
    </resourcetype>
...
  <href>/davical/caldav.php/q1/verify-calendar/</href>
   <status>HTTP/1.1 200 OK</status>

REPORT 时间查询命中事件:

curl -s -u q1:q1w2e3r4 -X REPORT http://davical.example.com/davical/caldav.php/q1/verify-calendar/ \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0"?><c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:prop><d:getetag/><c:calendar-data/></d:prop><c:filter><c:comp-filter name="VCALENDAR"><c:comp-filter name="VEVENT"/></c:comp-filter></c:filter></c:calendar-query>' \
  -w "\n[REPORT=%{http_code}]\n"

输出节选(实测首尾):

<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
 <response>
  <href>/davical/caldav.php/q1/verify-calendar/evt-001.ics</href>
  <propstat>
   <prop>
    <getetag>"2adc662fe732733e324774cfd01d2792"</getetag>
    <C:calendar-data>BEGIN:VCALENDAR
...
SUMMARY:DAViCal Install Verify
...
   <status>HTTP/1.1 200 OK</status>
[REPORT=207]

空闲忙碌查询(freebusy.php 为 GET 参数式,非 POST,实测见"常见问题"Q8):

curl -s -u q1:q1w2e3r4 "http://davical.example.com/davical/freebusy.php/q1?start=2026-11-15T00:00:00&end=2026-11-16T00:00:00"
BEGIN:VCALENDAR
PRODID:-//davical.org//NONSGML AWL Calendar//EN
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VFREEBUSY
DTSTAMP:20261101T124010Z
DTSTART:20261115T000000Z
DTEND:20261116T000000Z
FREEBUSY:20261115T090000Z/20261115T100000Z
END:VFREEBUSY
END:VCALENDAR

通讯录回读(服务端自动补 REV 修订时间戳):

curl -s -u q1:q1w2e3r4 http://davical.example.com/davical/caldav.php/q1/verify-contacts/alice.vcf
BEGIN:VCARD
VERSION:3.0
UID:alice-001
FN:Alice Zhang
EMAIL:alice@example.com
REV:20261101T123925Z
END:VCARD

数据库侧核验(集合确实落库、schema 表数量 34):

su postgres -c "psql davical -c \"select c.dav_name, c.dav_displayname, c.is_calendar, c.is_addressbook from collection c join usr u on u.user_no=c.user_no where u.username='q1' and c.dav_name like '/q1/verify%' order by c.dav_name;\""
su postgres -c "psql davical -Atc \"select count(*) from pg_tables where schemaname='public';\""
       dav_name       | dav_displayname | is_calendar | is_addressbook 
----------------------+-----------------+-------------+----------------
 /q1/verify-calendar/ | Verify Calendar | t           | f
 /q1/verify-contacts/ | Verify Contacts | f           | t
(2 rows)

34

六、方案对比与选择建议

维度方案 A(apt 1.1.12-2)方案 B(tar.xz 1.1.13)
版本/schema1.1.12 / DB 1.3.51.1.13 / DB 1.3.6
依赖处理全自动解析手工 apt 装 11 个包
代码位置包管理 /usr/share/davical自解压 /usr/share/davical
Apache 配置预置 sites-available/davical.conf自取 config/apache-davical.conf 放 conf-available
PHP include_path包预置需自建 99-davical.ini
配置文件预置 /etc/davical/config.php自建 <域名>-conf.php + config.php 兜底
升级apt 随发行版换包重解压 + update-davical-database
已知额外坑mod_headers 未启用致 configtest 失败php8.3-xml 缺失致 MKCALENDAR 500

选择建议:追求最新特性(PHP 8.2/8.3 修复、CSP 修正、iSchedule 改进)或多域名实例 → 方案 B;只求快速可用、接受一个发行版滞后 → 方案 A。两方案的数据库初始化(pg_hba、角色、create-database.sh)完全同构,可互为排障参照。

七、配置说明

7.1 关键文件一览

路径用途来源
/etc/davical/<SERVER_NAME>-conf.php按域名的应用配置(DB 连接、域名、管理员邮箱等)自建(方案 B)/ 模板参考
/etc/davical/config.php域名不匹配时的兜底配置;CLI 维护脚本依赖方案 A 预置 / 方案 B 自建
/etc/davical/administration.yml管理界面执行 DDL 的 DB 凭据自建
/etc/postgresql/16/main/pg_hba.confdavical_dba/davical_app 的 md5 规则手工插入
/etc/apache2/conf-available/davical.confAlias + CSP + .well-known 重写包预置 / config/apache-davical.conf
/etc/php/8.3/apache2/conf.d/99-davical.iniPHP include_path自建(方案 B)
/usr/share/davical/dba/数据库初始化与升级脚本软件自带
/usr/share/davical/scripts/告警刷新、导出导入等维护脚本软件自带

7.2 配置加载机制

AWL 框架按请求的 SERVER_NAME 选择 /etc/davical/<域名>-conf.php,找不到则回落 config.php。这带来两个实践要点:① 生产多域部署时每个域名一份 conf.php;② 命令行维护脚本(如 refresh-alarms.php)内部硬编码 SERVER_NAME=localhost,必须存在 config.php 兜底才能静默运行——这正是方案 B 也要额外复制一份 config.php 的原因。

7.3 升级

方案 B 升级(以 1.1.12 → 1.1.13 为例):备份数据库(pg_dump davical)→ 新 tar 包覆盖解压到 /usr/share/davical(AWL 同步升级)→ 执行权限与补丁:

PGPASSWORD=q1w2e3r4 /usr/share/davical/dba/update-davical-database \
  --dbuser davical_dba --dbname davical --appuser davical_app --owner davical_dba
The database is version 16.0 currently at revision 1.3.6.
No patches were applied.
Supported locales updated.
Updated view: dav_principal.sql applied.
CalDAV functions updated.
WARNING:  no privileges were granted for "dav_id_seq"
WARNING:  no privileges were granted for "dav_id_seq"
RRULE functions updated.
Database permissions updated.

No patches were applied 表示 schema 已到 1.3.6 无需补丁;两条 dav_id_seq WARNING 为序列权限重复授予的提示,不影响功能(实测可忽略)。方案 A 升级随 apt-get install -y davical 由发行版推送。

7.4 降级与多版本管理

dba/patches/ 只前向打补丁(1.1.11.sql、1.1.12.sql……),不支持 schema 回退:降级代码版本前必须恢复旧版数据库备份,否则新版 schema(如 1.3.6)无法被旧代码使用。多版本共存无官方支持,同一数据库只能对应一个代码版本;如需并行验证,用独立域名 + 独立数据库实例(<域名>-conf.php 各连各库)。

八、常见问题(排障实录)

8.1 Q1:create-database.sh 报 Peer authentication failed

connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  Peer authentication failed for user "davical_dba"

定位:pg_hba.conf 的 md5 规则位置在通用 local all all peer 之后,PostgreSQL 取首条匹配规则,peer 先行生效。解决:把两条 local davical ... md5 移到通用行之前并 systemctl reload postgresql(实测移前后对比见 5.5 节)。

8.2 Q2:update-davical-database 报 fe_sendauth: no password supplied

见 4.5 节。脚本以 davical_dba 身份连接但未提供口令。用 PGPASSWORD 环境变量或 ~postgres/.pgpass(600 权限,*:*:davical:davical_dba:q1w2e3r4)解决;--help 亦明确推荐 .pgpass 方式。

8.3 Q3:HTTP 500 且日志报 permission denied for table awl_db_revision

现象:Web 界面 500,Apache 错误日志含 ERROR: permission denied for table awl_db_revision。定位:schema 以 postgres 身份加载后表属主未归 davical_dba、应用角色未获 DML 授权。解决:补跑一次带 --owner 的权限设置(实测回显见 7.3 节):

/usr/share/davical/dba/update-davical-database --dbuser postgres --dbname davical --appuser davical_app --owner davical_dba

8.4 Q4:MKCALENDAR 返回 500,报 xml_parser_create_ns 未定义

PHP Fatal error:  Call to undefined function xml_parser_create_ns() ...

定位:DAViCal 解析请求 XML 依赖 PHP 的 expat 扩展(xml 模块),Ubuntu 上由 php8.3-xml 提供,基础 PHP 包不含。解决:apt-get install -y php8.3-xml && systemctl restart apache2,MKCALENDAR 即恢复 201(实测)。

8.5 Q5:页面显示 "Database Error: Could not connect to database"

响应体实测:

<html><head><title>Database Connection Failure</title></head><body>
  <h1>Database Error</h1>
  <h3>Could not connect to database</h3>
  </body></html>

Apache 错误日志伴随:

PHP Warning:  Undefined variable $connection_strings in /usr/share/awl/inc/AwlQuery.php on line 55

定位:$connection_strings 未定义说明配置文件根本没被加载成功——常见原因是按域名找不到 <SERVER_NAME>-conf.php 且 config.php 不存在,或 config.php 属主为 root:root 而 www-data 无读权限。解决:确认 /etc/davical/ 下配置存在且 chown root:www-data、chmod 640(实测修复即恢复 200/OPTIONS 正常)。

8.6 Q6:a2ensite 后 apache2ctl configtest 报 Invalid command 'Header'

实测报错 AH00526: Syntax error on line 55 ... Invalid command 'Header'。原因:配置用 Header 指令但未启用 mod_headers。解决:a2enmod headers 后 Syntax OK。

8.7 Q7:MKCALENDAR 返回 406

实测响应体:

The XML is not a "DAV::mkcol" or "urn:ietf:params:xml:ns:caldav:mkcalendar" document (DAV::mkcalendar)

原因:请求体根元素写成了 DAV: 命名空间的 mkcalendar。CalDAV 的 MKCALENDAR 根元素必须用 urn:ietf:params:xml:ns:caldav 命名空间(正确载荷实测 201,见 4.8 节)。

8.8 Q8:freebusy.php 用 POST 无输出

实测:freebusy.php/<user> 是 GET 参数式接口(?start=...&end=...),POST 无响应体。正确用法见 5.9 节,返回标准 VFREEBUSY 日历。

九、卸载与清理

方案 A(apt):

a2dissite davical.conf
apt-get purge -y davical libawl-php
su postgres -c "dropdb davical; psql -c \"DROP ROLE davical_app;\" -c \"DROP ROLE davical_dba;\""
rm -rf /etc/davical

方案 B(tar.xz):

a2disconf davical
rm -f /etc/php/8.3/apache2/conf.d/99-davical.ini
rm -rf /usr/share/davical /usr/share/awl /etc/davical
su postgres -c "dropdb davical; psql -c \"DROP ROLE davical_app;\" -c \"DROP ROLE davical_dba;\""
systemctl reload apache2

验证清理彻底:curl -s -o /dev/null -w "%{http_code}\n" http://davical.example.com/davical/index.php → 404;su postgres -c "psql -lqt | cut -d'|' -f1 | grep -w davical" 无输出。

十、参考链接

Ubuntu 24.04 安装 DAViCal 1.1.13 指南 - Quzuo