--- /dev/null
+# 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))
--- /dev/null
+#!/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"
+}