Поднимем сервис Davis, чтобы хранить файлы, календари и контакты на своём сервере, а затем подключим его к Linux Mint и Android. Всё это добро завернём в Docker Compose, а HTTPS и автоматическое получение TLS-сертификата настроим с помощью реверс-прокси Caddy.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике - заглядывайте в Вороний чат @r4ven_me_chat🧐. Также в блоге теперь доступно соавторство 🐧🐧🐧.
Предисловие
Думаю, сегодня не нужно объяснять, почему личные файлы и данные необходимо хранить на собственных приватных серверах. Поднимать ради этого полноценный облачный сервис вроде Nextcloud не всегда оправданно, особенно если нужны только стандартные протоколы *DAV и простая админка.
Например, я храню свои секреты в базе KeePass, расположенной на сервере WebDav. Это удобно, если вам нужно иметь доступ к этой базе с разных устройств и точек подключения.
И недавно я наткнулся на такое приложение, как Davis - это компактный Open source сервер на базе Symfony и sabre/dav. Он поддерживает сразу три полезных протокола:
WebDAV- доступ к файлам;CalDAV- синхронизация календарей;CardDAV- синхронизация контактов.
У Davis есть Web GUI для управления пользователями, календарями и адресными книгами. Для небольшой личной инсталляции можно использовать SQLite, поэтому отдельный сервер базы данных нам не понадобится.
Файловую часть в Linux можно подключить разными способами. В этой статье для демонстрации я воспользуюсь файловым менеджером Nemo из Linux Mint, а более гибкое монтирование WebDav через rclone я недавно подробно разбирал в статье про монтирование сетевых хранилищ SFTP, WebDAV и S3, обязательно загляните.
Вводные данные
Для настройки подразумевается, что у нас уже есть:
- Linux-сервер с установленным Docker Engine и Docker Compose;
- домен, указывающий на публичный IP-адрес сервера, в моём примере это
dav.r4ven.me; - открытые входящие порты
80/tcpи443/tcp; - привилегированный доступ к серверу через
sudo.
Обратный прокси Caddy будет работать рядом с Davis в одном Compose-проекте, принимать внешние подключения на портах 80 и 443, автоматически получать сертификат Let’s Encrypt и проксировать запросы к контейнеру davis по внутренней Docker-сети.
☝️ DAV-клиенты передают логин и пароль через HTTP Basic Auth. Используйте только HTTPS, иначе реквизиты доступа можно перехватить в сети.
Установка Davis
Подключаемся к серверу по SSH и переходим на УЗ root:
ssh ivan@dav.r4ven.me
sudo -iСоздаём директорию проекта и ограничиваем к ней доступ:
install -m 700 -d /etc/compose/davis
cd /etc/compose/davisСоздание compose-файла
Создаём файл compose.yaml, например, с помощью vim:
vim ./compose.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_data- база данныхSQLiteи служебные данные приложения;davis_webdav- файлы пользователей WebDAV;davis_caddy_data- TLS-сертификаты, ключи и состояние Caddy;davis_caddy_config- служебная конфигурация Caddy.
При каждом запуске контейнер Davis создаёт нужные директории, применяет миграции базы данных и проверяет права доступа. Благодаря этому обновление не требует отдельного ручного запуска миграций.
⚠️ Тег 5.6.3 повторяет мою текущую конфигурацию, это последняя версия образа на момент написания статьи. Возможно, когда вы её читаете, доступна более свежая версия, проверьте страницу релизов.
Настройка переменных окружения
Создаём файл .env:
vim ./.envНаполняем:
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Основные параметры:
DAVIS_DOMAIN- доменное имя, для которого Caddy получит TLS-сертификат;ACME_EMAIL- email для регистрации ACME-аккаунта и уведомлений о сертификате;CALDAV_ENABLED,CARDDAV_ENABLEDиWEBDAV_ENABLED- включают нужные DAV-протоколы;PUBLIC_CALENDARS_ENABLED- разрешает создавать публичные календари, но не делает их публичными автоматически;ADMIN_LOGINиADMIN_PASSWORD- реквизиты администратора Web GUI;AUTH_METHOD=Basic- локальная авторизация пользователей Davis;AUTH_REALM=SabreDAV- realm HTTP-аутентификации;WEBDAV_TMP_DIR=/tmp- временная директория WebDAV;WEBDAV_HOMES_DIR=/webdav/homes- корень личных файловых директорий пользователей;DATABASE_DRIVER=sqlite- использование встроенной базы данных;DATABASE_URL- путь к файлу базы внутри томаdavis_data;SYMFONY_TRUSTED_PROXIES=REMOTE_ADDR- доверие заголовкам от непосредственно подключённого reverse proxy.
Сгенерируем пароль администратора:
ADMIN_PASSWORD="$(openssl rand -base64 32 | tr -d '\n')"
sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PASSWORD}|" ./.envИ закроем конфигурацию от посторонних пользователей:
chmod 600 ./.env ./compose.yaml☝️ Не меняйте AUTH_REALM после создания пользователей. В старых записях Davis realm мог участвовать в хеше пароля, поэтому после его изменения существующая авторизация способна перестать работать.
В примере отправка почтовых приглашений отключена значением MAILER_DSN=null://null. Если нужны приглашения участникам событий, понадобится рабочий SMTP. Как его подключить, разберём чуть ниже.
Запуск контейнера
Проверяем итоговую конфигурацию:
docker compose configЗапускаем стек:
docker compose up -d
docker compose logs -fДля выхода из просмотра логов нажмите Ctrl+c. Затем проверяем состояние контейнеров:
docker compose ps
Настройка HTTPS через Caddy
После запуска Caddy прочитает встроенный в compose.yaml Caddyfile, обратится к ACME-серверу Let’s Encrypt и запросит сертификат для домена из переменной DAVIS_DOMAIN. Полученные сертификаты и ключи сохранятся в томе davis_caddy_data, поэтому не будут выпускаться заново после обычного пересоздания контейнера.
Для автоматического выпуска сертификата должны выполняться три условия:
- DNS-запись домена (в моём примере
dav.r4ven.me) указывает на публичный IP-адрес сервера; - входящие порты
80/tcpи443/tcpдоступны из интернета; - эти порты не заняты другими сервисами.
Проверяем журнал Caddy:
docker compose logs davis_caddyВ логе должно появиться сообщение об успешном получении сертификата. После этого открываем https://dav.r4ven.me/ в браузере и проверяем сертификат:

Davis самостоятельно обслуживает пути /.well-known/caldav и /.well-known/carddav, перенаправляя клиентов на /dav/. Добавлять отдельные rewrite-правила в Caddyfile не нужно.
Проверяем внешний доступ:
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) и создаём обычную учётную запись:

Тут указываем:
- имя пользователя, например
ivan; - отдельный сложный пароль;
- email пользователя;
- отображаемое имя;
- права администратора - только если они действительно нужны.
📝 Администратор Web GUI и DAV-пользователь - разные учётные записи. Переменные ADMIN_LOGIN и ADMIN_PASSWORD дают доступ к панели управления, но для подключения календарей, контактов и файлов нужно создать пользователя в самой панели.

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

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

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

Про настройку SMTP
Davis использует почту только для одного: отправки приглашений участникам событий календаря. Для личного календаря без приглашений можно оставить MAILER_DSN=null://null и пропустить этот раздел.
Если приглашения нужны, открываем .env и меняем две переменные:
vim /etc/compose/davis/.envMAILER_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 само.
⚠️ Спецсимволы в логине и пароле (@, :, /, #, $, & и прочие) нужно URL-кодировать. Именно поэтому @ в логине выше превратилась в %40. Забудете - получите ошибку разбора DSN или авторизацию не на тот хост.
Закодировать пароль можно прямо из консоли:
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, поэтому пересоздаём его:
cd /etc/compose/davis
docker compose up -d --force-recreate davisПроверяем отправку тестовым письмом, не создавая события:
docker compose exec davis php bin/console davis:mail:test kar-kar@r4ven.me

Ещё транспорт отображается на странице /dashboard/diagnostics, но только схема и хост, без реквизитов. Заодно там видно, у скольких пользователей не заполнен email.
📝 Приглашение уйдёт, только если организатор события совпадает с полем Email пользователя в панели Davis. Если поле пустое или отличается от адреса, который отправляет клиент, письма не будет.
Автозапуск через Systemd
Контейнеры в нашем compose.yaml уже имеют restart: always и restart: unless-stopped, поэтому после перезагрузки сервера они поднимутся вместе с Docker. Но я люблю, когда весь стек управляется единообразно, как обычный сервис: с понятным статусом, зависимостью от docker.service и командами systemctl start|stop|restart. Для этого сделаем простой unit.
Создаём файл:
vim /etc/compose/davis/davis.serviceНаполняем:
[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, включаем автозапуск и сразу стартуем:
ln -sv /etc/compose/davis/davis.service /etc/systemd/system/davis.service
systemctl daemon-reload
systemctl enable --now davis.serviceЕсли стек уже запущен вручную через up -d, ничего страшного: Compose просто подключится к работающим контейнерам и начнёт показывать их логи. Смотрим статус и журнал:
systemctl status davis.service
journalctl -fu davis.service
Теперь стеком можно управлять так:
systemctl restart davis.service
systemctl stop davis.serviceМоя финальная проверка - перезагрузка сервера. После неё убеждаюсь, что сервис и контейнеры поднимаются самостоятельно:
reboot
# после загрузки
systemctl is-active davis.service
docker compose -f /etc/compose/davis/compose.yaml ps
Подключение WebDAV в Linux Mint
В Linux Mint файловый менеджер Nemo умеет подключать WebDAV без дополнительного ПО.
Открываем Nemo, выбираем в меню Файл -> Подключиться к серверу:

И заполняем параметры:
- тип -
WebDAV (HTTPS); - сервер -
dav.r4ven.me; - порт -
443; - папка -
/dav/home/; - имя пользователя -
ivan; - пароль - пароль DAV-пользователя.

После подключения ресурс появится в боковой панели Nemo. Можно создавать директории, копировать файлы и открывать документы почти как в обычной файловой системе.
Если удобнее вводить адрес вручную, используйте URI со схемой davs:
davs://dav.r4ven.me/dav/home/Проверяем запись, создав небольшую тестовую директорию или файл. После этого убеждаемся, что объект появился внутри тома Davis через WebDAV, а не только в локальном кэше файлового менеджера:


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

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

Тут указываем:
- Адрес сервера -
https://dav.r4ven.me/dav/calendars/ivan/default; - Имя пользователя -
ivan; - Пароль пользователя -
***.
В строке адреса указываем URL, который мы видели ранее в разделе календарей пользователя:

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


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

В качестве тестирования давайте запланируем событие, синхронизацию которого позже проверим на Android.
Главное: при редактировании события укажите правильный календарь. В моём примере это “Default Calendar”:

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


Устанавливаем DAVx⁵ удобным способом, выдаём запрошенные разрешения и создаём новую учётную запись.
Выбираем вход с URL и вводим:
- базовый URL -
https://dav.r4ven.me/dav/; - имя пользователя -
ivan; - пароль - пароль DAV-пользователя.


Davis поддерживает well-known discovery, поэтому также можно попробовать корневой URL:
https://dav.r4ven.me/Но явный адрес /dav/ проще диагностировать и именно его рекомендует страница совместимости DAVx⁵ с Davis.
После обнаружения сервисов задаём имя учётной записи. Если планируется работа с приглашениями CalDAV, лучше использовать email пользователя. Затем открываем созданную учётную запись и отмечаем календари и адресные книги, которые должны синхронизироваться:



Запускаем ручную синхронизацию. В системном календаре Android создаём событие именно в календаре DAVx⁵, а в приложении контактов - контакт в адресной книге DAVx⁵. Локальные коллекции телефона и Google-аккаунта DAVx⁵ не синхронизирует.
📝 Если позже создадите новый календарь или адресную книгу на сервере, откройте соответствующую вкладку учётной записи в DAVx⁵ и обновите список коллекций жестом вниз. Новая коллекция не всегда появляется автоматически.
Проверяем стандартный календарь смартфона (в моём случае Samsung и OneUI):



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


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


Резервное копирование и обновление
В нашей конфигурации всё важное хранится в томах davis_data и davis_webdav, расположенных в /var/lib/docker/volumes/.
Перед обновлением Davis останавливаем запись:
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 на нужную версию. Загружаем образ и снова запускаем сервис:
docker compose -f /etc/compose/davis/compose.yaml pull davis
systemctl start davis
journalctl -fu davisКоманда запуска из compose.yaml автоматически применит миграции базы данных.
Возможные проблемы
DAVx⁵ не находит календари и контакты
Проверьте основной endpoint и well-known-редиректы:
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-пользователя и его пароль.
Логи приложения можно посмотреть так:
docker exec -it davis tail /var/www/davis/var/log/prod.logWebDAV открывается, но запись запрещена
Проверьте, что используется личный путь пользователя:
https://dav.r4ven.me/dav/home/ivan/Также проверьте права внутри контейнера:
docker exec -it davis ls -ld /webdav /webdav/homes /webdav/homes/ivanПри штатном запуске команда в compose.yaml назначает владельцем /webdav пользователя www-data.
Неверные ссылки или HTTP вместо HTTPS
Если Davis генерирует ссылки с неправильной схемой или доменом, проверьте значение:
SYMFONY_TRUSTED_PROXIES=REMOTE_ADDRCaddy автоматически передаёт стандартные заголовки X-Forwarded-Proto и X-Forwarded-Host. Также убедитесь, что в Caddyfile указан upstream davis:9000, а оба контейнера подключены к сети davis.
Послесловие
Протокол WebDav является универсальным вариантом для доступа к удалённым хранилищам с помощью стандартного протокола HTTP. Да, протокол не самый быстрый, но довольно распространённый. Сколько пользуюсь веб-технологиями, всё время задействовал WebDav для каких-либо задач.
Davis оказался приятной находкой для случая, когда полноценное облако не требуется, а стандартные календари, контакты и WebDAV хочется держать у себя. Два контейнера, SQLite, автоматический HTTPS - получилось отличное решение для небольшой домашней инфраструктуры 😌.
Главное, теперь не забывайте про резервное копирование. Особенно с учётом последних событий. Синхронизация удобна, но бэкапом она от этого не становится.
Спасибо, что читаете! И берегите свои данные.
Используемые материалы
- GitHub-репозиторий Davis
- Релизы Davis
- DAVx⁵ - подключение к Davis
- DAVx⁵ - учётные записи и коллекции
- Rclone mount - Монтирование сетевых хранилищ SFTP, WebDAV, S3 как локальной директории в Linux
👨💻Ну и…
Не забывайте про нашу телегу📱и чат 💬
Или может хотите стать соавтором? Тогда клик сюда🔗
Всех благ✌️
That should be it. If not, check the logs 🙂



Комментарии