Skip to content

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: '/'