Baikal¶
Baïkal is a lightweight CalDAV+CardDAV server. It offers an extensive web interface with easy management of users, address books and calendars. Baïkal allows to seamlessly access your contacts and calendars from every device. It is compatible with iOS, Mac OS X, DAVx5 on Android, Mozilla Thunderbird and every other CalDAV and CardDAV capable application.
Install the files¶
Drop into a sudo shell session and make the installation directory:
sudo -s
mkdir /var/www/dav.example.com
Move into the new directory and download the latest release of Baikal:
cd /var/www/dav.example.com
wget https://github.com/sabre-io/Baikal/releases/download/0.7.2/baikal-0.7.2.zip
Unzip the file and remove the original:
unzip baikal-0.7.2.zip -d ./
rm baikal-0.7.2.zip
Create the user for the site:
groupadd www-dav
useradd -g www-dav www-dav
Create a temp directory and set the permissions:
mkdir -p /var/www/dav.example.com/baikal/tmp
chown -R www-dav:www-dav baikal
chmod 755 baikal
find /var/www/dav.example.com/baikal -type f -exec chmod 600 {} \;
find /var/www/dav.example.com/baikal -type d -exec chmod 700 {} \;
chown -R www-dav:www-data /var/www/dav.example.com/baikal/html
find /var/www/dav.example.com/baikal/html -type f -exec chmod 640 {} \;
find /var/www/dav.example.com/baikal/html -type f -name '*.php' -exec chmod 600 {} \;
find /var/www/dav.example.com/baikal/html -type d -exec chmod 750 {} \;
find /var/www/dav.example.com/baikal/html -type d -exec chmod g+s,u+s {} \;
Create a database¶
Open MariaDB:
mysql -u root -p
Create the database and user:
CREATE DATABASE baikal;
GRANT ALL PRIVILEGES ON baikal.* TO 'baikal_user'@'localhost' IDENTIFIED BY '<your-db-password>';
FLUSh PRIVILEGES;
EXIT;
Create the Apparmor profiles¶
Create the PHP pool profile:
nano /etc/apparmor.d/php-fpm.d/dav
Enter the following:
^dav flags=(attach_disconnected, complain) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
#include <abstractions/php>
#include <abstractions/ssl_certs>
signal receive peer=php-fpm7.4,
deny /usr/share/{phpmyadmin,postfixadmin}/** r,
deny /var/www/{,webmail.,video.,audio.}example.com/** r,
/run/mysqld/mysqld.sock rwlk,
@{run}/php/php7.4-fpm-www-dav.sock rwlk,
owner /var/www/dav.example.com/** r,
/etc/mime.types r,
}
Create the Apache profile:
nano /etc/apparmor.d/apache2.d/dav-a2
Enter the following:
^dav-a2 flags=(attach_disconnected) {
#include <abstractions/apache2-common>
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/php>
capability setuid,
capability setgid,
# for log writing (could be abstracted)
/var/log/apache2/access.log w,
/var/log/apache2/error.log w,
# Socket access
/run/php/php7.4-fpm-www-dav.sock wr,
# Access to standard Baikal files
/var/www/dav.example.com/baikal/html/ r,
/var/www/dav.example.com/baikal/html/** r,
# Deny access to these locations
deny /usr/share/{phpmyadmin,postfixadmin}/** r,
deny /var/www/{,audio.,video.,webmail.}example.com/** r,
deny /var/www/dav.example.com/baikal/**.{php,sample} r,
deny /var/www/dav.example.com/baikal/{Core,Specific}/** r,
# Deny access to Bash
deny /bin/bash r,
deny /bin/dash r,
}
Reload Apparmor:
service apparmor reload
Monitor with aa-logprof
and once happy, enforce the profile with aa-enforce
PHP-FPM Pool¶
Create the FPM pool:
nano /etc/php/7.4/fpm/pool.d/www-dav
Enter the following:
; pool name
[www-dav]
; Unix user/group of processes
; will be used.
user = www-dav
group = www-dav
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Socket to which the Apache will connect
listen = /run/php/php7.4-fpm-www-dav.sock
apparmor_hat = dav
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 500
php_admin_value[allow_url_fopen] = On
php_admin_value[allow_url_include] = On
php_admin_value[memory_limit] = 512M
php_admin_flag[output_buffering] = Off
php_admin_value[max_execution_time] = 1800
php_admin_value[max_input_time] = 3600
php_admin_value[post_max_size] = 10M
php_admin_value[upload_max_filesize] = 10M
php_admin_value[upload_tmp_dir] = /var/www/dav.example.com/baikal/tmp
php_admin_value[sys_temp_dir] = /var/www/dav.example.com/baikal/tmp
php_admin_value[max_file_uploads] = 20
php_admin_flag[session.cookie_secure] = True
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
php_admin_value[open_basedir] = /var/www/dav.example.com/baikal/:/usr/share/javascript/:/usr/share/php/tcpdf/:/usr/share/php/phpseclib/
Restart PHP-FPM
service php7.4-fpm restart
Create certificates¶
Create RSA Certificates with:
~/.acme.sh/acme.sh --issue --dns dns_cloudns -d dav.example.com --keylength 4096 --key-file /etc/letsencrypt/rsa-certs/dav.example.com/privkey.pem --ca-file /etc/letsencrypt/rsa-certs/dav.example.com/chain.pem --cert-file /etc/letsencrypt/rsa-certs/dav.example.com/cert.pem --fullchain-file /etc/letsencrypt/rsa-certs/dav.example.com/fullchain.pem --pre-hook "mkdir -p /etc/letsencrypt/rsa-certs/dav.example.com" --post-hook "find /etc/letsencrypt/rsa-certs/dav.example.com/ -name '*.pem' -type f -exec chmod 600 {} \;" --renew-hook "find /etc/letsencrypt/rsa-certs/dav.example.com/ -name '*.pem' -type f -exec chmod 600 {} \; -exec service nginx reload \;"
and ECC certificates with:
~/.acme.sh/acme.sh --issue --dns dns_cloudns -d dav.example.com --keylength ec-384 --key-file /etc/letsencrypt/ecc-certs/dav.example.com/privkey.pem --ca-file /etc/letsencrypt/ecc-certs/dav.example.com/chain.pem --cert-file /etc/letsencrypt/ecc-certs/dav.example.com/cert.pem --fullchain-file /etc/letsencrypt/ecc-certs/dav.example.com/fullchain.pem --pre-hook "mkdir -p /etc/letsencrypt/ecc-certs/dav.example.com" --post-hook "find /etc/letsencrypt/ecc-certs/dav.example.com/ -name '*.pem' -type f -exec chmod 600 {} \;" --renew-hook "find /etc/letsencrypt/ecc-certs/dav.example.com/ -name '*.pem' -type f -exec chmod 600 {} \; -exec service nginx reload \;"
Create Apache2 virtualhost¶
Create the file:
nano /etc/apache2/sites-available/dav.example.com.conf
Enter the following:
<VirtualHost 127.0.0.1:8080>
DocumentRoot /var/www/dav.example.com/baikal/html
ServerName dav.example.com
<IfModule mod_apparmor.c>
AADefaultHatName dav-a2
</IfModule>
<Directory "/var/www/dav.example.com/baikal/html">
Options None
AllowOverride None
Require all granted
</Directory>
<IfModule mod_expires.c>
ExpiresActive Off
</IfModule>
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php/php7.4-fpm-www-dav.sock|fcgi://localhost/"
</FilesMatch>
SetEnvIf HTTPS on HTTPS=on
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Enable the site:
a2ensite dav.example.com.conf
Reload the service:
service apache2 reload
Nginx¶
Now create the Nginx site:
nano /etc/nginx/sites-available/dav.example.com
Enter the following:
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name dav.example.com;
ssl_certificate /etc/letsencrypt/ecc-certs/dav.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/ecc-certs/dav.example.com/privkey.pem;
ssl_certificate /etc/letsencrypt/rsa-certs/dav.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/rsa-certs/dav.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/ecc-certs/dav.example.com/chain.pem;
include /etc/nginx/custom-config/ssl.conf;
include /etc/nginx/custom-config/header.conf;
# Nginx Bad Bot Blocker Includes
include /etc/nginx/bots.d/ddos.conf;
include /etc/nginx/bots.d/blockbots.conf;
# Block Bad Countries
if ($allowed_country = no) {
set $blockreason '[geo_blocked]';
return 403;
}
# 7g Firewall
if ( $bad_querystring !~* "\[OK\]" ) {
set $blockreason $bad_querystring;
return 403;
}
if ( $bad_request !~* "\[OK\]" ) {
set $blockreason $bad_request;
return 403;
}
if ( $bad_request_method !~* "\[OK\]||\[bad_request_method_rule_1\]" ) {
set $blockreason $bad_request_method;
return 403;
}
rewrite ^/.well-known/caldav /dav.php redirect;
rewrite ^/.well-known/carddav /dav.php redirect;
charset utf-8;
location ~ /(\.ht|Core|Specific) {
deny all;
return 404;
}
location / {
include /etc/nginx/custom-config/proxy.conf;
proxy_pass http://127.0.0.1:8080;
client_max_body_size 100M;
}
}
Enable the site:
ln -s /etc/nginx/sites-available/dav.example.com /etc/nginx/sites-available/
Reload Nginx
service nginx reload
Check for errors:
nginx -t
Finish configuration¶
Now browse to your new site at https://dav.example.com to complete the setup. You will need the database details from the database you set up earlier (the server will be 'localhost'). For the authntication type, select 'Digest'.
Once complete, edit the configuration file:
nano /var/www/dav.example.com/baikal/config/baikal.yaml
Change the base_uri value:
system:
...
base_uri: '/'