Davis - Установка и настройка WebDAV, CalDAV и CardDAV сервера
Приветствую!

Поднимем сервис Davis, чтобы хранить файлы, календари и контакты на своём сервере, а затем подключим его к Linux Mint и Android. Всё это добро завернём в Docker Compose, а HTTPS и автоматическое получение TLS-сертификата настроим с помощью реверс-прокси Caddy.

Предисловие

Думаю, сегодня не нужно объяснять, почему личные файлы и данные необходимо хранить на собственных приватных серверах. Поднимать ради этого полноценный облачный сервис вроде Nextcloud не всегда оправданно, особенно если нужны только стандартные протоколы *DAV и простая админка.

Например, я храню свои секреты в базе KeePass, расположенной на сервере WebDav. Это удобно, если вам нужно иметь доступ к этой базе с разных устройств и точек подключения.

И недавно я наткнулся на такое приложение, как Davis - это компактный Open source сервер на базе Symfony и sabre/dav. Он поддерживает сразу три полезных протокола:

У Davis есть Web GUI для управления пользователями, календарями и адресными книгами. Для небольшой личной инсталляции можно использовать SQLite, поэтому отдельный сервер базы данных нам не понадобится.

Файловую часть в Linux можно подключить разными способами. В этой статье для демонстрации я воспользуюсь файловым менеджером Nemo из Linux Mint, а более гибкое монтирование WebDav через rclone я недавно подробно разбирал в статье про монтирование сетевых хранилищ SFTP, WebDAV и S3, обязательно загляните.

Вводные данные

Для настройки подразумевается, что у нас уже есть:

Обратный прокси Caddy будет работать рядом с Davis в одном Compose-проекте, принимать внешние подключения на портах 80 и 443, автоматически получать сертификат Let’s Encrypt и проксировать запросы к контейнеру davis по внутренней Docker-сети.

Установка Davis

Подключаемся к серверу по SSH и переходим на УЗ root:

BASH
ssh ivan@dav.r4ven.me

sudo -i
Нажмите, чтобы развернуть и увидеть больше

Создаём директорию проекта и ограничиваем к ней доступ:

BASH
install -m 700 -d /etc/compose/davis

cd /etc/compose/davis
Нажмите, чтобы развернуть и увидеть больше

Создание compose-файла

Создаём файл compose.yaml, например, с помощью vim:

BASH
vim ./compose.yaml
Нажмите, чтобы развернуть и увидеть больше

Наполняем:

YAML
---
name: davis

networks:
  davis:

volumes:
  davis_data:
    name: davis_data
  davis_webdav:
    name: davis_webdav
  davis_caddy_data:
    name: davis_caddy_data
  davis_caddy_config:
    name: davis_caddy_config

services:
  davis:
    image: ghcr.io/tchapi/davis-standalone:5.6.3
    container_name: davis
    restart: always
    stop_grace_period: 30s
    cpus: 1
    mem_limit: 1g
    hostname: davis
    command:
      - sh
      - -c
      - |
        set -e
        mkdir -p /webdav/public /webdav/homes
        APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction
        chown -R www-data:www-data /data /webdav
        exec /usr/bin/supervisord -c /etc/supervisord.conf
    env_file:
      - .env
    volumes:
      - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - davis_data:/data
      - davis_webdav:/webdav
    expose:
      - "9000"
    networks:
      - davis
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  davis_caddy:
    image: docker.io/caddy:2-alpine
    depends_on:
      - davis
    container_name: davis-caddy
    restart: unless-stopped
    stop_grace_period: 30s
    cpus: 1
    mem_limit: 1g
    hostname: davis-caddy
    env_file: ./.env
    configs:
      - source: Caddyfile
        target: /etc/caddy/Caddyfile
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - davis_caddy_data:/data
      - davis_caddy_config:/config
    networks:
      - davis
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

configs:
  Caddyfile:
    content: |
      {
          email ${ACME_EMAIL}
          admin off
      }

      ${DAVIS_DOMAIN} {
          encode zstd gzip

          reverse_proxy davis:9000

          header {
              Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
              X-Content-Type-Options "nosniff"
              X-Frame-Options "SAMEORIGIN"
              Referrer-Policy "no-referrer"
              -Server
          }
      }
Нажмите, чтобы развернуть и увидеть больше

В данном варианте standalone-образ Davis содержит веб-сервер и php-fpm, а отдельный контейнер Caddy отвечает за внешний HTTPS. Порт 9000 Davis доступен только внутри Docker-сети и не публикуется на хосте.

Конфигурация Caddy описана прямо в compose.yaml в секции configs. Переменные ACME_EMAIL и DAVIS_DOMAIN попадут в Caddyfile из .env. Директива reverse_proxy davis:9000 направляет запросы к Davis по имени сервиса.

Данные разделены на четыре Docker volume:

При каждом запуске контейнер Davis создаёт нужные директории, применяет миграции базы данных и проверяет права доступа. Благодаря этому обновление не требует отдельного ручного запуска миграций.

Настройка переменных окружения

Создаём файл .env:

BASH
vim ./.env
Нажмите, чтобы развернуть и увидеть больше

Наполняем:

INI
TZ=Europe/Moscow
DAVIS_DOMAIN=dav.r4ven.me
ACME_EMAIL=kar-kar@r4ven.me
APP_ENV=prod
APP_TIMEZONE=Europe/Moscow
CALDAV_ENABLED=true
CARDDAV_ENABLED=true
WEBDAV_ENABLED=true
PUBLIC_CALENDARS_ENABLED=true
BIRTHDAY_REMINDER_OFFSET=PT9H
ADMIN_LOGIN=ivan
ADMIN_PASSWORD=r4ven_me
ADMIN_AUTH_BYPASS=false
AUTH_METHOD=Basic
AUTH_REALM=SabreDAV
WEBDAV_TMP_DIR=/tmp
WEBDAV_PUBLIC_DIR=
WEBDAV_HOMES_DIR=/webdav/homes
INVITE_FROM_ADDRESS=no-reply@r4ven.me
MAILER_DSN=null://null
SYMFONY_TRUSTED_PROXIES=REMOTE_ADDR
DATABASE_DRIVER=sqlite
DATABASE_URL=sqlite:////data/davis_database.db
Нажмите, чтобы развернуть и увидеть больше

Основные параметры:

Сгенерируем пароль администратора:

BASH
ADMIN_PASSWORD="$(openssl rand -base64 32 | tr -d '\n')"

sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PASSWORD}|" ./.env
Нажмите, чтобы развернуть и увидеть больше

И закроем конфигурацию от посторонних пользователей:

BASH
chmod 600 ./.env ./compose.yaml
Нажмите, чтобы развернуть и увидеть больше

В примере отправка почтовых приглашений отключена значением MAILER_DSN=null://null. Если нужны приглашения участникам событий, понадобится рабочий SMTP. Как его подключить, разберём чуть ниже.

Запуск контейнера

Проверяем итоговую конфигурацию:

BASH
docker compose config
Нажмите, чтобы развернуть и увидеть больше

Запускаем стек:

BASH
docker compose up -d

docker compose logs -f
Нажмите, чтобы развернуть и увидеть больше

Для выхода из просмотра логов нажмите Ctrl+c. Затем проверяем состояние контейнеров:

BASH
docker compose ps
Нажмите, чтобы развернуть и увидеть больше

Настройка HTTPS через Caddy

После запуска Caddy прочитает встроенный в compose.yaml Caddyfile, обратится к ACME-серверу Let’s Encrypt и запросит сертификат для домена из переменной DAVIS_DOMAIN. Полученные сертификаты и ключи сохранятся в томе davis_caddy_data, поэтому не будут выпускаться заново после обычного пересоздания контейнера.

Для автоматического выпуска сертификата должны выполняться три условия:

Проверяем журнал Caddy:

BASH
docker compose logs davis_caddy
Нажмите, чтобы развернуть и увидеть больше

В логе должно появиться сообщение об успешном получении сертификата. После этого открываем https://dav.r4ven.me/ в браузере и проверяем сертификат:

Davis самостоятельно обслуживает пути /.well-known/caldav и /.well-known/carddav, перенаправляя клиентов на /dav/. Добавлять отдельные rewrite-правила в Caddyfile не нужно.

Проверяем внешний доступ:

BASH
curl -I https://dav.r4ven.me/

curl -I https://dav.r4ven.me/.well-known/caldav

curl -I https://dav.r4ven.me/.well-known/carddav
Нажмите, чтобы развернуть и увидеть больше

Для well-known-адресов ожидаем перенаправление на основной endpoint /dav/. Если сертификат ещё не получен, сначала проверяем журнал docker compose logs davis_caddy, DNS-запись и доступность портов 80 и 443.

Первичная настройка Davis

Открываем в браузере адрес панели администратора:

Вывод
https://dav.r4ven.me/dashboard
Нажмите, чтобы развернуть и увидеть больше

Вводим значения ADMIN_LOGIN и ADMIN_PASSWORD из файла .env:

Переходим в раздел пользователей (Users and Resources) и создаём обычную учётную запись:

Тут указываем:

После создания пользователя для него также создаются календарь и адресная книга: Default Calendar и Default Address Book:

При открытии раздела календарей, если навести мышь на символ i, то отобразится полный Web-адрес данного календаря:

Этот адрес понадобится нам во время настройки клиентов.

Наконец открываем раздел диагностики по адресу /dashboard/diagnostics и убеждаемся, что миграции применены, директории доступны для записи, а DAV endpoints включены:

Про настройку SMTP

Davis использует почту только для одного: отправки приглашений участникам событий календаря. Для личного календаря без приглашений можно оставить MAILER_DSN=null://null и пропустить этот раздел.

Если приглашения нужны, открываем .env и меняем две переменные:

BASH
vim /etc/compose/davis/.env
Нажмите, чтобы развернуть и увидеть больше
INI
MAILER_DSN=smtp://no-reply%40r4ven.me:PASSWORD@smtp.r4ven.me:587
INVITE_FROM_ADDRESS=no-reply@r4ven.me
Нажмите, чтобы развернуть и увидеть больше

Формат DSN стандартный для Symfony Mailer: smtp://логин:пароль@сервер:порт. Для порта 465 с неявным TLS схема будет smtps://, для 587 соединение обычно апгрейдится через STARTTLS само.

Закодировать пароль можно прямо из консоли:

BASH
python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' 'my:p@ss/word'
Нажмите, чтобы развернуть и увидеть больше
Вывод
my%3Ap%40ss%2Fword
Нажмите, чтобы развернуть и увидеть больше

Адрес из INVITE_FROM_ADDRESS должен быть разрешён вашему SMTP-серверу как отправителю, иначе письма будут отклоняться. Если оставить переменную пустой, Davis вообще не станет отправлять приглашения и напишет предупреждение в лог.

Применяем изменения. Контейнеру нужно перечитать .env, поэтому пересоздаём его:

BASH
cd /etc/compose/davis

docker compose up -d --force-recreate davis
Нажмите, чтобы развернуть и увидеть больше

Проверяем отправку тестовым письмом, не создавая события:

BASH
docker compose exec davis php bin/console davis:mail:test kar-kar@r4ven.me
Нажмите, чтобы развернуть и увидеть больше

Ещё транспорт отображается на странице /dashboard/diagnostics, но только схема и хост, без реквизитов. Заодно там видно, у скольких пользователей не заполнен email.

Автозапуск через Systemd

Контейнеры в нашем compose.yaml уже имеют restart: always и restart: unless-stopped, поэтому после перезагрузки сервера они поднимутся вместе с Docker. Но я люблю, когда весь стек управляется единообразно, как обычный сервис: с понятным статусом, зависимостью от docker.service и командами systemctl start|stop|restart. Для этого сделаем простой unit.

Создаём файл:

BASH
vim /etc/compose/davis/davis.service
Нажмите, чтобы развернуть и увидеть больше

Наполняем:

INI
[Unit]
Description=Davis (Docker Compose)
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/etc/compose/davis
ExecStart=/usr/bin/docker compose up --no-color --remove-orphans
ExecStop=/usr/bin/docker compose down
Restart=on-failure
RestartSec=10
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target
Нажмите, чтобы развернуть и увидеть больше

Здесь docker compose up запускается без -d, то есть остаётся в foreground и является главным процессом сервиса. Поэтому systemd видит реальное состояние стека, а весь вывод контейнеров попадает в журнал - journald. Флаг --no-color убирает ANSI-коды, чтобы journalctl не показывал непечатываемые символы. Путь до бинарника, на всякий случай, проверьте через command -v docker, на Debian это /usr/bin/docker.

Создаём симлинк на наш файл сервиса в директории с юнитами, перечитываем конфигурацию systemd, включаем автозапуск и сразу стартуем:

BASH
ln -sv /etc/compose/davis/davis.service /etc/systemd/system/davis.service

systemctl daemon-reload

systemctl enable --now davis.service
Нажмите, чтобы развернуть и увидеть больше

Если стек уже запущен вручную через up -d, ничего страшного: Compose просто подключится к работающим контейнерам и начнёт показывать их логи. Смотрим статус и журнал:

BASH
systemctl status davis.service

journalctl -fu davis.service
Нажмите, чтобы развернуть и увидеть больше

Теперь стеком можно управлять так:

BASH
systemctl restart davis.service

systemctl stop davis.service
Нажмите, чтобы развернуть и увидеть больше

Моя финальная проверка - перезагрузка сервера. После неё убеждаюсь, что сервис и контейнеры поднимаются самостоятельно:

BASH
reboot

# после загрузки
systemctl is-active davis.service

docker compose -f /etc/compose/davis/compose.yaml ps
Нажмите, чтобы развернуть и увидеть больше

Подключение WebDAV в Linux Mint

В Linux Mint файловый менеджер Nemo умеет подключать WebDAV без дополнительного ПО.

Открываем Nemo, выбираем в меню Файл -> Подключиться к серверу:

И заполняем параметры:

После подключения ресурс появится в боковой панели Nemo. Можно создавать директории, копировать файлы и открывать документы почти как в обычной файловой системе.

Если удобнее вводить адрес вручную, используйте URI со схемой davs:

Вывод
davs://dav.r4ven.me/dav/home/
Нажмите, чтобы развернуть и увидеть больше

Проверяем запись, создав небольшую тестовую директорию или файл. После этого убеждаемся, что объект появился внутри тома Davis через WebDAV, а не только в локальном кэше файлового менеджера:

Подключение календаря в Linux Mint

Подключение календаря Linux Mint к Davis через WebDav осуществляется через приложение Online Accounts. Запустить его можно через основное меню приложений или календарь:

В качестве провайдера выбираем WebDav:

Тут указываем:

В строке адреса указываем URL, который мы видели ранее в разделе календарей пользователя:

Нажимаем “Авторизоваться”, выбираем нужные элементы для синхронизации и закрываем окно по нажатию “крестика” вверху справа:

В списке календарей появится новый “Default calendar” или как вы его там назвали:

В качестве тестирования давайте запланируем событие, синхронизацию которого позже проверим на Android.

Главное: при редактировании события укажите правильный календарь. В моём примере это “Default Calendar”:

Подключение WebDav в Android через DAVx⁵

DAVx⁵ - это Open Source приложение для Android, доступное для установки в F-Droid и Google Play. Оно подключает CalDAV и CardDAV к системным хранилищам Android. После этого календари видят обычные приложения календаря, а контакты - системное приложение контактов. Также это приложение позволяет подключать WebDav-хранилища как точки монтирования для работы с файлами:

Устанавливаем DAVx⁵ удобным способом, выдаём запрошенные разрешения и создаём новую учётную запись.

Выбираем вход с URL и вводим:

Davis поддерживает well-known discovery, поэтому также можно попробовать корневой URL:

Вывод
https://dav.r4ven.me/
Нажмите, чтобы развернуть и увидеть больше

Но явный адрес /dav/ проще диагностировать и именно его рекомендует страница совместимости DAVx⁵ с Davis.

После обнаружения сервисов задаём имя учётной записи. Если планируется работа с приглашениями CalDAV, лучше использовать email пользователя. Затем открываем созданную учётную запись и отмечаем календари и адресные книги, которые должны синхронизироваться:

Запускаем ручную синхронизацию. В системном календаре Android создаём событие именно в календаре DAVx⁵, а в приложении контактов - контакт в адресной книге DAVx⁵. Локальные коллекции телефона и Google-аккаунта DAVx⁵ не синхронизирует.

Проверяем стандартный календарь смартфона (в моём случае Samsung и OneUI):

Наше событие, добавленное в календаре Linux Mint, на месте 😌.

Для доступа к обычным файлам WebDAV в DAVx⁵ потребуется настроить точку монтирования в левом боковом меню приложения:

Точка монтирования появится в стандартном просмотрщике файлов Android:

Резервное копирование и обновление

В нашей конфигурации всё важное хранится в томах davis_data и davis_webdav, расположенных в /var/lib/docker/volumes/.

Перед обновлением Davis останавливаем запись:

BASH
systemctl stop davis
Нажмите, чтобы развернуть и увидеть больше

И создаём резервные копии обоих томов (/var/lib/docker/volumes/davis_data и /var/lib/docker/volumes/davis_webdav), например, с помощью моего Bash-скрипта:

После резервного копирования меняем тег образа ghcr.io/tchapi/davis-standalone:5.6.3 в compose.yaml на нужную версию. Загружаем образ и снова запускаем сервис:

BASH
docker compose -f /etc/compose/davis/compose.yaml pull davis

systemctl start davis

journalctl -fu davis
Нажмите, чтобы развернуть и увидеть больше

Команда запуска из compose.yaml автоматически применит миграции базы данных.

Возможные проблемы

DAVx⁵ не находит календари и контакты

Проверьте основной endpoint и well-known-редиректы:

BASH
curl -I https://dav.r4ven.me/dav/

curl -I https://dav.r4ven.me/.well-known/caldav

curl -I https://dav.r4ven.me/.well-known/carddav
Нажмите, чтобы развернуть и увидеть больше

Проверьте, что контейнеры davis и davis-caddy находятся в одной сети, а Caddy направляет запросы на davis:9000. В DAVx⁵ сначала используйте явный базовый URL https://dav.r4ven.me/dav/.

Ошибка 401 при подключении

В логах может появиться сообщение об отсутствующем заголовке авторизации:

Вывод
401 - No 'Authorization: Basic' header found. Login was needed
Нажмите, чтобы развернуть и увидеть больше

Сам по себе такой запрос не всегда означает проблему - браузер и DAV-клиенты сначала могут обращаться без реквизитов, получать 401, а затем повторять запрос с авторизацией. Если вход всё равно не работает, проверьте логин обычного DAV-пользователя и его пароль.

Логи приложения можно посмотреть так:

BASH
docker exec -it davis tail /var/www/davis/var/log/prod.log
Нажмите, чтобы развернуть и увидеть больше

WebDAV открывается, но запись запрещена

Проверьте, что используется личный путь пользователя:

Вывод
https://dav.r4ven.me/dav/home/ivan/
Нажмите, чтобы развернуть и увидеть больше

Также проверьте права внутри контейнера:

BASH
docker exec -it davis ls -ld /webdav /webdav/homes /webdav/homes/ivan
Нажмите, чтобы развернуть и увидеть больше

При штатном запуске команда в compose.yaml назначает владельцем /webdav пользователя www-data.

Неверные ссылки или HTTP вместо HTTPS

Если Davis генерирует ссылки с неправильной схемой или доменом, проверьте значение:

INI
SYMFONY_TRUSTED_PROXIES=REMOTE_ADDR
Нажмите, чтобы развернуть и увидеть больше

Caddy автоматически передаёт стандартные заголовки X-Forwarded-Proto и X-Forwarded-Host. Также убедитесь, что в Caddyfile указан upstream davis:9000, а оба контейнера подключены к сети davis.

Послесловие

Протокол WebDav является универсальным вариантом для доступа к удалённым хранилищам с помощью стандартного протокола HTTP. Да, протокол не самый быстрый, но довольно распространённый. Сколько пользуюсь веб-технологиями, всё время задействовал WebDav для каких-либо задач.

Davis оказался приятной находкой для случая, когда полноценное облако не требуется, а стандартные календари, контакты и WebDAV хочется держать у себя. Два контейнера, SQLite, автоматический HTTPS - получилось отличное решение для небольшой домашней инфраструктуры 😌.

Главное, теперь не забывайте про резервное копирование. Особенно с учётом последних событий. Синхронизация удобна, но бэкапом она от этого не становится.

Спасибо, что читаете! И берегите свои данные.

Используемые материалы

Комментарии

Авторские права

Автор: Иван Чёрный

Ссылка: https://r4ven.me/storage/davis-ustanovka-i-nastroyka-webdav-caldav-carddav-servera/

Лицензия: CC BY-NC-SA 4.0

Использование материалов блога разрешается при условии: указания авторства/источника, некоммерческого использования и сохранения лицензии.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
↵
ESC
⌘K Горячая клавиша