diff --git a/117_radicale_deployment.md b/117_radicale_deployment.md new file mode 100644 index 0000000..20a9a54 --- /dev/null +++ b/117_radicale_deployment.md @@ -0,0 +1,185 @@ +# 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.md` — **only after physical deployment** + +--- + +## Phase 1 — Create LXC container + +```bash +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 + +```bash +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 + +```bash +mkdir -p /etc/radicale /var/lib/radicale/collections +htpasswd -c /etc/radicale/users spendlik +``` + +`/etc/radicale/config`: +```ini +[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`: +```ini +[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 +``` + +```bash +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): + +```nginx +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 + +```bash +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: +```bash +mkdir -p /var/lib/radicale/collections/spendlik/personal +chown -R radicale:radicale /var/lib/radicale/collections/spendlik +``` + +Test with `curl` first: +```bash +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