Skip to content

rewulff/calendula

Repository files navigation

Calendula — Focused Eisenhower task planning for Microsoft 365. Open Source. No AI bloat.

Calendula

License: MIT Version PWA Ready

Buy Me A Coffee

Why Calendula?

Morgen.so is expensive and overloaded. Calendula does one thing well: focused Eisenhower task planning for Microsoft 365 — self-hostable, installable as a PWA, no AI bloat.

Calendula Morgen.so Other tools
Price Free (OSS) Subscription Mostly free
Eisenhower + Time-Blocking ✅ + AI + everything Usually no
Microsoft 365 native Multi-provider Varies
No AI bloat Heavy AI Trend-following
Self-hostable / PWA ✅ Docker + PWA Cloud-only Varies

⚠️ Single-user app: Calendula is designed for one operator. App-level authentication (scrypt + HMAC session) is enabled by default since 0.4.0 (#205). On first start the frontend redirects to /setup to set a password. See Authentication below and docs/security.md.

Screenshots

The screenshots below use mock task data — the UI is the real app.

Today — calendar + sidebar

Today view

Matrix — Eisenhower prioritization

Matrix view

Focus — decision mode

Focus view

Settings — calendars and preferences

Settings

Stack

  • Backend: FastAPI + Microsoft Graph API (O365 To Do + Calendar)
  • Frontend: SvelteKit + FullCalendar.js + Tailwind CSS
  • Auth: MSAL Python (Delegated — Device Code Flow, Token Cache)
  • Event-Log: SQLite + SQLAlchemy (status changes, undo — NOT for task data)
  • Container: Docker Compose

Quickstart

Step 0 — Set up Azure App Registration (one-time, ~5 minutes): See docs/azure-setup.md.

Then:

cp .env.example .env   # fill in AZURE_TENANT_ID + AZURE_CLIENT_ID from Step 0
docker compose up

Language

The UI is currently German-only. English translation (i18n) is on the post-OSS-launch roadmap — see #233. Docs (README, setup guide) are in English. Contributions welcome.

Architecture

Frontend (SvelteKit) --- API ---> FastAPI
                                    |
                              TaskProvider ABC
                                    |
                            O365TaskProvider
                                    |
                              GraphClient (MSAL)
                                    |
                          Microsoft Graph API
                          - To Do Tasks (CRUD + Extensions)
                          - Calendar Events (Time-Blocking)
                          - Open Extensions (schedule data)
                          - Categories (taskId linking)

All task data lives in O365. Calendula has no own task database.

Features

  • Task lists from all O365 To Do lists (including Flagged Emails with Outlook deep-links)
  • Eisenhower prioritization (P1-P4) via O365 categories
  • Time-blocking via drag-and-drop into calendar (creates O365 Calendar Events)
  • Completed tasks visually dimmed in calendar (opacity + strikethrough)
  • Standard calendar overlay (read-only, dimmed) alongside task time-blocks
  • Subtasks (O365 checklist items)
  • Recurrence display
  • Due dates + reminders
  • Matrix view — Eisenhower matrix (P0–P4) with drag-drop between quadrants
  • Focus view — Decision-helper mode: pick one task to work on, timer-based single-task UI
  • Done view — Completed tasks archive
  • Mutable settings — Calendar selection, timezone, work hours, slot duration are runtime-editable (no restart)
  • Hot-reload — Settings changes apply on next request (no container restart)
  • Undo — Local event log tracks status changes; recent changes can be undone
  • Installable as PWA — Home-screen shortcut on desktop and mobile, offline app shell via service worker

Flagged Emails

Flagged emails from Outlook appear in the "Flagged Emails" list. The detail popup shows an "In Outlook oeffnen" link.

Tip: To open mail links in the Outlook desktop app instead of the browser, enable "Open links in desktop app" in your O365/Outlook Web settings.

Authentication

Calendula ships with built-in password authentication (APP_AUTH_ENABLED=true by default since 0.4.0, #205).

Why default-on

For OSS self-hosters: a fresh deploy without explicit auth config used to expose the API on whatever port the reverse proxy exposed. A log warning is not enough — the safe default is on, with a graceful first-start flow instead of a hard error.

First start

  1. Start the stack — backend logs print two warnings: APP_AUTH_ENABLED=true ... aber noch kein Passwort gesetzt. Erstes Passwort via /setup ... Setup token generated: see data/calendula_setup_token (chmod 600) ...
  2. Grab the setup token (#215) — it gates POST /api/app-auth/setup so a random external POST cannot hijack a fresh deployment in the seconds between startup and your first browser tab:
    docker exec <container_name> cat /app/data/calendula_setup_token
    Alternative: set CALENDULA_SETUP_TOKEN=<your-token> as env-var (Komodo stack UI / .env). The env-var takes precedence and the file is never generated; useful if you provision the token via secret manager.
  3. Open the frontend in your browser. The layout detects setup_complete: false via GET /api/app-auth/status and redirects to /setup.
  4. Paste the setup token into the form, set a password (min. 8 characters). Hash is stored as auth.password_hash in the config store (SQLite-backed JSON, persisted in calendula_data volume). The token file is deleted after success.
  5. Login screen appears — log in with the password you just set.
  6. Session cookie calendula_session (HttpOnly, SameSite=Strict, 7 days) is set; the normal app loads.

For non-browser clients (curl, scripts): protected routes return 401 {"detail": "setup_required", "setup_url": "/setup", "message": ...} until a password is set via POST /api/app-auth/setup {"password": "...", "setup_token": "..."}. A missing or wrong token returns 403 {"detail": "setup_token_invalid"}.

Migration note for existing self-hosters: if a password is already configured (config store has auth.password_hash), the token check is skipped — the endpoint returns the familiar 403 setup_already_done as before. No action required on upgrade.

Opt-out

Set APP_AUTH_ENABLED=false in .env / stack env. The ASGI middleware is not registered and the API is fully open. Use only if an upstream layer (Caddy IP-whitelist + VPN, network isolation, mutual TLS) is enforcing access control. A clear warning is logged on every start.

Changing the password

PUT /api/app-auth/password with the current session cookie, body {"old_password": "...", "new_password": "..."}. Existing session tokens remain valid until they expire (no server-side revocation list).

Set CALENDULA_SECURE_COOKIES=1

For production behind HTTPS: forces the Secure flag on the session cookie. Default off so local-development on http://localhost:5173 works without certificate gymnastics.

Contributing

External contributions are welcome. See CONTRIBUTING.md for setup, branch-naming, commit conventions, and PR workflow.

Self-Hosting

When serving Calendula under your own domain, set CORS_ORIGINS in .env to the list of origins that may call the API. Accepts a comma-separated list (or JSON array). The default covers only localhost dev ports — production browsers will be blocked by CORS until your FQDN is added. Example: CORS_ORIGINS=https://calendula.example.com,http://localhost:5173.

Local Development

# Backend
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload

# Frontend
cd frontend
npm install
npm run dev

Local smoke testing

Verify your changes against the App-Auth + CORS path without touching the live deploy:

cp .env.smoke.example .env.smoke  # fill AZURE_TENANT_ID + AZURE_CLIENT_ID
docker compose -f docker-compose.smoke.yml --env-file .env.smoke up -d --build
python3 scripts/smoke_test.py     # 9 HTTP assertions, exits non-zero on failure
docker compose -f docker-compose.smoke.yml --env-file .env.smoke down -v

Smoke uses isolated ports (8001/5174) and volumes (calendula_smoke_data) so it runs in parallel to a live container.

Azure App Registration

See docs/azure-setup.md for the step-by-step setup guide with screenshots.

Summary:

  • Permissions (Delegated): Tasks.ReadWrite, Calendars.ReadWrite, User.Read, offline_access
  • Public Client Flows: Enabled (for Device Code Flow)
  • Credentials in .env (not in repo)

Versioning

Calendula uses Semantic Versioning. All notable changes are tracked in CHANGELOG.md.

Checking your running version

curl https://<host>/api/version
# {"version":"0.4.0","commit":"a20d601","built_at":"2026-06-01T10:30:00Z"}

Updating

git pull
docker compose -f docker-compose.prod.yml up -d --build

Via a stack manager (Portainer / Komodo / Dockge / etc.): trigger a redeploy of the stack. Set BUILD_COMMIT and BUILD_TIMESTAMP build args to inject version metadata into the /api/version response.

Roadmap

Calendula is preparing for an OSS release. Tracked in Forgejo:

  • #156 Native auth — Local password + optional session cookies (scrypt + HMAC). Epic in progress, 4 sub-packages.
  • #109 Versioning — SemVer, /api/version endpoint, Docker image tags.
  • #157 Multi-calendar overlay — N read-only overlay calendars with per-calendar color + blur.
  • #9 PWA — Installable app for desktop + mobile (manifest + service worker).

Once #156 + #109 ship, the built-in-authentication warning at the top will be replaced by standard OSS setup docs.

Production Deployment

Calendula runs on any Docker host. The compose files (docker-compose.yml for dev, docker-compose.prod.yml for production) work standalone or with stack managers like Portainer, Komodo, or Dockge.

Stack layout

  • Compose file: docker-compose.prod.yml
  • Backend: built from backend/Dockerfile.prod (no bind mounts, only calendula_data volume)
  • Frontend: built from frontend/Dockerfile.prod (multi-stage; SvelteKit node build/index.js on port 3000)
  • Persistence: named volume calendula_data holds msal_token_cache.json + calendula.db

Required env vars (set in your stack manager / .env, not committed)

  • AZURE_TENANT_ID
  • AZURE_CLIENT_ID
  • CALENDAR_USER_EMAIL
  • TODO_LIST_NAMES (optional, empty = all lists)
  • PUBLIC_API_BASE_URL (default https://calendula.example.com)
  • PUBLIC_GRAFANA_URL
  • DEBUG_LEVEL (default off)
  • VITE_DEBUG_LEVEL (default off)

First start (Device Code Flow)

  1. Deploy stack
  2. Tail backend logs: device code + verification URL appear once
  3. User opens https://login.microsoftonline.com/common/oauth2/deviceauth, enters code
  4. Token cache lands in the calendula_data volume — subsequent restarts are headless

Reverse proxy

Any HTTPS reverse proxy (Caddy, Traefik, nginx) works. Route /api/* and /health to backend (port 8000), everything else to the SvelteKit frontend (port 3000). Restrict by IP/network as needed.

About

Focused Eisenhower task planning for Microsoft 365 — drag your O365 to-dos into the Outlook calendar as time blocks. Self-hostable, PWA, no AI bloat.

Topics

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors