Files
nidus/README.md
T
arnefandCopilot ab3c7f44d5 Add web UI: login, dashboard, and share management (templ + Tailwind + htmx)
New internal/web package mounted at /ui/, separate from DAV Basic Auth:

- Cookie-based sessions (opaque random tokens in a new web_sessions
  SQLite table, internal/db/sessions.go), checked against the same
  cfg.Users/bcrypt credentials as DAV Basic Auth.
- Dashboard listing the logged-in user's own calendars/address books,
  who they're shared with, and what's shared with them.
- Share/unshare directly from the dashboard, updated in place via htmx
  partial swaps (POST to create/update, DELETE to revoke). Always
  verifies the resource actually belongs to the logged-in user before
  granting a share.
- Templates written in templ (internal/web/templates/*.templ, generated
  *_templ.go committed), styled with Tailwind CSS v4 (web/input.css,
  compiled to web/static/app.css), with htmx vendored as a static file
  for the dynamic bits. Both are embedded into the binary at build time
  (web/staticassets.go) so the compiled server has no Node.js/web/
  runtime dependency.
- Wired into cmd/server/main.go at /ui/ alongside the existing /cal/,
  /card/, /files/ routes; welcome page links to it.
- Tests: internal/web/server_test.go covers login success/failure, the
  login-required redirect, dashboard rendering, share/unshare including
  the htmx-v2-sends-DELETE-params-as-query-string quirk, and rejecting
  shares of resources the user doesn't own.
- Docs: README (new 'Web UI' section, updated sharing section, project
  layout, dependencies) and copilot-instructions updated accordingly.
  Makefile: new templ-generate/web-deps/web-css targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-19 07:12:56 +02:00

326 lines
9.5 KiB
Markdown

# DAV Server
A self-hosted **CalDAV**, **CardDAV**, and **WebDAV** server written in Go.
## 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 small dashboard (login, manage shares) at `/ui/`, 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. Generate password hashes
```bash
go run ./tools/hashpwd mysecretpassword
# Outputs: $2b$12$...
```
### 3. Create your `config.yaml`
Copy the example config and edit it — `config.yaml` is git-ignored so your
real credentials/domain never get committed:
```bash
cp config.example.yaml config.yaml
```
Replace the placeholder hashes with your real bcrypt hashes:
```yaml
users:
alice:
password: "$2b$12$<hash generated above>"
display_name: "Alice Smith"
email: "alice@example.com"
calendars:
- personal
- work
address_books:
- contacts
```
### 4. Run the server
```bash
make run
# or
go run ./cmd/server -config config.yaml
```
The server starts at **http://localhost:8080**.
---
## 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
```
---
## Client configuration
### Apple Calendar / Contacts (macOS / iOS)
1. Go to **Settings → Calendar → Accounts → Add Account → Other → Add CalDAV Account**
2. Enter:
- **Server**: `http://yourserver:8080`
- **Username**: `alice`
- **Password**: your plaintext password
3. The app will auto-discover calendars at `/cal/`.
Same flow for **CardDAV** with Contacts app.
### Thunderbird
1. Install the **TbSync** add-on + **CalDAV & CardDAV** provider
2. Add a new account and point it at `http://yourserver:8080/.well-known/caldav`
### GNOME Calendar / Evolution
Use the GNOME Online Accounts panel:
- **Server**: `http://yourserver:8080`
- Check *CalDAV* / *CardDAV* as appropriate
---
## 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>/` | 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/<book>/` | 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
`<data_dir>/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. `<owner>~<calendar name>`) — 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 dashboard is served at `/ui/` (separate from the
DAV endpoints, which stay on HTTP Basic Auth):
- **Login** (`/ui/login`) — cookie-based session, stored server-side in
`nidus.db` (`web_sessions` table), independent of DAV Basic Auth.
- **Dashboard** (`/ui/`) — lists your own calendars/address books, who
they're shared with, and any resources other users have shared with you.
- **Share management** — add/remove shares directly from the dashboard
(same effect as `nidusctl`); updates happen in place via
[htmx](https://htmx.org/) without a full page reload.
- **Logout** (`/ui/logout`).
Implementation: [templ](https://templ.guide/) for type-safe Go HTML
templates, [Tailwind CSS v4](https://tailwindcss.com/) for styling, and
htmx for the sprinkles of dynamic behavior (form submission via
POST/DELETE, partial page swaps) — no separate JS build/framework needed.
The compiled CSS and the htmx bundle are embedded into the Go binary
(`web/staticassets.go`), so no Node.js is required at runtime, only when
you change styles or templates during development:
```bash
make web-deps # once, installs the Tailwind CLI (needs Node.js/npm)
make web-css # regenerate templ code + rebuild web/static/app.css
```
---
## TLS / Reverse proxy
### Self-signed certificate (development)
```bash
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```
Update `config.yaml`:
```yaml
tls:
enabled: true
cert_file: cert.pem
key_file: key.pem
```
### Caddy reverse proxy (recommended for production)
```
dav.example.com {
reverse_proxy localhost:8080
}
```
---
## 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:
<username>:
password: "<bcrypt hash>"
display_name: "Full Name"
email: "user@example.com"
calendars: # pre-created calendar names
- personal
address_books: # pre-created address book names
- contacts
```
---
## 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
```
---
## Dependencies
| Package | Purpose |
|---------|---------|
| `github.com/emersion/go-webdav` | WebDAV/CalDAV/CardDAV protocol layer |
| `github.com/emersion/go-ical` | iCalendar parsing/serialisation |
| `github.com/emersion/go-vcard` | vCard parsing/serialisation |
| `golang.org/x/crypto` | bcrypt |
| `golang.org/x/net` | `golang.org/x/net/webdav` |
| `gopkg.in/yaml.v3` | YAML config parsing |
| `modernc.org/sqlite` | Pure-Go SQLite driver (shares, web UI sessions) |
| `github.com/a-h/templ` | Type-safe Go HTML templates (web UI) |
Front-end (dev-only, not required at runtime — see [Web UI](#web-ui)):
| Tool | Purpose |
|------|---------|
| Tailwind CSS v4 (`web/package.json`) | Utility-first CSS, compiled to `web/static/app.css` |
| [htmx](https://htmx.org/) (`web/static/htmx.min.js`, vendored) | Partial page updates without a JS framework |