Claude_Homelab/117_radicale_deployment.md

5.6 KiB

117 — Radicale Deployment Guide

Status: NOT YET DEPLOYED — planning reference only. Do not treat CT 117 or this config as live until each step is physically completed and confirmed.

Service: Radicale (lightweight CalDAV/CardDAV server) CT ID: 117 IP: 192.168.1.117 Domain: cal.spendlik.sk Container type: Unprivileged LXC, Debian 13 (trixie) — no Docker required, Radicale is a single Python package Auth: Authelia (CT 102) in front, plus Radicale's own htpasswd file as a second layer (CalDAV clients need Basic Auth to authenticate directly — Authelia SSO alone doesn't work well with CalDAV client protocols)


Phase overview

  1. Create LXC container
  2. Install Radicale + dependencies
  3. Configure Radicale (storage path, users, rights)
  4. Create systemd service
  5. nginx reverse proxy (CT 101)
  6. DNS — WebSupport A record (both management pages) + DDNS updater (CT 108)
  7. Let's Encrypt SSL via certbot + manual nginx config inspection
  8. Authelia bypass/config decision
  9. Create first calendar + test with a CalDAV client
  10. Client setup — Thunderbird (CachyOS) + DAVx⁵ (Galaxy S25)
  11. Update 00_index.md and homelab-overview.mdonly after physical deployment

Phase 1 — Create LXC container

pct create 117 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
  --hostname radicale \
  --cores 1 \
  --memory 256 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.1.117/24,gw=192.168.1.1 \
  --unprivileged 1 \
  --features nesting=0 \
  --storage local-lvm \
  --rootfs local-lvm:4
  • 256MB RAM / 4GB disk is generous for Radicale — it's a single-user Python app with a flat-file backend
  • nesting=0 — no Docker needed, so no privileged container required, unlike your other services

Phase 2 — Install Radicale

pct start 117
pct enter 117

apt update && apt install -y python3-pip python3-venv nano

python3 -m venv /opt/radicale
source /opt/radicale/bin/activate
pip install radicale
  • nano installed per your standing convention for new containers

Phase 3 — Configure Radicale

mkdir -p /etc/radicale /var/lib/radicale/collections
htpasswd -c /etc/radicale/users spendlik

/etc/radicale/config:

[server]
hosts = 0.0.0.0:5232

[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = bcrypt

[storage]
filesystem_folder = /var/lib/radicale/collections

[rights]
type = owner_only
  • owner_only — each authenticated user only sees their own collections; fine for single-user use, revisit if you ever add a second account

Phase 4 — systemd service

/etc/systemd/system/radicale.service:

[Unit]
Description=Radicale CalDAV/CardDAV server
After=network.target

[Service]
ExecStart=/opt/radicale/bin/radicale --config /etc/radicale/config
Restart=on-failure
User=radicale

[Install]
WantedBy=multi-user.target
useradd -r -s /usr/sbin/nologin radicale
chown -R radicale:radicale /var/lib/radicale /etc/radicale
systemctl daemon-reload
systemctl enable --now radicale
systemctl status radicale

Phase 5 — nginx reverse proxy (CT 101)

New vhost, HTTP-only first (per your certbot gotcha — always issue on a working HTTP config before adding SSL):

server {
    listen 80;
    server_name cal.spendlik.sk;

    location / {
        proxy_pass http://192.168.1.117:5232/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Script-Name /;
    }
}

Phase 6 — DNS

  • Add cal.spendlik.sk A record in both WebSupport management pages ⚠️
  • Add cal.spendlik.sk to the DDNS updater script in CT 108, with record ID once created
  • Update 00_index.md WebSupport DNS Record ID table — after the record actually exists

Phase 7 — SSL

certbot --nginx -d cal.spendlik.sk
  • ⚠️ Manually inspect the resulting nginx config afterward — check for duplicate server_name and missing closing braces (known recurring certbot issue in this environment)

Phase 8 — Authelia

CalDAV clients (Thunderbird, DAVx⁵) authenticate via HTTP Basic Auth on every request — they don't handle browser-based SSO redirects well. Recommended: bypass Authelia entirely for cal.spendlik.sk and rely on Radicale's own htpasswd auth + HTTPS. Add the bypass rule in CT 102's Authelia config, ordered before the catch-all 2FA rule (same pattern as your n8n /api/* bypass).

Phase 9 — Create calendar + test

Radicale auto-creates collections on first client connection, or manually:

mkdir -p /var/lib/radicale/collections/spendlik/personal
chown -R radicale:radicale /var/lib/radicale/collections/spendlik

Test with curl first:

curl -u spendlik https://cal.spendlik.sk/spendlik/personal/

Phase 10 — Clients

CachyOS (Thunderbird):

  • Calendar → New Calendar → On the Network → CalDAV
  • URL: https://cal.spendlik.sk/spendlik/personal/

Galaxy S25 (DAVx⁵):

  • Add account → base URL https://cal.spendlik.sk/spendlik/
  • Syncs to native Samsung Calendar app once configured

Phase 11 — Documentation (after physical deployment only)

  • Add CT 117 row to 00_index.md inventory table
  • Add cal.spendlik.sk DNS record ID once known
  • Add 117_radicale_deployment.md to Documentation Map
  • Move Radicale from "Active Projects" to deployed state
  • Update homelab-overview.md container table

Notes

  • No Docker, no privileged container — smallest-footprint service in the stack
  • Backup: /var/lib/radicale/collections is the only stateful data — include in existing LXC nightly backup job, no special handling needed