Ctrl K
ComunidadGuías…

README.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
Davis
---

[![Build Status](https://github.com/tchapi/davis/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/tchapi/davis/actions/workflows/ci.yml)
[![Publish Docker image](https://github.com/tchapi/davis/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/tchapi/davis/actions/workflows/main.yml)
[![Latest release](https://img.shields.io/github/v/release/tchapi/davis?cacheSeconds=3600)](https://github.com/tchapi/davis/releases)
[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/tchapi/davis/blob/main/LICENSE)
![Platform](https://img.shields.io/badge/platform-amd64%20%7C%20arm64-blue?logo=docker)
![PHP Version](https://img.shields.io/badge/php-8.2%20%7C%208.3%20%7C%208.4-777BB4?logo=php&logoColor=white)
[![Sponsor me](https://img.shields.io/badge/sponsor%20me-🙏-blue?logo=paypal)](https://paypal.me/tchap)

A modern, simple, feature-packed, fully translatable DAV server, admin interface and frontend based on `sabre/dav`, built with [Symfony 7](https://symfony.com/) and [Bootstrap 5](https://getbootstrap.com/), initially inspired by [Baïkal](https://github.com/sabre-io/Baikal) (_see dependencies table below for more detail_)

### Web admin dashboard

Provides user edition, calendar creation and sharing, and address book creation. The interface is simple and straightforward, responsive, and provides a light and a dark mode.

Supports **Basic authentication**, as well as **IMAP** and **LDAP** (_via external providers_).

### DAV Server

The underlying server implementation supports (*non-exhaustive list*) CalDAV, CardDAV, WebDAV, calendar sharing, scheduling, mail notifications, and server-side subscriptions (*depending on the capabilities of the client*).

### Additional features ✨

- Subscriptions (to be added via the client, such as the macOS calendar, for instance)
- Public calendars, available to anyone with the link
- Automatic birthday calendar, updated on the fly when birthdates change in your contacts

### Deployment

Easily containerisable (_`Dockerfile` and sample `docker-compose` configuration file provided_).

NixOS [package](https://search.nixos.org/packages?channel=unstable&show=davis&from=0&size=50&sort=relevance&type=packages&query=davis) and module available.

Comes with already built Docker images in two flavours: [standalone](https://github.com/tchapi/davis/pkgs/container/davis-standalone) (with included Caddy reverse proxy) or [barebone](https://github.com/tchapi/davis/pkgs/container/davis).

- - -

✨ Created and maintained (with the help of the community) by [@tchapi](https://github.com/tchapi). ✨

![Dashboard page](_screenshots/dashboard.png)
![User creation page](_screenshots/user.png)
![Sharing page](_screenshots/sharing.png)

| Dark / Light mode  | Useful information at hand        |
|--------------------|----------------------------|
| ![Color mode](_screenshots/mode.png)| ![Setup information](_screenshots/setup_info.png)|

# 🔩 Requirements

  - PHP > 8.2 (with `pdo_mysql` [or `pdo_pgsql`, `pdo_sqlite`], `gd` and `intl` extensions), compatible up to PHP 8.5 (_See dependencies table below_)
  - A compatible database layer, such as MySQL or MariaDB (recommended), PostgreSQL (not extensively tested yet) or SQLite (not extensively tested yet)
  - Composer > 2 (_The last release compatible with Composer 1 is [v1.6.2](https://github.com/tchapi/davis/releases/tag/v1.6.2)_)
  - The [`imap`](https://www.php.net/manual/en/imap.installation.php) and [`ldap`](https://www.php.net/manual/en/ldap.installation.php) PHP extensions if you want to use either authentication methods (_these are not enabled / compiled by default except in the Docker image_)

Dependencies
------------

| Release            | Status                     | PHP version        |
|--------------------|----------------------------|--------------------|
| `main` (edge)      | development branch         | PHP 8.2+           |
| `v5.x`             | stable                     | PHP 8.2+           |
| `v4.x`             | security fixes only        | PHP 8.0 → 8.3      |
| `v3.x`             | :warning: unmaintained     | PHP 7.3 → 8.2      |

# 🧰 Installation

0. Clone this repository

1. Retrieve the dependencies:

    a. If you plan to run Davis locally, for development purposes

    ```
    composer install
    ```

    b. If you plan to run Davis on production

    ```
    composer install --no-dev --optimize-autoloader
    ```

   And set `APP_ENV=prod` in your `.env.local` file (see below)

2. Generate a unique `APP_SECRET` and set it in your `.env.local` file (or as a real environment variable, see below):

    ```
    openssl rand -hex 16
    ```

    > [!CAUTION]
    >
    > The `.env` file committed in this repository ships a default `APP_SECRET`. It is the same for every installation, so you **must** override it. This value is not used to encrypt any data: you can change it at any time without losing anything.

3. At least put the correct credentials to your database (driver and url) in your `.env.local` file so you can easily create the necessary tables.

4. Run the migrations to create all the necessary tables:

    ```
    bin/console doctrine:migrations:migrate
    ```

**Davis** can also be used with a pre-existing MySQL database (_for instance, one previously managed by Baïkal_). See the paragraph "Migrating from Baikal" for more info.

> [!NOTE]
>
> The tables are not _exactly_ equivalent to those of Baïkal, and allow for a bit more room in columns for instance (among other things)

## Configuration

Create your own `.env.local` file to change the necessary variables, if you plan on using `symfony/dotenv`.

> [!NOTE]
>
> If your installation is behind a web server like Apache or Nginx, you can setup the env vars directly in your Apache or Nginx configuration (see below). Skip this part in this case.

> [!CAUTION]
>
> In a production environnement, the `APP_ENV` variable MUST be set to `prod` to prevent leaking sensitive data, and `APP_SECRET` MUST be set to a unique value (see the installation steps above).

**a. The database driver and url** (_you should already have it configured since you created the database previously_)

```shell
DATABASE_DRIVER=mysql # or postgresql, or sqlite
DATABASE_URL=mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4
```

**b. The admin password for the backend**

```shell
ADMIN_LOGIN=admin
ADMIN_PASSWORD=test
```

> [!NOTE]
>
> You can bypass auth entirely if you use a third party authorization provider such as Authelia. In that case, set the `ADMIN_AUTH_BYPASS` env var to `true` (case-sensitive, this is actually the string `true`, not a boolean) to allow full access to the dashboard. This does not change the behaviour of the DAV server.

**c. The auth Realm and method for HTTP auth**

```shell
AUTH_REALM=SabreDAV
AUTH_METHOD=Basic # can be "Basic", "IMAP" or "LDAP"
```
> See [the following paragraph](#specific-environment-variables-for-imap-and-ldap-authentication-methods) for more information if you choose either IMAP or LDAP.

> [!WARNING]
>
> **Do not change `AUTH_REALM` on an existing installation.** Davis stored passwords as
> `md5(username:AUTH_REALM:password)` until v1.10.0 (September 2021), and a database imported from
> Baïkal uses the same scheme. The realm is part of those hashes, so changing it makes every one of
> them stop working, with no error beyond a failed login — the accounts are still there, they simply
> cannot authenticate any more.
>
> Passwords set since v1.10.0 use bcrypt and are unaffected. You can still be carrying legacy ones
> without ever having used Baïkal, if the account predates that version. To find out:
>
> ```sql
> SELECT username FROM users WHERE digesta1 NOT LIKE '$2y$%';
> ```
>
> Any row returned is a legacy hash tied to the current realm.
>
> There is no password reset in Davis. If you must change the realm, the only way back is to open
> each of those accounts in the dashboard and type a new password: the field is blank on the edit
> page and leaving it blank keeps the current hash, so filling it in is what replaces it. The new
> one is stored with bcrypt and no longer depends on the realm. Your own admin login is unaffected —
> it comes from `ADMIN_LOGIN` / `ADMIN_PASSWORD`, not from the users table.

**App keys**

```shell
APP_KEYS_ENABLED=false
```

App keys are off by default. To use them, set `APP_KEYS_ENABLED=true`. When they are off, the app keys page, the `davis:app-key:*` commands and the app key login are not available. Existing keys stay in the database and work again if you turn the setting back on.

A service that already knows who the user is (a webmail behind SSO, a scheduling tool like cal.com) can read and write a user's calendars and address books without holding that user's password. An admin creates an app key from the dashboard (or with `bin/console davis:app-key:create`), scoped to some users or to all of them, and the service logs in over HTTP Basic as `<login>*<app_id>` with the key's secret as the password, Dovecot style. Davis then acts as `<login>`.

You must say which users the key can act as. List their usernames (they must already exist in Davis), or use `*` for all users (`--all-users` on the command line). `*` does not include admins, because an admin can read every calendar and address book. To let a key act as an admin, add the admin's username to the list.

When logged in as `alice`, the key has the same access as alice. This includes calendars that other users shared with her. If she is a delegate for another user, it also includes that user's calendars.

When you delete a user from the dashboard, Davis removes them from the keys that list them. If a key was only for that user, the key is revoked. Keys with `*` are not changed. With IMAP or LDAP, removing someone from the directory does not delete their Davis account, so their keys keep working until you delete the account or revoke the keys.

Davis records which users each key has acted as, with the first and last time. You can see this on the app keys page and with `davis:app-key:list`.

It works with every `AUTH_METHOD` and never contacts the backend: a wrong secret is refused outright, and a username ending in `*<something that is not an app id>` is sent to the backend as is. The login must be an existing Davis account, with exactly the same username. An app key never creates an account, even with IMAP or LDAP and `*_AUTH_USER_AUTOCREATE`, because it never asks the IMAP or LDAP server if the user is real. The account must already exist.

The secret is shown once at creation and stored hashed. Keep it on the server side, never in a browser, and revoke the key (dashboard, or `davis:app-key:revoke <app_id>`) when the service goes away; an expiry date can be set when the access is meant to be temporary. This is the use case of [#183](https://github.com/tchapi/davis/issues/183).

Revoked keys stay listed. To remove one for good, use `davis:app-key:delete <app_id>` (an active key must be revoked first). To clean up in bulk, `davis:app-key:prune <days>` deletes, in one go, every key revoked (or expired) at least `<days>` days ago; `--dry-run` shows what would go. A key with an expiry date still ahead is active and is never deleted by either command. Deleting a key also deletes its usage history, so pick `<days>` according to how long you want to keep it.

Davis does not schedule the prune itself. If you want it, add a cron job, for example every night at 3am with a 90 days retention:

```shell
0 3 * * * cd /path/to/davis && bin/console davis:app-key:prune 90 -q
```

or, with the docker image:

```shell
0 3 * * * docker exec davis sh -c "APP_ENV=prod bin/console davis:app-key:prune 90 -q"
```

**Update check**

```shell
UPDATE_CHECK_ENABLED=false
```

Davis does not phone home. Set `UPDATE_CHECK_ENABLED=true` to have the [diagnostics page](#start-with-the-diagnostics-page) ask GitHub whether a newer release exists, and link to its release notes when there is one. The dashboard then counts it among the points that need your attention. GitHub (`api.github.com`) is asked at most once a day, anonymously, and only when you open the dashboard; nothing about your install is sent. If GitHub cannot be reached, the check says so and is tried again an hour later.

**d. The global flags to enable CalDAV, CardDAV and WebDAV**. You can also disable the option to have calendars public

```shell
CALDAV_ENABLED=true
CARDDAV_ENABLED=true
WEBDAV_ENABLED=false

PUBLIC_CALENDARS_ENABLED=true
```

> [!NOTE]
>
> By default, `PUBLIC_CALENDARS_ENABLED` is true. That doesn't mean that all calendars are public by default — it just means that you have an option, upon calendar creation, to set the calendar public (but it's not public by default).


**e. Mailer configuration**

It includes:
  - the mailer uri (`MAILER_DSN`)
  - The email address that your invites are going to be sent from

```shell
MAILER_DSN=smtp://user:pass@smtp.example.com:port
INVITE_FROM_ADDRESS=no-reply@example.org
```

> [!WARNING]
> If the username, password or host contain any character considered special in a URI (such as `: / ? # [ ] @ ! $ & ' ( ) * + , ; =`), you MUST encode them.
> See [here](https://symfony.com/doc/current/mailer.html#transport-setup) for more details.

If `INVITE_FROM_ADDRESS` is empty, Davis does not send scheduling emails at all and logs a warning when the DAV endpoint starts.

Invitations are only produced for events whose `ORGANIZER` matches the organiser's own address, and that address is the `email` of their principal — the *Email* field of the account in the dashboard. If it is empty or different from what the calendar client sends, the server has nothing to send an invitation about and stays silent. To find accounts in that state:

```sql
SELECT uri, email FROM principals WHERE email IS NULL OR email = '';
```

To check the mailer itself without creating an event:

```shell
php bin/console davis:mail:test you@example.org
```


**f. The reminder offset for all birthdays**

You must specify a relative duration, as specified in [the RFC 5545 spec](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.6)

```shell
BIRTHDAY_REMINDER_OFFSET=PT9H
```

If you don't want a reminder for birthday events, set it to the `false` value (lowercase):

```shell
BIRTHDAY_REMINDER_OFFSET=false
```

> [!NOTE]
>
> By default, if the env var is not set or empty, we use `PT9H` (9am on the date of the birthday).

**g. The paths for the WebDAV installation**

> [!TIP]
>
> I recommend that you use absolute directories so you know exactly where your files reside.

```shell
WEBDAV_TMP_DIR=/webdav/tmp
WEBDAV_PUBLIC_DIR=/webdav/public
WEBDAV_PUBLIC_DIR_WRITABLE=false
WEBDAV_HOMES_DIR=
```

> [!NOTE]
>
> The public directory (served at `/dav/public`) is readable by every authenticated user. By default only users flagged as admins in the dashboard can create, modify or delete files in it; set `WEBDAV_PUBLIC_DIR_WRITABLE=true` to let every authenticated user write to it. The Diagnostics page of the dashboard shows which of the two applies.

> [!IMPORTANT]
>
> Up to Davis 5.4 included, every authenticated user could write to the public directory. If you relied on that (a shared drop folder), set `WEBDAV_PUBLIC_DIR_WRITABLE=true` when upgrading, otherwise your users will get a `403` when saving files there.

> [!NOTE]
>
> The directories must be absolute paths and must not live inside the web root (Davis refuses to start the DAV server otherwise). The tmp dir and the homes dir must not be inside the public dir either, and vice versa.

> [!NOTE]
>
> In a docker setup, I recommend setting `WEBDAV_TMP_DIR` to `/tmp`.

> [!NOTE]
>
> By default, home directories are disabled totally (the env var is set to an empty string). If needed, use a folder that is **NOT** a child of the public dir, such as `/webdav/homes` for instance, so that users cannot access other users' homes: Davis checks this and refuses to start the DAV server otherwise.

**h. The log file path**

You can use an absolute file path here, and you can use Symfony's `%kernel.logs_dir%` and `%kernel.environment%` placeholders if needed (as in the default value). Setting it to `/dev/null` will disable logging altogether.

```shell
LOG_FILE_PATH="%kernel.logs_dir%/%kernel.environment%.log"
```

**i. The timezone you want for the app**

This must comply with the [official list](https://www.php.net/manual/en/timezones.php)

```shell
APP_TIMEZONE=Australia/Lord_Howe
```

> Set a void value like so:
> ```shell
> APP_TIMEZONE=
> ```
> in your environment file if you wish to use the **actual default timezone of the server**, and not enforcing it.

**j. Trusting forwarded headers**

If you're behind one or several proxies, the TLS termination might be upstream and the application might not be aware of the HTTPS context. In order for urls to be generated with the correct scheme, you should indicate that you trust the chain of proxies until the TLS termination one. You can use the Symfony mechanism for that (see [documentation](https://symfony.com/doc/7.2/deployment/proxies.html) for possible values):

```shell
SYMFONY_TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR
```

> [!WARNING]
>
> **If your PHP was built without IPv6** (`--disable-ipv6`, as some source-based distributions do),
> your proxy must connect to Davis over **IPv4**, or every request returns a 500 with
> `RuntimeException: Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6"`.
>
> Symfony picks its IPv4/IPv6 comparison from the address the request *came from*, not from the
> value you set here, so listing only IPv4 proxies does **not** avoid it. In practice this means
> writing the proxy target as `127.0.0.1` rather than `localhost`, which usually resolves to `::1`
> first:
>
> ```apache
> ProxyPass / http://127.0.0.1:9000/
> ```
>
> Unsetting `SYMFONY_TRUSTED_PROXIES` also avoids the error, but then Davis no longer sees the
> `X-Forwarded-*` headers and generates URLs with the wrong scheme or host.

#### Overriding the dotenv (`.env`) path

You can override the expected location of the environment files (`.env`, `.env.local`, etc) by setting the `ENV_DIR` variable.

The value should be to a _folder_ containing the env files. This value must be specified in the actual environment and *not* in an `.env` file as it is read and evaluated **before** the env files are read.

For instance, you can use it to call `bin/console` with a specific dotenv directory:

```shell
> ENV_DIR=/var/lib/davis bin/console
```

Or use it directly in the Apache configuration

```apache
<VirtualHost *:80>
    # .. rest of config (see ¶ below)

    SetEnv ENV_DIR /var/lib/davis
    # ... other env vars if needed
</VirtualHost>
```

### Specific environment variables for IMAP and LDAP authentication methods

In case you use the `IMAP` auth type, you must specify the auth url (_the "mailbox" url_) in `IMAP_AUTH_URL` as `host:port`, the encryption method (SSL, TLS or None) and whether the certificate should be validated.

You should also explicitely define whether you want new authenticated users to be created upon login:

```shell
IMAP_AUTH_URL=imap.mydomain.com:993
IMAP_ENCRYPTION_METHOD=ssl # ssl, tls or false
IMAP_CERTIFICATE_VALIDATION=true
IMAP_AUTH_USER_AUTOCREATE=true # false by default
```

Same goes for LDAP, where you must specify the LDAP server url, the DN pattern, the Mail attribute, as well as whether you want new authenticated users to be created upon login (_like for IMAP_):

```shell
LDAP_AUTH_URL=ldap://127.0.0.1:3890 # default LDAP port
LDAP_DN_PATTERN=uid=%u,ou=users,dc=domain,dc=com
LDAP_MAIL_ATTRIBUTE=mail
LDAP_AUTH_USER_AUTOCREATE=true # false by default
LDAP_CERTIFICATE_CHECKING_STRATEGY=try # try by default. Other values are: never, hard, demand or allow
```

> Ex: for [Zimbra LDAP](https://zimbra.github.io/adminguide/latest/#zimbra_ldap_service), you might want to use the `zimbraMailDeliveryAddress` attribute to retrieve the principal user email:
>    ```shell
>    LDAP_MAIL_ATTRIBUTE=zimbraMailDeliveryAddress
>    ```

## Migrating from Baïkal?

If you're migrating from Baïkal, then you will likely want to do the following :

1. Get a backup of your data (without the `CREATE`  statements, but with complete `INSERT`  statements):

    ```shell
    mysqldump -u root -p --no-create-info --complete-insert baikal > baikal_to_davis.sql # baikal is the actual name of your database
    ```

2. Create a new database for Davis (let's name it `davis`) and create the base schema:

    ```shell
    bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20191030113307' --no-interaction
    ```

3. Reimport the data back:

    ```
    mysql -uroot -p davis < baikal_to_davis.sql
    ```

4. Run the necessary remaining migrations:

    ```
    bin/console doctrine:migrations:migrate
    ```

> [!NOTE]
> Some details / steps to resolve are also available in https://github.com/tchapi/davis/issues/226.

## Updating (without Docker)

Davis is not distributed as a bundled archive: the release assets are the plain source tree, without `vendor/`. Updating therefore means fetching the new code and re-running the same steps as the installation. The whole procedure takes a minute; **the part that matters is reading the release notes**.

> [!CAUTION]
> **Do not automate updates with cron.** Some releases need a human: v5.3.0 changed the `IMAP_AUTH_URL` format, v5.4.0 changed the dashboard URLs, and major versions (v3, v4, v5) each had their own migration notes. Every release has a *How to upgrade* section on [the releases page](https://github.com/tchapi/davis/releases) — read it first.

### 0. Prerequisites

- Run all commands below **as the user that owns the Davis directory** (ideally the same user PHP-FPM runs as). Running them as `root` leaves `var/cache` and `vendor/` unreadable or unwritable for the web server.
- If you keep your configuration in the web server (`SetEnv`, `fastcgi_param`) or in another folder (`ENV_DIR`) rather than in `.env.local`, export the same values for the shell session, otherwise the CLI falls back to the `dev` defaults of `.env`:

  ```shell
  export APP_ENV=prod
  export ENV_DIR=/var/lib/davis   # only if you use it
  # export DATABASE_DRIVER=... DATABASE_URL=... if they are not in an env file either
  ```

- Check where you are: `git describe --tags` (git install).

### 1. Back up the database

Migrations can go wrong (even though they are generally reversible). Back up **before** touching anything:

```shell
# MySQL / MariaDB
mysqldump -u davis -p davis > davis-$(date +%F).sql
# PostgreSQL
pg_dump -U davis davis > davis-$(date +%F).sql
# SQLite (safe while the server is running)
sqlite3 var/data.db ".backup davis-$(date +%F).db"
```

Also keep a copy of `.env.local` if you have one.

### 2. Update the code

**a. If you cloned the repository**

```shell
cd /var/www/davis
git status                     # must be clean: local edits are not supported and may block the checkout
git fetch --tags
git checkout vX.Y.Z            # always a release tag — never `main`, which is the unstable edge branch
```

To see the newest tag: `git tag --sort=-v:refname | head -n 1`. `.env.local`, `var/` and `vendor/` are untracked and survive the checkout.

**b. If you installed from a release archive**

Unpack the new archive **next to** the current install, not over it, then move your state into it:

```shell
cd /var/www
tar xzf vX.Y.Z.tar.gz                # creates davis-X.Y.Z/
cp davis/.env.local davis-X.Y.Z/     # if you use one
mv davis/var davis-X.Y.Z/var         # logs, cache, and the SQLite database if you use the default path
mv davis davis-old && mv davis-X.Y.Z davis
```

### 3. Install the dependencies

```shell
composer install --no-dev --optimize-autoloader
```

This also clears the cache and installs the public assets (`cache:clear` and `assets:install` run automatically after install, see `composer.json`). Use plain `composer install` only for a development setup with `APP_ENV=dev`.

### 4. Migrate the database

```shell
bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
```

`--allow-no-migration` makes the command succeed when the release contains no migration, so the same command works for every update.

> [!NOTE]
> Coming from **before v3.0.0**? Follow the v3.0.0 release notes first: `doctrine:migrations:migrate` alone will not work.

### 5. Reload PHP (OPcache)

If `opcache.validate_timestamps=0` in your PHP configuration (a common production setting), the old code stays in memory until PHP-FPM is reloaded:

```shell
sudo systemctl reload php8.4-fpm   # adjust to your PHP version / service name
```

### 6. Check

Open `/dashboard/diagnostics`. It reports pending migrations, the database engine in use and the state of each endpoint — if everything is green, you're done. The log, if any, is in `var/log/prod.log`.

### Rolling back

Code: `git checkout <previous tag>` (or swap the directories back for an archive install), then `composer install --no-dev --optimize-autoloader` again. Database: restore the backup from step 1.

### Being notified of new releases

On GitHub, use *Watch → Custom → Releases*, or subscribe to the feed at `https://github.com/tchapi/davis/releases.atom`. Alternatively, set `UPDATE_CHECK_ENABLED=true` (see [Configuration](#configuration)) and the diagnostics page tells you when a newer release exists. Davis never checks on its own unless you turn that on, and never installs anything.

# 🌐 Access / Webserver

A simple status page is available on the root `/` of the server.

The administration interface is available at `/dashboard`. You need to login to use it (See `ADMIN_LOGIN` and `ADMIN_PASSWORD` env vars).

The main endpoint for CalDAV, WebDAV or CardDAV is at `/dav`.

> [!TIP]
>
> For shared hosting, the `symfony/apache-pack` is included and provides a standard `.htaccess` file in the public directory so redirections should work out of the box.

## API Endpoint

For user and calendar management there is an API endpoint. See [the API documentation](docs/api/README.md) for more information.

> [!TIP]
>
> The API endpoint requires an environment variable `API_KEY` set to a secret key that you will use in the `X-Davis-API-Token` header of your requests to authenticate. You can generate it with `bin/console api:generate`

## Webserver Configuration Examples

### Example Caddy 2 configuration

```
dav.domain.tld {
    # General settings
    encode zstd gzip
    header {
        -Server
        -X-Powered-By

        # enable HSTS
        Strict-Transport-Security max-age=31536000;

        # disable clients from sniffing the media type
        X-Content-Type-Options nosniff

        # keep referrer data off of HTTP connections
        Referrer-Policy no-referrer-when-downgrade
    }

    root * /var/www/davis/public
    php_fastcgi 127.0.0.1:8000
    file_server
}
```
### Example Apache 2.4 configuration

```apache
<VirtualHost *:80>
    ServerName dav.domain.tld

    DocumentRoot /var/www/davis/public
    DirectoryIndex /index.php

    <Directory /var/www/davis/public>
        AllowOverride None
        Order Allow,Deny
        Allow from All
        FallbackResource /index.php
    </Directory>

    # Apache > 2.4.25, else remove this part
    <Directory /var/www/davis/public/bundles>
        FallbackResource disabled
    </Directory>

    # If you proxy to Davis rather than serving it directly, target 127.0.0.1 and not localhost:
    # on a PHP built without IPv6, a request arriving from ::1 returns a 500.
    # ProxyPass / http://127.0.0.1:9000/

    # Env vars (if you did not use .env.local)
    SetEnv APP_ENV prod
    SetEnv APP_SECRET <app-secret-id>
    SetEnv DATABASE_DRIVER "mysql"
    SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4"
    # ... etc
</VirtualHost>
```

### Example Nginx configuration

```nginx
server {
    server_name dav.domain.tld;
    root /var/www/davis/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location /bundles {
        try_files $uri =404;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.4-fpm.sock; # Change for your PHP version
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        # Env vars (if you did not use .env.local)
        fastcgi_param APP_ENV prod;
        fastcgi_param APP_SECRET <app-secret-id>;
        fastcgi_param DATABASE_DRIVER "mysql";
        fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4";
        # ... etc ...

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }

    location ~ \.php$ {
        return 404;
    }
}
```

More examples and information [here](https://symfony.com/doc/current/setup/web_server_configuration.html).

## Well-known redirections for CalDAV and CardDAV

Web-based protocols like CalDAV and CardDAV can be found using a discovery service. Some clients require that you implement a path prefix to point to the correct location for your service. See [here](https://en.wikipedia.org/wiki/List_of_/.well-known/_services_offered_by_webservers) for more info.

Davis answers `/.well-known/caldav` and `/.well-known/carddav` itself and redirects them to its DAV endpoint, so **no web server configuration is needed**. Because the redirect is built from the application's own base path, it also works when Davis is installed in a sub-directory (`https://example.org/davis/`), which a hard-coded `/dav/` rewrite does not.

> [!NOTE]
>
> If your web server still rewrites these two paths itself (earlier versions of this README suggested doing so), you can remove those rules: they take precedence over Davis and will send clients to the wrong place on a sub-directory installation.

# 🐳 Dockerized installation

A `Dockerfile` is available for you to compile the image.

To build the checked out version, just run:

    docker build --pull --file docker/Dockerfile --tag davis:latest --build-arg fpm_user=82:82 .

> [!TIP]
>
> The `fpm_user` build arg allows to set:
>  - the uid FPM will run with
>  - the owner of the app folder
>
> This is helpful if you have a proxy that does not use the same default PHP Alpine uid/gid for www-data (82:82). For instance, in the docker compose file, nginx uses 101:101
>

This will build a `davis:latest` image that you can directly use. Do not forget to pass sensible environment variables to the container since the _dist_ `.env` file will take precedence if no `.env.local` or environment variable is found.

You can use `--platform` to specify the platform to build for. Currently, `arm64` (ARMv8) and `amd64` (x86) are supported.

> [!IMPORTANT]
>
> ⚠ Do not forget to run all the database migrations the first time you run the container :
>
>     docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"

## Docker images

For each release, a Docker image is built and published in the [Github package repository](https://github.com/tchapi/davis/pkgs/container/davis).

### Release images

Each release builds and tags two images: one for the standard build (no reverse-proxy) and one for the standalone build (including Caddy as a reverse-proxy). Example:

```
docker pull ghcr.io/tchapi/davis:v4.4.0
```

```
docker pull ghcr.io/tchapi/davis-standalone:v4.4.0
```

### Edge image

The edge image is generally built from the tip of the main branch, but might sometimes be used for specific branch testing:

```
docker pull ghcr.io/tchapi/davis:edge
```

> [!WARNING]
>
> The `edge` image must not be considered stable. **Use only release images for production setups**.

## Full stack

A few `docker-compose.yml` files are also included (in the `docker` folder) as minimal example setups, with various databases for instance.

You can start the containers with :

    cd docker && docker compose up -d

> [!NOTE]
>
> The default recipe above uses MariaDB.

> [!IMPORTANT]
>
> ⚠ Do not forget to run all the database migrations the first time you run the container :
>
>     docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"

> [!WARNING]
>
> For SQLite, you must also make sure that the folder the database will reside in AND the database file in itself have the right permissions! You can do for instance:
> `chown -R www-data: /data` if `/data` is the folder your SQLite database will be in, just after you have run the migrations

### Updating from a previous version

> [!TIP]
> For installations without Docker, see [Updating (without Docker)](#updating-without-docker)

If you update the code, you need to make sure the database structure is in sync.

**Before v3.0.0**, you need to force the update:

    docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:schema:update --force --no-interaction"

**For v3.0.0 and after**, you can just migrate again (_provided you correctly followed the migration notes in the v3.0.0 release_):

    docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration"


Then, head up to `http://<YOUR_DOCKER_IP>:9000` to see the status display :

![Status page](_screenshots/status.png)

> Note that there is no user and no principals created by default.

# NixOS Installation

To install Davis on NixOS, you can use the builtin NixOS module [`services.davis`](https://search.nixos.org/options?channel=unstable&query=services.davis).

Currently the NixOS module and package are in the nixos-unstable channel, but they are slated to enter the stable channel in the 24.05 release.

* [All `services.davis` options](https://search.nixos.org/options?channel=unstable&query=services.davis)
* [Basic Guide](https://nixos.org/manual/nixos/unstable/#module-services-davis)

If you encounter a bug or problem with the NixOS Davis module please open an issue [at the nixpkgs repo](https://github.com/NixOS/nixpkgs/issues/new/choose) so the module maintainers can assist.

# Development

You can spin off a local PHP webserver with:

    php -S localhost:8000 -t public

If you change or add translations, you need to update the `messages` XLIFF file with:

    bin/console translation:extract en --force --domain=messages+intl-icu

## Testing

You can use:

    ./vendor/bin/phpunit

## ✨ Code linting

We use [PHP-CS-Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) with:

    PHP_CS_FIXER_IGNORE_ENV=True ./vendor/bin/php-cs-fixer fix

## ❓ How-to's

Below are some issues that can bring more info / insight into custom setups that Davis users have experienced in the past. Hopefully it can help:

  - **Davis on Proxmox / TrueNAS Scale**: https://github.com/tchapi/davis/issues/164


## 🐛 Troubleshooting

### Start with the diagnostics page

The dashboard tells you how many points need your attention and links to **Diagnostics**(`/dashboard/diagnostics`), which is usually the fastest way to find out why something configured does not happen.

![Diagnostics page](_screenshots/diagnostics.png)

It groups the checks into Runtime, Database, Authentication, Scheduling and mail, and Endpoints, and each one that needs action says what to do about it and gives the command or query to run. Among other things it reports migrations that have not been run, the database engine actually in use, whether the log directory is writable, whether the selected authentication method has the PHP extension it needs, and how many accounts have no email address and therefore never send an invitation.

Everything on it is read-only and passive — no connection is opened, so it stays usable when something is down. The only exception is the optional update check (`UPDATE_CHECK_ENABLED=true`), which asks GitHub at most once a day whether a newer release exists, and degrades to "unknown" when it cannot.

Credentials are never shown: the mail transport is displayed as scheme and host only. Use `bin/console davis:mail:test you@example.org` to actually exercise the mailer.

### Logs

Depending on how you run Davis, logs are either:
  - [dev] printed out directly in the console
  - [dev] available in the Symfony Debug Bar in the [Profiler](https://symfony.com/doc/current/profiler.html)
  - [dev] logged in `./var/log/dev.log`
  - [prod] logged in `./var/log/prod.log`, but only if there has been an error (_it's the fingers_crossed filter, explained [here](https://symfony.com/doc/current/logging.html#handlers-that-modify-log-entries)_)

> [!NOTE]
>
> It's `./var/log` (relative to the Davis installation), not `/var/log`.
>
> To tail the aplication log on Docker, do:
> ```
> docker exec -it davis tail /var/www/davis/var/log/prod.log
> ```

### I have a "Bad timezone configuration env var" error

The **Server timezone** check on the [diagnostics page](#start-with-the-diagnostics-page) flags it.
It means that the value you set for the `APP_TIMEZONE` env var is not a correct timezone, as per [the official list](https://www.php.net/manual/en/timezones.php). Your timezone has thus not been set and is the server's default (Here, UTC). Adjust the setting accordingly.

### I have a 500 and no tables have been created

You probably forgot to run the migration once to create the necessary DB schema

In Docker:

```shell
docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"
```

In a shell, if you run Davis locally:

    bin/console doctrine:migrations:migrate

### I have a 500 and a log about `Uncaught Error: Class "Symfony\Bundle\WebProfilerBundle\WebProfilerBundle" not found`

You are running the app in dev mode, but you haven't installed the dev dependencies (the Docker images default to `APP_ENV=prod`, so this only happens if you override it). Either:

a. Set `APP_ENV=prod` in your local env file (See configuration above)

b. Or `composer install` (without the `--no-dev` flag)


### The LDAP connection is not working

> [!NOTE]
>
> Make sure all environment parameters are in plain text (no quotes).

Check if your instance can reach your LDAP server:

  - For Docker instances: make sure it is on the same network
  - Check connection via `ldapsearch`:

    ```shell
    # For docker: connect into container's shell
    docker exec -it davis sh

    # install ldap utils (for alpine linux)
    apk add openldap-clients

    # User checking their own entry
    ldapsearch -H ldap://lldap-server:3890 -D "uid=someuser,ou=users,dc=domain,dc=com" -W -b "dc=domain,dc=com" "(uid=someuser)"
    ```

  - Check that the `LDAP_DN_PATTERN` filter is compliant with your LDAP service
  - Example: `uid=%u,ou=people,dc=domain,dc=com`: [LLDAP](https://github.com/lldap/lldap) uses `people` instead of `users`.

### The birthday calendar is not synced / not up to date

An update event might have been missed. In this case, it's easy to resync all contacts by issuing the command:

```
bin/console dav:sync-birthday-calendar
```

> [!WARNING]
> This is a repair command, don't put it on a schedule. It deletes and recreates every birthday
> event, so each run tells your clients that all of them changed and they download the lot again.
> Birthdays are kept up to date on their own whenever a contact is added, changed or removed.

### macOS Calendar shows "There was a problem accessing the availability for this account"

macOS Calendar reads the user's availability (the hours they can be invited, RFC 7953) from the `calendar-availability` property of their scheduling inbox (`/dav/calendars/<user>/inbox/`) when the account settings are opened. Like any sabre/dav server (Baïkal behaves the same), Davis only stores that property once a client has written it, so for a new user it does not exist. Calendar treats a missing value as an error, whatever the server answers (a `404`, or the empty `418` propstat sabre/dav returns because Calendar asks for a minimal response), and never writes a default itself.

Davis therefore answers with a default availability, **weekdays from 09:00 to 17:00**, until the user saves their own in Calendar. A few things to know:

- The default is only shown to clients: free-busy lookups (when someone checks whether the user is free before inviting them) keep treating a user who never saved an availability as **always available**, as before. Once the user saves their availability, it applies everywhere.
- The times are "floating": every client reads 09:00 to 17:00 in its own time zone.
- Calendar asks for the property under its old Apple name (`{http://calendarserver.org/ns/}calendar-availability`), which sabre/dav maps to the standard one (`{urn:ietf:params:xml:ns:caldav}calendar-availability`): both names read and write the same value.

To check what a user has, run a PROPFIND on their inbox (it returns the default if nothing is stored):

```shell
curl -u <user>:<password> -X PROPFIND -H 'Depth: 0' https://dav.example.org/dav/calendars/<user>/inbox/ \
  --data '<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:prop><c:calendar-availability/></d:prop></d:propfind>'
```

# 📚 Libraries used

  - Symfony 7 (Licence : MIT)
  - Sabre-io/dav (Licence : BSD-3-Clause)
  - Bootstrap 5 (Licence : MIT)

_This project does not use any pipeline for the assets since the frontend side is relatively simple, and based on Bootstrap._

# ⚖️ Licence

This project is release under the MIT licence. See the LICENCE file