| @@ -3,2 +2,0 @@ |
{toc}
|
|
|
|
|
| @@ -7 +5,4 @@ |
{code}zypper install radicale # Creates radicale:radicale system user automatically {/code}
|
|
| zypper install radicale |
| # Creates radicale:radicale system user automatically |
|
|
|
| @@ -13 +14,12 @@ |
{code}[server] hosts = localhost:5232 [auth] type = htpasswd htpasswd_filename = /etc/radicale/users htpasswd_encryption = autodetect [storage] filesystem_folder = /srv/radicale/collections {/code}
|
|
| [server] |
| hosts = localhost:5232 |
|
| [auth] |
| type = htpasswd |
| htpasswd_filename = /etc/radicale/users |
| htpasswd_encryption = autodetect |
|
| [storage] |
| filesystem_folder = /srv/radicale/collections |
|
|
|
| @@ -20,2 +32,2 @@ |
| <code>ReadWritePaths=/var/lib/radicale/collections /srv/radicale/
|
| </code>
|
| ReadWritePaths=/var/lib/radicale/collections /srv/radicale/ |
|
|
|
| @@ -26 +38 @@ |
| <code>mkdir -p /srv/radicale/collections
|
| mkdir -p /srv/radicale/collections |
|
|
| @@ -30 +42 @@ |
| </code>
|
|
|
|
| @@ -37 +49 @@ |
| <code># Create directory structure
|
| # Create directory structure |
|
|
| @@ -41,2 +53,2 @@ |
| # Props files — collection-root needs one too!
|
| echo '{}' > /srv/radicale/collections/collection-root/.Radicale.props
|
| # Props files — collection-root needs one too! |
| echo '{}' > /srv/radicale/collections/collection-root/.Radicale.props |
|
|
| @@ -44,2 +56,2 @@ |
| cat > /srv/radicale/collections/collection-root/lsces/.Radicale.props << 'EOF'
|
| {"D:displayname": "lsces"}
|
| cat > /srv/radicale/collections/collection-root/lsces/.Radicale.props << 'EOF' |
| {"D:displayname": "lsces"} |
|
|
| @@ -48,2 +60,2 @@ |
| cat &amp;amp;amp;gt; /srv/radicale/collections/collection-root/lsces/calendar/.Radicale.props &amp;amp;amp;lt;&amp;amp;amp;lt; &amp;amp;amp;#39;EOF&amp;amp;amp;#39;
|
| {&amp;amp;amp;quot;C:supported-calendar-component-set&amp;amp;amp;quot;: &amp;amp;amp;quot;VEVENT,VJOURNAL,VTODO&amp;amp;amp;quot;, &amp;amp;amp;quot;D:displayname&amp;amp;amp;quot;: &amp;amp;amp;quot;Personal Calendar&amp;amp;amp;quot;, &amp;amp;amp;quot;tag&amp;amp;amp;quot;: &amp;amp;amp;quot;VCALENDAR&amp;amp;amp;quot;}
|
| cat > /srv/radicale/collections/collection-root/lsces/calendar/.Radicale.props << 'EOF' |
| {"C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO", "D:displayname": "Personal Calendar", "tag": "VCALENDAR"} |
|
|
| @@ -52,2 +64,2 @@ |
| cat &amp;amp;amp;gt; /srv/radicale/collections/collection-root/lsces/contacts/.Radicale.props &amp;amp;amp;lt;&amp;amp;amp;lt; &amp;amp;amp;#39;EOF&amp;amp;amp;#39;
|
| {&amp;amp;amp;quot;D:displayname&amp;amp;amp;quot;: &amp;amp;amp;quot;Personal Contacts&amp;amp;amp;quot;, &amp;amp;amp;quot;tag&amp;amp;amp;quot;: &amp;amp;amp;quot;VADDRESSBOOK&amp;amp;amp;quot;}
|
| cat > /srv/radicale/collections/collection-root/lsces/contacts/.Radicale.props << 'EOF' |
| {"D:displayname": "Personal Contacts", "tag": "VADDRESSBOOK"} |
|
|
| @@ -57 +69 @@ |
| &amp;amp;lt;/code&amp;amp;gt;
|
|
|
|
| @@ -62,2 +74,2 @@ |
| &amp;amp;lt;code&amp;amp;gt;htpasswd -B -c /etc/radicale/users lsces
|
| &amp;amp;lt;/code&amp;amp;gt;
|
| htpasswd -B -c /etc/radicale/users lsces |
|
|
|
| @@ -68 +80 @@ |
| &amp;amp;lt;code&amp;amp;gt;systemctl daemon-reload
|
| systemctl daemon-reload |
|
|
| @@ -72 +84 @@ |
| &amp;amp;lt;/code&amp;amp;gt;
|
|
|
|
| @@ -77 +89 @@ |
| &amp;amp;lt;code&amp;amp;gt;location /dav/ {
|
| location /dav/ { |
|
|
| @@ -85 +97 @@ |
| &amp;amp;lt;/code&amp;amp;gt;
|
|
|
|
| @@ -87 +99 @@ |
**Both Authorization headers needed — nginx strips it without explicit passthrough!**
|
Both Authorization headers needed — nginx strips it without explicit passthrough! |
|
|
| @@ -91 +103,3 @@ |
**Calendar export** — Nextcloud UI: Calendar → Edit → Export → saves `.ics` Radicale requires **one event per file** — split the export:
|
Calendar export — Nextcloud UI:
|
Calendar → Edit → Export → saves `.ics`
|
| Radicale requires **one event per file** — split the export: |
|
|
| @@ -94 +108 @@ |
| &amp;amp;lt;code&amp;amp;gt;import re, os
|
| import re, os |
|
|
| @@ -96 +110 @@ |
| with open(&amp;amp;amp;#39;personal.ics&amp;amp;amp;#39;, &amp;amp;amp;#39;r&amp;amp;amp;#39;) as f:
|
| with open('personal.ics', 'r') as f: |
|
|
| @@ -99,4 +113,4 @@ |
| header = content[:content.index(&amp;amp;amp;#39;BEGIN:VEVENT&amp;amp;amp;#39;)]
|
| footer = &amp;amp;amp;#39;END:VCALENDARn&amp;amp;amp;#39;
|
| events = re.findall(&amp;amp;amp;#39;BEGIN:VEVENT.*?END:VEVENT&amp;amp;amp;#39;, content, re.DOTALL)
|
| os.makedirs(&amp;amp;amp;#39;/srv/radicale/split&amp;amp;amp;#39;, exist_ok=True)
|
| header = content[:content.index('BEGIN:VEVENT')] |
| footer = 'END:VCALENDARn' |
| events = re.findall('BEGIN:VEVENT.*?END:VEVENT', content, re.DOTALL) |
| os.makedirs('/srv/radicale/split', exist_ok=True) |
|
|
| @@ -105,2 +119,2 @@ |
| uid = re.search(&amp;amp;amp;#39;UID:(.*?)n&amp;amp;amp;#39;, event).group(1).strip()
|
| with open(f&amp;amp;amp;#39;/srv/radicale/split/{uid}.ics&amp;amp;amp;#39;, &amp;amp;amp;#39;w&amp;amp;amp;#39;) as f:
|
| uid = re.search('UID:(.*?)n', event).group(1).strip() |
| with open(f'/srv/radicale/split/{uid}.ics', 'w') as f: |
|
|
| @@ -108 +122 @@ |
| f.write(event + &amp;amp;amp;#39;n&amp;amp;amp;#39;)
|
| f.write(event + 'n') |
|
|
| @@ -111,2 +125,2 @@ |
| print(f&amp;amp;amp;#39;Split {len(events)} events&amp;amp;amp;#39;)
|
| &amp;amp;lt;/code&amp;amp;gt;
|
| print(f'Split {len(events)} events') |
|
|
|
| @@ -117 +131 @@ |
| &amp;amp;lt;code&amp;amp;gt;import re, os
|
| import re, os |
|
|
| @@ -119 +133 @@ |
| with open(&amp;amp;amp;#39;contacts.vcf&amp;amp;amp;#39;, &amp;amp;amp;#39;r&amp;amp;amp;#39;) as f:
|
| with open('contacts.vcf', 'r') as f: |
|
|
| @@ -122,2 +136,2 @@ |
| contacts = re.findall(&amp;amp;amp;#39;BEGIN:VCARD.*?END:VCARD&amp;amp;amp;#39;, content, re.DOTALL)
|
| os.makedirs(&amp;amp;amp;#39;/srv/radicale/split_contacts&amp;amp;amp;#39;, exist_ok=True)
|
| contacts = re.findall('BEGIN:VCARD.*?END:VCARD', content, re.DOTALL) |
| os.makedirs('/srv/radicale/split_contacts', exist_ok=True) |
|
|
| @@ -126,4 +140,4 @@ |
| uid = re.search(&amp;amp;amp;#39;UID:(.*?)n&amp;amp;amp;#39;, contact)
|
| uid = uid.group(1).strip() if uid else f&amp;amp;amp;#39;contact_{contacts.index(contact)}&amp;amp;amp;#39;
|
| with open(f&amp;amp;amp;#39;/srv/radicale/split_contacts/{uid}.vcf&amp;amp;amp;#39;, &amp;amp;amp;#39;w&amp;amp;amp;#39;) as f:
|
| f.write(contact + &amp;amp;amp;#39;n&amp;amp;amp;#39;)
|
| uid = re.search('UID:(.*?)n', contact) |
| uid = uid.group(1).strip() if uid else f'contact_{contacts.index(contact)}' |
| with open(f'/srv/radicale/split_contacts/{uid}.vcf', 'w') as f: |
| f.write(contact + 'n') |
|
|
| @@ -131,2 +145,2 @@ |
| print(f&amp;amp;amp;#39;Split {len(contacts)} contacts&amp;amp;amp;#39;)
|
| &amp;amp;lt;/code&amp;amp;gt;
|
| print(f'Split {len(contacts)} contacts') |
|
|
|
| @@ -137 +151 @@ |
| &amp;amp;lt;code&amp;amp;gt;# Calendar events
|
| # Calendar events |
|
|
| @@ -139 +153 @@ |
| uid=$(basename &amp;amp;amp;quot;$f&amp;amp;amp;quot; .ics)
|
| uid=$(basename "$f" .ics) |
|
|
| @@ -142,4 +156,4 @@ |
| -H &amp;amp;amp;quot;Content-Type: text/calendar&amp;amp;amp;quot;
|
| --data-binary @&amp;amp;amp;quot;$f&amp;amp;amp;quot;
|
| &amp;amp;amp;quot;https://rdm1.uk/dav/lsces/calendar/${uid}.ics&amp;amp;amp;quot;
|
| echo &amp;amp;amp;quot;$uid&amp;amp;amp;quot;
|
| -H "Content-Type: text/calendar" |
| --data-binary @"$f" |
| "https://rdm1.uk/dav/lsces/calendar/${uid}.ics" |
| echo "$uid" |
|
|
| @@ -150 +164 @@ |
| uid=$(basename &amp;amp;amp;quot;$f&amp;amp;amp;quot; .vcf)
|
| uid=$(basename "$f" .vcf) |
|
|
| @@ -153,4 +167,4 @@ |
| -H &amp;amp;amp;quot;Content-Type: text/vcard&amp;amp;amp;quot;
|
| --data-binary @&amp;amp;amp;quot;$f&amp;amp;amp;quot;
|
| &amp;amp;amp;quot;https://rdm1.uk/dav/lsces/contacts/${uid}.vcf&amp;amp;amp;quot;
|
| echo &amp;amp;amp;quot;$uid&amp;amp;amp;quot;
|
| -H "Content-Type: text/vcard" |
| --data-binary @"$f" |
| "https://rdm1.uk/dav/lsces/contacts/${uid}.vcf" |
| echo "$uid" |
|
|
| @@ -158 +172 @@ |
| &amp;amp;lt;/code&amp;amp;gt;
|
|
|
|
| @@ -162 +176,11 @@ |
**Thunderbird** — Add new calendar/contacts: - Network → CalDAV/CardDAV - URL: `https://yourdomain/dav/username/calendar/` **DAVx⁵ (Android)** — Add account: - Base URL: `https://yourdomain/dav/username/` - Auto-discovers both calendar and contacts
|
|
| Thunderbird — Add new calendar/contacts: |
| Network → CalDAV/CardDAV |
| URL: https://yourdomain/dav/username/calendar/ |
|
|
|
| DAVx⁵ (Android) — Add account: |
| Base URL:https://yourdomain/dav/username/ |
| Auto-discovers both calendar and contacts |
|
|
|
| @@ -163,0 +188,2 @@ |
Developed with Claude AI |
| assistance - Anthropic - April 2026 |
|
|