A Habitica ⇄ CalDAV bridge: it makes Habitica tasks readable — and tickable — from any DAV-capable app (Tasks.org, Apple Reminders, Nextcloud Tasks, Thunderbird…) by exposing them through an embedded Radicale CalDAV server.
Multi-user: one QuestSync instance serves many people. Each person logs in with their own Habitica User ID + API token as their CalDAV credentials and sees only their own tasks. The server stores no accounts.
QuestSync is a custom Radicale storage + auth plugin — not a copy of your data:
- Auth (
radicale_auth.py): DAV username = Habitica User ID, DAV password = Habitica API token, validated live against Habitica; the User ID becomes the DAV principal soowner_onlyrights isolate each user. - Storage (
radicale_storage.py): per request it fetches that user's tasks and renders them asVTODOs (read-through); a clientPUT/DELETEis translated into Habitica API calls (write-through). - One source of truth (Habitica) ⇒ no database, no sync conflicts, no state store.
Supported today: todos (full bidirectional) and dailies (materialized — shown while due; completing one scores it). Habits/rewards are out of scope.
git clone https://github.com/SDR3078/questsync && cd questsync
docker compose up -d --buildAdd a CalDAV account in your app:
| Field | Value |
|---|---|
| Server / URL | http://<host>:5232/ |
| Username | your Habitica User ID |
| Password | your Habitica API Token |
Both are at https://habitica.com/user/settings/api. You'll get two task lists: Habitica To-Dos and Habitica Dailies.
⚠️ Use HTTPS in production. Credentials travel in HTTP Basic auth on every request — never expose QuestSync without TLS. See Security.
| Var | Default | Meaning |
|---|---|---|
QUESTSYNC_TASK_TYPES |
todos,dailys |
Which lists to expose |
QUESTSYNC_CACHE_TTL |
30 |
Seconds to cache each user's task snapshot |
QUESTSYNC_LOGIN_TTL |
300 |
Seconds to trust a validated login before re-checking |
QUESTSYNC_CLIENT_AUTHOR |
(user's id) | Fixed x-client author id |
QUESTSYNC_DEMO |
(off) | 1 = offline fixture + accept any login (dev/CI) |
QUESTSYNC_ACCESS_POLICY |
allowall |
Who may log in: allowall / allowlist / http |
QUESTSYNC_ALLOWLIST |
— | (allowlist) comma-separated allowed Habitica User IDs |
QUESTSYNC_ALLOWLIST_FILE |
— | (allowlist) file of IDs, one per line, re-read live |
QUESTSYNC_ACCESS_HTTP_URL |
— | (http) endpoint asked GET ?user=<id> (200=allow) |
The server needs no Habitica credentials of its own — users bring theirs.
By default (allowall) anyone with valid Habitica credentials may log in. To
restrict an instance, set QUESTSYNC_ACCESS_POLICY:
allowlist— a static set of Habitica User IDs fromQUESTSYNC_ALLOWLISTand/orQUESTSYNC_ALLOWLIST_FILE(one id per line, re-read live — a mounted ConfigMap edit applies without a restart).http— delegate to an external endpoint (GET <url>?user=<id>;200= allow,403/404= deny) — the hook for a hosted/paid control plane.
The gate runs on the User ID, before the Habitica credential check, so a denied user never triggers a Habitica call. Policies can only deny, never grant.
- TLS is mandatory in production — Basic-auth credentials (incl. the API token) are sent on every request.
- Tokens are in-memory only: validated against Habitica, cached in process, never logged or persisted. The login cache keys on a SHA-256 of the password, not the plaintext.
- Isolation:
owner_onlyscopes each user to/<their-id>/…; cross-user access returns403(tested). - Trust model: like any CalDAV bridge, the operator's server handles each user's token in memory while they're connected. Run it where you and your users trust the operator.
See deploy/ for the Kustomize manifests (Deployment, Service, Ingress,
NetworkPolicy) and deploy/README.md for the runbook. No app secret
is required (users bring their own creds) — you only need to route HTTPS to the
Service (an ingress + cert, or a Cloudflare Tunnel). The reference deployment is
GitOps-managed by ArgoCD and fronted by a Cloudflare Tunnel.
# offline demo: fixture data, any login accepted
QUESTSYNC_DEMO=1 docker compose up -d
bash scripts/build-verify.sh # todos read+write
bash scripts/dailys-verify.sh # dailies
# unit tests (converter + credstore)
docker run --rm -e PYTHONPATH=/app -v "$PWD/src:/app:ro" -v "$PWD/tests:/tests:ro" \
python:3.12-slim sh -lc "pip install -q vobject python-dateutil pytest && pytest -q /tests"
# live checks against a real account (creds read from .env, never printed)
docker compose up -d && bash scripts/live-multiuser-verify.sh- ✅ Multi-user, bidirectional todos + dailies — live-verified.
- ✅ CI builds & publishes to GHCR and auto-rolls the deploy via ArgoCD.
- ✅ Optional access gate (allowlist / external
httppolicy). - ⬜ Real subtasks (
RELATED-TO), tag sync, per-request rate limiting.