rationale Link to heading
- hosting a lightweight caldav compatible server
- podman w/ selinux enabled
- caddy as a reverse proxy to handle dns/certs/multisite
- several services on same domain.tld
- being gnome/davx5 caldav compatible
- share calendars with other users
prerequesites Link to heading
- vps/cloud instance
- rocky/alma/fedora server (other gnu/linux works)
- non root/sudo/wheel user for podman
*domain.tld
pointing to host ip
todo Link to heading
- setup mail/signal/mattermost for reminders
- sending reminders through cardav contacts
- setup webdav in caddy for file sharing
- implement simple protection corazawaf or reaction or anubis or ratelimit
- having several scenarri of caldav/webdav sharing
setup firewall Link to heading
assuming you already setup your firewall for ssh/other services
sudo firewall-cmd --permanent --add-port=80/tcp --add-port=443/tcp
sudo firewall-cmd --complete-reload
install podman Link to heading
sudo dnf install epel-release -y
sudo dnf install -y podman podman-compose
check this file for search registries
/etc/containers/registries.conf
unqualified-search-registries = ["registry.access.redhat.com", "registry.redhat.io", "docker.io"]
create user without sudo/wheel rights
sudo useradd -m poduser
sudo passwd poduser
sudo loginctl enable-linger poduser
login with poduser through this command otherwise you can have problems
su --login poduser
create directory structure
mkdir -p web/{caldav.domain.tld,logs,radicale}
cd web
dockerbuild Link to heading
as latest version of caddy is not the ‘real’ latest we prefer specifying version
curl -s https://api.github.com/repos/caddyserver/caddy/releases/latest | jq -r .tag_name
2.10.0
caddy need to be build with dns plugins for tls/dns challenge or whatsoever
i use ovh/ionos and caddy-ratelimit
~/web/Dockerfile
ARG CADDY_VERSION=2.10.0
FROM caddy:builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/ionos \
--with github.com/caddy-dns/ovh \
--with github.com/mholt/caddy-ratelimit
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
before building caddy check if there’s no other image
podman images
if so you can remove it
podman rmi caddy.vps1
build caddy
podman build --no-cache -f Dockerfile -t caddy.vps1 .
- if you need to build your image on another machine it’s quite simple
podman compose Link to heading
- to be docker compatible just take off the
Z
z
occurences.
~/web/podman-compose.yml
---
version: "3.9"
services:
caddy:
image: caddy.vps1
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro,Z
- caddy_data:/data
- caddy_config:/config
- ./logs:/srv/data:z
environment:
- ACME_AGREE=true
restart: unless-stopped
radicale:
image: tomsquest/docker-radicale
container_name: radicale
ports:
- "0.0.0.0:5232:5232"
init: true
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- SETUID
- SETGID
- CHOWN
- KILL
deploy:
resources:
limits:
memory: 256M
pids: 50
restart: unless-stopped
environment:
- TAKE_FILE_OWNERSHIP=true
- TZ=Europe/Paris
volumes:
- "./calendar.domain.tld:/data:rw,Z"
- "./radicale:/config:ro,Z"
volumes:
caddy_data:
caddy_config:
calendar.domain.tld:
caddy configuration Link to heading
~/web/Caddyfile
calendar.domain.tld {
tls {
dns ovh {
endpoint https://eu.api.ovh.com/v1
application_key REDACTED
application_secret REDACTED
consumer_key REDACTED
}
handle /radicale/* {
reverse_proxy radicale:5232 {
header_up X-Script-Name /radicale/
}
}
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "no-referrer"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
Cache-Control "no-store"
}
log {
format json
output file data/calendar.access.log {
roll_local_time
roll_keep 12
roll_keep_for 365d
}
}
}
}
radicale config Link to heading
now images are ready we need to add radicale configuration and users
- switch logging level to debug if needed
it is quite a simple conf you can play with it
~/web/radicale/config
- increase max_connections (i need only 2)
- don’t need to expose web interface
- htpasswd for internal auth
- use bcrypt as hashing
- delay of 300 seconds between failed attempts
- etc …
[server]
hosts = 0.0.0.0:5232
max_connections = 2
max_content_length = 100000
[encoding]
request = utf-8
stock = utf-8
[auth]
type = htpasswd
htpasswd_filename = /config/users
htpasswd_encryption = bcrypt
delay = 300
[rights]
type = authenticated
permit_delete_collection = True
permit_overwrite_collection = True
[storage]
filesystem_folder = /data/collections
[web]
type = none
[logging]
level = info
mask_passwords = True
[headers]
[hook]
[reporting]
users list Link to heading
create bcrypt password with this command
mkpasswd -m bcrypt
~/multisite/radicale/users
luigi:$2b$05$8CBfV/S6zveENJK8Qq/Jz.ptSPoz1tApyRi9GtOJr66hrmCZo7T2W
brian:$2b$05$8CBfV/S6zveENJK8Qq/Jz.ptSPoz1tApyRi9GtOJr66hrmCZo7T2W
clients Link to heading
official docs pretend all those clients are compatible
- Android with DAVx⁵ (formerly DAVdroid),
- OneCalendar
- GNOME Calendar, Contacts and Evolution
- Mozilla Thunderbird with CardBook and Lightning
- InfCloud, CalDavZAP and CardDavMATE
clients i tested
- davx5
4.4.9
/ etar1.0.48
https://calendar.domain.tld/radicale/luigi
- gnome calendar
47~rc-1.fc41
https://calendar.domain.tld/radicale/
troubleshooting Link to heading
curl diagnostics
curl -u 'luigi:realpassword' https://calendar.domain.tld/radicale/brian/ -X MOVE -H "Destination: https://calendar.domain.tld/radicale/luigi/
curl -X MKCOL -v -u 'brian:realpassword' https://calendar.domain.tld/brian/calendar/
find selinux problems
chcon -Rt svirt_sandbox_file_t /home/poduser/web/calendar.domain.tld
ressources Link to heading
- official doc
- radicale howto
https://cheatsheets.stephane.plus/self-hosted/radicale/
https://radicale.org/v3.html#authentication-and-rights
https://github.com/Kozea/Radicale/issues/947
https://github.com/Kozea/Radicale/blob/master/config
- storage
https://github.com/Kozea/Radicale/wiki/Collection-Storage
- diags
https://github.com/Kozea/Radicale/wiki/Server-Diagnostics---Troubleshooting
- rights
https://github.com/Kozea/Radicale/blob/master/rights
- docker compose
https://github.com/tomsquest/docker-radicale/blob/master/docker-compose.yml
- podman stuff
https://github.com/tomsquest/docker-radicale/issues/122
- caddy reverse proxy
https://github.com/Kozea/Radicale/blob/master/contrib/caddy/radicale.caddyfile
https://caddy.community/t/radicale-reverse-proxy-caddy-2-0/8580
https://caddy.community/t/make-radicale-work-behind-caddy/3787
https://github.com/caddyserver/examples/blob/master/radicale/Caddyfile
https://github.com/tomsquest/docker-radicale?tab=readme-ov-file#running-behind-caddy
https://github.com/Kozea/Radicale/wiki/Reverse-Proxy-Diagnostics-Troubleshooting
- security
https://github.com/Kozea/Radicale/wiki/Fail2Ban-Setup
https://github.com/mholt/caddy-ratelimit
- sharing calendars
https://www.reddit.com/r/selfhosted/comments/raq3pa/how_do_you_share_calendars_in_radicale/