radicale3: new package
authorJosef Schlehofer <redacted>
Mon, 29 Dec 2025 09:04:15 +0000 (10:04 +0100)
committerJosef Schlehofer <redacted>
Fri, 9 Jan 2026 16:09:08 +0000 (17:09 +0100)
Radicale is a small but powerful CalDAV (calendars, to-do lists) and
CardDAV (contacts) server.
This package provides the latest 3.x series, which succeeds radicale2.

This is replacament for recently dropped radicale2 and radicale1.

Signed-off-by: Josef Schlehofer <redacted>
net/radicale3/Makefile [new file with mode: 0644]
net/radicale3/files/radicale3.config [new file with mode: 0644]
net/radicale3/files/radicale3.init [new file with mode: 0755]

diff --git a/net/radicale3/Makefile b/net/radicale3/Makefile
new file mode 100644 (file)
index 0000000..1db52b1
--- /dev/null
@@ -0,0 +1,54 @@
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=radicale3
+PKG_VERSION:=3.5.10
+PKG_RELEASE:=1
+
+PKG_LICENSE:=GPL-3.0-or-later
+PKG_LICENSE_FILES:=COPYING.md
+PKG_CPE_ID:=cpe:/a:radicale:radicale
+
+PYPI_NAME:=Radicale
+PYPI_SOURCE_NAME:=radicale
+PKG_HASH:=569f2a8cf990faf9bb25b7442f36ddd439526b95db81d8878952d77836ab3d4c
+
+include ../../lang/python/pypi.mk
+include $(INCLUDE_DIR)/package.mk
+include ../../lang/python/python3-package.mk
+
+define Package/radicale3
+  SECTION:=net
+  CATEGORY:=Network
+  SUBMENU:=Web Servers/Proxies
+  URL:=https://radicale.org/
+  TITLE:=Radicale 3.x CalDAV/CardDAV server
+  USERID:=radicale3=226:radicale3=226
+  DEPENDS:=+python3 +python3-dateutil +python3-vobject +python3-setuptools +python3-defusedxml +python3-passlib +python3-requests +python3-pika
+  PROVIDES:=radicale radicale2
+endef
+
+define Package/radicale3/description
+  The Radicale Project is a CalDAV (calendar) and CardDAV (contact) server. It aims to be a light solution, easy to use, easy to install, easy to configure. As a consequence, it requires few software dependencies and is pre-configured to work out-of-the-box.
+  The Radicale Project runs on most of the UNIX-like platforms (Linux, BSD, MacOS X) and Windows. It is known to work with Evolution, Lightning, iPhone and Android clients. It is free and open-source software, released under GPL version 3.
+
+  This package contains the python files.
+endef
+
+define Package/radicale3/conffiles
+/etc/config/radicale3
+endef
+
+define Py3Package/radicale3/install
+       $(INSTALL_DIR) $(1)/usr/bin
+       $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/radicale $(1)/usr/bin/radicale3
+       $(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d
+       $(INSTALL_CONF) ./files/radicale3.config $(1)/etc/config/radicale3
+       $(INSTALL_BIN) ./files/radicale3.init $(1)/etc/init.d/radicale3
+endef
+
+$(eval $(call Py3Package,radicale3))
+$(eval $(call BuildPackage,radicale3))
+$(eval $(call BuildPackage,radicale3-src))
diff --git a/net/radicale3/files/radicale3.config b/net/radicale3/files/radicale3.config
new file mode 100644 (file)
index 0000000..5107219
--- /dev/null
@@ -0,0 +1,7 @@
+#config section server
+       # list host 127.0.0.1:5232
+       # list host ::1:5232
+
+#config user
+       #option name user1
+       #option password password1
diff --git a/net/radicale3/files/radicale3.init b/net/radicale3/files/radicale3.init
new file mode 100755 (executable)
index 0000000..1afc25f
--- /dev/null
@@ -0,0 +1,221 @@
+#!/bin/sh /etc/rc.common
+
+START=99
+USE_PROCD=1
+
+PROG=/usr/bin/radicale3
+CFGDIR=/var/etc/radicale3
+SYSCFG=$CFGDIR/config
+USRCFG=$CFGDIR/users
+DATADIR="/srv/radicale3/data"
+
+conf_line() {
+       local cfgfile="$1"
+       local option="$2"
+       local value="$3"
+
+       if [ -n "$value" ]; then
+               echo "$option = $value" >> "$cfgfile"
+       fi
+}
+
+conf_getline() {
+       local cfg="$1"
+       local cfgfile="$2"
+       local option="$3"
+       local defval="$4"
+       local boolean="$5"
+       # Note: $value is used by the caller, so we do not declare it local here.
+       unset value
+
+       if [ "$boolean" != "1" ]; then
+               config_get value "$cfg" "$option" "$defval"
+               conf_line "$cfgfile" "$option" "$value"
+       else
+               config_get_bool value "$cfg" "$option" "$defval"
+               # config_get_bool returns 0/1 in value
+               if [ -n "$value" ]; then
+                       if [ "$value" -eq 1 ]; then
+                               conf_line "$cfgfile" "$option" "True"
+                       else
+                               conf_line "$cfgfile" "$option" "False"
+                       fi
+               fi
+       fi
+}
+
+build_hosts_line() {
+       local val="$1"
+       append hostlist "$val" ", "
+}
+
+conf_section() {
+       local cfg="$1"
+       local cfgfile="$2"
+       local hostlist=""
+       local value
+
+       echo "[$cfg]" >> "$cfgfile"
+
+       case $cfg in
+       server)
+               config_list_foreach "$cfg" host build_hosts_line
+               conf_line "$cfgfile" hosts "$hostlist"
+               conf_getline "$cfg" "$cfgfile" max_connections
+               conf_getline "$cfg" "$cfgfile" max_content_length
+               conf_getline "$cfg" "$cfgfile" timeout
+
+               conf_getline "$cfg" "$cfgfile" ssl 0 1
+               if [ "$value" -eq 1 ]; then
+                       conf_getline "$cfg" "$cfgfile" certificate
+                       conf_getline "$cfg" "$cfgfile" key
+                       conf_getline "$cfg" "$cfgfile" certificate_authority
+                       conf_getline "$cfg" "$cfgfile" protocol
+                       conf_getline "$cfg" "$cfgfile" ciphers
+               fi
+
+               conf_getline "$cfg" "$cfgfile" dns_lookup 1 1
+               conf_getline "$cfg" "$cfgfile" realm
+               ;;
+       encoding)
+               conf_getline "$cfg" "$cfgfile" request
+               conf_getline "$cfg" "$cfgfile" stock
+               ;;
+       auth)
+               conf_getline "$cfg" "$cfgfile" "type" htpasswd
+               if [ "$value" = "htpasswd" ]; then
+                       conf_getline "$cfg" "$cfgfile" htpasswd_filename "$USRCFG"
+                       conf_getline "$cfg" "$cfgfile" htpasswd_encryption plain
+               fi
+
+               conf_getline "$cfg" "$cfgfile" delay
+               ;;
+       rights)
+               conf_getline "$cfg" "$cfgfile" "type"
+               if [ "$value" = "from_file" ]; then
+                       conf_getline "$cfg" "$cfgfile" "file"
+               fi
+               ;;
+       storage)
+               conf_getline "$cfg" "$cfgfile" filesystem_folder "$DATADIR"
+               # Update global DATADIR if user specified a custom one, so we can mkdir it later
+               if [ -n "$value" ]; then
+                       DATADIR="$value"
+               fi
+               conf_getline "$cfg" "$cfgfile" filesystem_locking 1 1
+               conf_getline "$cfg" "$cfgfile" max_sync_token_age
+               conf_getline "$cfg" "$cfgfile" filesystem_close_lock_file 0 1
+               conf_getline "$cfg" "$cfgfile" hook
+               ;;
+       web)
+               conf_getline "$cfg" "$cfgfile" "type"
+               ;;
+       logging)
+               conf_getline "$cfg" "$cfgfile" config
+               conf_getline "$cfg" "$cfgfile" debug 0 1
+               conf_getline "$cfg" "$cfgfile" full_environment 0 1
+               conf_getline "$cfg" "$cfgfile" mask_passwords 1 1
+               ;;
+       headers)
+               config_get cors "$cfg" cors
+               if [ -n "$cors" ]; then
+                       echo "Access-Control-Allow-Origin = $cors" >> "$cfgfile"
+               fi
+               ;;
+       esac
+
+       echo "" >> "$cfgfile"
+}
+
+add_missing_sections() {
+       local cfgfile="$1"
+
+       for section in server encoding auth rights storage web logging headers; do
+               if ! grep -q "\[$section\]" "$cfgfile"; then
+                       echo "[$section]" >> "$cfgfile"
+                       case $section in
+                       server)
+                               echo "hosts = 0.0.0.0:5232, [::]:5232" >> "$cfgfile"
+                               ;;
+                       auth)
+                               echo "type = htpasswd" >> "$cfgfile"
+                               echo "htpasswd_filename = $USRCFG" >> "$cfgfile"
+                               echo "htpasswd_encryption = plain" >> "$cfgfile"
+                               ;;
+                       storage)
+                               echo "filesystem_folder = $DATADIR" >> "$cfgfile"
+                               ;;
+                       esac
+                       echo "" >> "$cfgfile"
+               fi
+       done
+}
+
+add_user() {
+       local cfg="$1"
+       local tmpfile="$2"
+       local name password
+
+       config_get name "$cfg" name
+       config_get password "$cfg" password
+
+       [ -n "$name" ] && echo "$name:$password" >> "$tmpfile"
+}
+
+build_users() {
+       local tmpfile="$1"
+       config_foreach add_user user "$tmpfile"
+}
+
+build_config() {
+       local tmpconf=$(mktemp)
+       local tmpusers=$(mktemp)
+
+       chmod 0640 "$tmpconf" "$tmpusers"
+
+       config_load radicale3
+       config_foreach conf_section section "$tmpconf"
+       add_missing_sections "$tmpconf"
+
+       build_users "$tmpusers"
+
+       # Apply permissions
+       mkdir -p "$CFGDIR"
+       chmod 0750 "$CFGDIR"
+       chgrp radicale3 "$CFGDIR"
+
+       cat "$tmpconf" > "$SYSCFG"
+       cat "$tmpusers" > "$USRCFG"
+       chmod 0640 "$SYSCFG" "$USRCFG"
+       chgrp radicale3 "$SYSCFG" "$USRCFG"
+
+       rm -f "$tmpconf" "$tmpusers"
+}
+
+start_service() {
+       # If custom config (/etc/radicale3/config) absent, build it from UCI
+       if [ ! -f /etc/radicale3/config ]; then
+               build_config
+               CFG_FILE="$SYSCFG"
+
+               # Ensure data dir exists (DATADIR might have been updated by build_config)
+               mkdir -p "$DATADIR"
+               chown radicale3:radicale3 "$DATADIR"
+       else
+               CFG_FILE="/etc/radicale3/config"
+               # We assume user handles directory creation if using manual config,
+               # but we could attempt it if we parsed it. simpler to leave it for now.
+       fi
+
+       procd_open_instance
+       procd_set_param command "$PROG" --config "$CFG_FILE"
+       procd_set_param user radicale3
+       procd_set_param respawn
+       procd_set_param stdout 1
+       procd_set_param stderr 1
+       procd_close_instance
+}
+
+service_triggers() {
+       procd_add_reload_trigger "radicale3"
+}
git clone https://git.99rst.org/PROJECT