Self-hosted CalDAV/CardDAV proxy, ICS transformer and DAV sync — in a single lightweight container.
⚠️ Pre-release. TidyDAV is feature-complete but has no tagged release yet; the configuration and database schema may still change. Run it from a clone.
TidyDAV cleans up and connects your calendars and contacts. It is a single static Go binary with an embedded web UI — the only runtime dependency is SQLite. No CGO, no Node.js at runtime, no Redis.
Pull one or more upstream ICS feeds and serve a clean, cached ICS endpoint:
- Rule pipeline applied in order — filter (black/whitelist), dedup, rename, strip fields, timezone normalization, drop expired events. Each rule matches by simple case-insensitive substring (for non-techies) or full Go regex.
- Merge several feeds into one (de-duplicated by UID).
- Caching in SQLite with a per-feed TTL, ETag revalidation and stale-on-error
fallback, so a dead upstream still serves the last good copy. Subscribers get
conditional GET (
ETag/If-None-Match→304 Not Modified), so a poll that finds nothing new transfers no calendar at all — which needs a TTL > 0 to pay off, since an uncached feed is re-fetched from the upstream on every request anyway. - Live preview in the UI showing the original vs. transformed events.
- Served at
/ics/<secret>, secured by an unguessable secret-id and optional HTTP Basic Auth — no session, so any calendar client can subscribe.
Synchronise a CalDAV calendar or CardDAV address book between two servers as independent jobs:
- Uni- (A→B, B→A) or bidirectional, matched across servers by UID.
- Conflict resolution: newest-wins (
LAST-MODIFIED) or source-wins; a change always beats a delete to avoid data loss. - ETag-aware (unchanged items are skipped); run on a per-job interval or manually.
- Fire webhook / ntfy / Gotify notifications the first time a feed rule matches an event — evaluated on a schedule, de-duplicated, never on calendar polls (no spam).
- Outage alert: get one warning when a source stops delivering (and one all-clear
when it recovers), so a dead upstream URL doesn't hide behind the cached last good
copy. Checked on the notifier schedule (
TIDYDAV_NOTIFY_INTERVAL, 15 min by default), which is also how late a warning can be. - Admin-only audit log of feed and sync-job changes.
- OIDC (any provider via discovery), email + password (with SMTP password
reset), and an access mode switch (
public/auth/both). The first registered user becomes admin.
From a clone (builds the image locally; works out of the box):
cd deploy
cp .env.example .env
# set TIDYDAV_SECRET_KEY (openssl rand -hex 32) and TIDYDAV_BASE_URL
docker compose -f compose.example.yaml up -dThen open TIDYDAV_BASE_URL in your browser and register the first (admin) account. Run
TidyDAV behind a reverse proxy that terminates TLS, and persist the /data volume.
All configuration is via TIDYDAV_* environment variables — only TIDYDAV_SECRET_KEY
and TIDYDAV_BASE_URL are required; everything else has sensible defaults. OIDC and SMTP
are optional and enable themselves once configured. See
deploy/.env.example for the full, documented reference, and run
the container as a non-root user via PUID/PGID.
See the Known limitations section in CHANGELOG.md — notably: VTODO is not yet a dedicated sync kind (CalDAV jobs still carry VTODOs), sync uses per-item ETags rather than a CTag fast-path, cross-source merge de-duplicates by UID only, and only filter/rename rules can trigger notifications.
- CLAUDE.md — architecture, conventions and design system
- CONTRIBUTING.md — development setup and contribution process
- CHANGELOG.md — changes and known limitations
Contributions are welcome! Please read CONTRIBUTING.md. All contributors must agree to the Contributor License Agreement.
TidyDAV is licensed under the GNU Affero General Public License v3.0.