# DAV Server A self-hosted **CalDAV**, **CardDAV**, and **WebDAV** server written in Go. ## About the name The project is called **nidus** — Latin for *"nest"*. Like a nest, it's a small, self-hosted, personal home for your own data: calendars, contacts, and files, all kept under your own roof instead of a third-party cloud. ## Features | Protocol | Use case | |----------|----------| | CalDAV | Calendars — sync with Apple Calendar, Thunderbird, GNOME Calendar, … | | CardDAV | Contacts — sync with Apple Contacts, GNOME Contacts, … | | WebDAV | General file access via Finder, Windows Explorer, Nautilus, … | - HTTP Basic Auth with **bcrypt** password storage - Per-user isolated collections - **Calendar/address book sharing** — grant other users read or write access to your calendars/address books - **Web UI** — a mobile-friendly app at `/web/` for managing calendars, contacts, files, and account settings (see [Web UI](#web-ui) below), built with templ + Tailwind + htmx - Auto-discovery via `/.well-known/caldav` and `/.well-known/carddav` - Optional **TLS** (or use a reverse proxy) - Structured logging (text or JSON) - Graceful shutdown - Docker & Docker Compose support ## Quick start ### 1. Install dependencies ```bash go mod tidy ``` ### 2. Create your `config.yaml` Copy the example config and edit it — `config.yaml` is git-ignored so your real settings never get committed: ```bash cp config.example.yaml config.yaml ``` Users, calendars, and address books are **no longer configured in `config.yaml`** — they live in the SQLite database and are managed with `nidusctl` (see below). ### 3. Run the server ```bash make run # or go run ./cmd/server -config config.yaml ``` The server starts at **http://localhost:8080**. ### 4. Create a user and their resources ```bash go run ./tools/nidusctl -config config.yaml user create alice \ --display-name "Alice Smith" --email alice@example.com # (prompts for a password; use --password to skip the prompt, e.g. in scripts) go run ./tools/nidusctl -config config.yaml calendar create alice personal go run ./tools/nidusctl -config config.yaml addressbook create alice contacts ``` Users can also be created/removed via the web UI (`/web/`) once logged in as an existing user — see **Web UI** below. --- ## Docker ```bash # Build and start docker compose up --build # Or build manually docker build -t davserver . docker run -p 8080:8080 \ -v ./config.yaml:/app/config.yaml:ro \ -v dav-data:/app/data \ davserver ``` The image also ships `nidusctl`, so once the container is running you can create your first user (and their calendars/address books) with `docker compose exec` — no need to install Go locally: ```bash docker compose exec davserver nidusctl -config /app/config.yaml user create alice \ --display-name "Alice Smith" --email alice@example.com # (prompts for a password; use --password to skip the prompt, e.g. in scripts) docker compose exec davserver nidusctl -config /app/config.yaml calendar create alice personal docker compose exec davserver nidusctl -config /app/config.yaml addressbook create alice contacts ``` ### Pre-built images Pushing a version tag (e.g. `v1.2.3`) or publishing a release triggers [`.github/workflows/docker-release.yml`](.github/workflows/docker-release.yml), which builds and publishes a multi-arch (`linux/amd64` + `linux/arm64`) image to `git.arnef.de/arnef/nidus`, tagged with the version, `.`, `latest`, and the short commit SHA. It authenticates via the `REGISTRY_USERNAME`/`REGISTRY_PASSWORD` repository secrets. A minimal standalone `docker-compose.yml` that pulls this image instead of building from a checkout — just fetch `config.example.yaml`, copy it to `config.yaml`, and adjust it to your needs: ```yaml services: davserver: image: git.arnef.de/arnef/nidus:latest ports: - "8080:8080" volumes: - ./config.yaml:/app/config.yaml:ro - dav-data:/app/data restart: unless-stopped healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"] interval: 30s timeout: 5s retries: 3 volumes: dav-data: ``` --- ## API endpoints | Path | Description | |------|-------------| | `/.well-known/caldav` | Redirects to `/cal/` | | `/.well-known/carddav` | Redirects to `/card/` | | `/cal/` | CalDAV principal (same URL for every user; resolved via Basic Auth) | | `/cal/home/` | Calendar home-set (lists the user's calendars) | | `/cal/home//` | Calendar collection | | `/card/` | CardDAV principal (same URL for every user; resolved via Basic Auth) | | `/card/home/` | Address book home-set (lists the user's address books) | | `/card/home//` | Address book collection | | `/files/` | WebDAV file storage (same URL for every user; resolved via Basic Auth) | | `/healthz` | Health check (unauthenticated) | > **Note:** the `home` segment is a fixed literal (not a username or real > resource) — it exists only to give the calendar/address-book home-set the > path depth that the underlying CalDAV/CardDAV library expects when > classifying resources by URL. Clients should never need to construct > these URLs by hand; they're discovered automatically via > `.well-known` + `current-user-principal` + `calendar-home-set` / > `addressbook-home-set` properties. --- ## Sharing calendars and address books A user can grant another user read or write access to one of their own calendars or address books. Shared resources show up automatically in the grantee's own home-set alongside their own calendars — no separate account or extra client configuration needed. Sharing grants are stored in a small SQLite database at `/nidus.db` (not in `config.yaml`) and can be managed either via the `nidusctl` CLI or the web UI's dashboard (see below): ```bash # Give bob write access to alice's "work" calendar go run ./tools/nidusctl -config config.yaml calendar share alice work bob write # List everyone alice's "work" calendar is shared with go run ./tools/nidusctl -config config.yaml calendar shares alice work # Revoke access go run ./tools/nidusctl -config config.yaml calendar unshare alice work bob # Address books work the same way, using "addressbook" instead of "calendar" go run ./tools/nidusctl -config config.yaml addressbook share alice contacts bob read ``` Or via `make`: `make nidusctl ARGS="calendar share alice work bob write"`. A calendar that `alice` shares with `bob` appears in bob's calendar home-set as `/cal/home/alice~work/` (i.e. `~`) — the data itself still physically lives under alice's own storage; nothing is copied. The same scheme applies to address books under `/card/home/`. Read-only shares reject any write (PUT/DELETE) with `403 Forbidden`. --- ## Web UI A small server-rendered app is served at `/web/` (separate from the DAV endpoints, which stay on HTTP Basic Auth), and works on both desktop and mobile browsers: - **Login** (`/web/login`) — cookie-based session, stored server-side in `nidus.db` (`web_sessions` table), independent of DAV Basic Auth. Includes a "Show/Hide" password toggle to rule out typos before submitting. - **Dashboard** (`/web/`) — create/delete your own calendars, address books, and ICS/webcal subscriptions; see who your resources are shared with and what others have shared with you; manage sharing grants directly (same effect as `nidusctl`) — updates happen in place via [htmx](https://htmx.org/) without a full page reload. - **Files** (`/web/files/`) — a browser for the same storage the WebDAV endpoint (`/files/`) serves: navigate folders, create new folders, upload files/folders (including via drag & drop), download, and delete files or folders. Files open **inline** in the browser when the type supports it (video, audio, images, PDF, …) instead of always forcing a download; a separate "Download" action is always available to force a save-as. - **Contacts** (`/web/contacts/`) — browse address books, create/edit/ delete contacts (name, organization, birthday, phone numbers, emails, addresses, photo), and import/export vCards (`.vcf`). - **Calendar** (`/web/calendar`) — month and week views across all your own and shared calendars, create/edit/delete events, per-calendar colors, and import/export `.ics` files. - **Account** (`/web/account`) — update your display name/email and change your password. - **Logout** (`/web/logout`). Implementation: [templ](https://templ.guide/) for type-safe Go HTML templates, [Tailwind CSS v4](https://tailwindcss.com/) for styling, htmx for the sprinkles of dynamic behavior (form submission via POST/DELETE, partial page swaps), and TypeScript (compiled to plain JS, `web/ts/`) for the few bits of client-side-only logic (e.g. the password-visibility toggle, file drag & drop) — no separate JS framework needed. The compiled CSS, compiled JS, and the htmx bundle are all embedded into the Go binary (`web/staticassets.go`), so no Node.js is required at runtime, only when you change styles, templates, or TypeScript during development: ```bash make web-deps # once, installs the Tailwind CLI + TypeScript compiler (needs Node.js/npm) make web-assets # regenerate templ code + rebuild web/static/app.css and web/static/*.js ``` --- ## Configuration reference ```yaml server: host: "0.0.0.0" port: 8080 base_url: "https://dav.example.com" # used in DAV responses auth: realm: "My DAV Server" storage: data_dir: "./data" # all user data lives here logging: level: "info" # debug | info | warn | error format: "text" # text | json tls: enabled: false cert_file: "" key_file: "" ``` Users, calendars, and address books are managed via `nidusctl`, not `config.yaml` — see **Managing users** below. ## Managing users All user/calendar/address-book management is done with `nidusctl` (or the web UI). Nothing is stored in `config.yaml` anymore. ```bash # Users nidusctl user create [--display-name NAME] [--email EMAIL] [--password PW] nidusctl user delete nidusctl user list nidusctl user passwd [--password PW] # Calendars nidusctl calendar create [--color '#RRGGBB'] nidusctl calendar color nidusctl calendar delete nidusctl calendar list nidusctl calendar share nidusctl calendar unshare nidusctl calendar shares # Address books nidusctl addressbook create nidusctl addressbook delete nidusctl addressbook list nidusctl addressbook share nidusctl addressbook unshare nidusctl addressbook shares ``` Passwords are prompted for interactively (masked, double-entry) when `--password` is omitted. The web UI (`/web/`) also lets a logged-in user create/delete their own calendars and address books from the dashboard. > **Upgrading from an older version?** The `users:` section in > `config.yaml` is no longer read. Recreate your users with > `nidusctl user create` (and their calendars/address books) — there is no > automatic migration from the old config format. --- ## Project layout ``` caldav-server/ ├── cmd/server/ # main entrypoint ├── internal/ │ ├── auth/ # HTTP Basic Auth middleware (DAV endpoints) │ ├── caldav/ # CalDAV backend │ ├── carddav/ # CardDAV backend │ ├── config/ # YAML config loader │ ├── db/ # SQLite store (shares, web UI sessions) │ ├── store/ # filesystem storage layer │ ├── web/ # web UI (cookie sessions, dashboard, share mgmt) │ │ └── templates/ # templ templates (+ generated *_templ.go) │ └── webdav/ # WebDAV file handler ├── tools/hashpwd/ # bcrypt password hasher CLI ├── tools/nidusctl/ # sharing-grant admin CLI ├── web/ # front-end assets: Tailwind input/config, static/ │ └── static/ # compiled app.css + htmx.min.js (embedded into the binary) ├── config.example.yaml # sample configuration (copy to config.yaml) ├── Dockerfile ├── docker-compose.yaml └── Makefile ``` --- ## Running tests ```bash make test # or go test ./... -race ``` --- ## A note on AI assistance Large parts of this project's code and documentation were written with the help of AI coding assistants (e.g. GitHub Copilot). Changes are reviewed and tested where practical, but not every part of the codebase has been fully reviewed yet — use accordingly, especially before relying on this in security-sensitive environments.