Baikal: CalDAV and CardDAV Server
PHP-based calendar and contacts server with web admin. Easy setup and maintenance.
In our increasingly connected world, calendar and contact synchronization has become essential. However, relying on cloud services means surrendering control of your personal scheduling data. Baikal offers a perfect middle ground: a feature-rich CalDAV and CardDAV server with an intuitive web interface, running entirely on your infrastructure.
This comprehensive guide covers everything from basic deployment to advanced configurations, client setup across all platforms, security hardening, and production-ready operations.
What is Baikal?
Baikal is a lightweight PHP-based CalDAV and CardDAV server built on the SabreDAV framework. It provides the perfect balance between simplicity and functionality, offering an elegant administrative web interface without the complexity of full-featured solutions like Nextcloud.
Why Choose Baikal?
1. User-Friendly Admin Interface
Unlike minimal solutions, Baikal provides a comprehensive web interface for:
- User management with individual permissions
- Calendar and address book creation
- System configuration and monitoring
- Database management
- User activity logs
2. Lightweight Yet Powerful
Baikal offers enterprise-grade features while maintaining a small footprint:
- Runs smoothly with 256MB RAM
- SQLite or MySQL/PostgreSQL support
- Handles thousands of events efficiently
- PHP 7.4+ compatible
3. SabreDAV Foundation
Built on SabreDAV, one of the most mature CalDAV/CardDAV libraries:
- Excellent standards compliance
- Regular security updates
- Extensive plugin ecosystem
- Battle-tested in production environments
4. Database-Driven
Unlike file-based systems, Baikal uses a database for:
- Better performance at scale
- Transaction safety
- Easier multi-user management
- Built-in data integrity
Baikal vs. Alternatives
| Feature | Baikal | Radicale | Nextcloud |
|---|---|---|---|
| Admin Interface | Full Web UI | Minimal | Full Suite |
| RAM Usage | 200-300MB | 50-100MB | 512MB+ |
| Database | SQLite/MySQL | Filesystem | MySQL/PostgreSQL |
| User Management | Built-in UI | Config files | Advanced |
| Best For | Small Teams | Single User | Organizations |
| Setup Time | 15 minutes | 10 minutes | 1-2 hours |
Architecture Overview
Our Baikal deployment architecture:
Internet
|
v
Reverse Proxy (Traefik/Nginx)
|
v
Baikal Container (PHP-FPM + Nginx)
|
v
Database (SQLite or MySQL/MariaDB)
|
v
Persistent Storage This provides:
- HTTPS encryption
- Database-backed reliability
- Scalable storage
- Easy backups
- Multi-user support
Prerequisites
Before deployment, ensure you have:
- Linux server or VM (2 CPU, 512MB RAM recommended)
- Docker and Docker Compose installed
- Basic command-line knowledge
- Domain name with DNS access (for SSL)
- Port 80/443 available for reverse proxy
Deployment: Getting Baikal Running
Let’s deploy Baikal using Docker Compose with both SQLite and MySQL options.
Step 1: Create Project Structure
# Create project directory
mkdir -p ~/baikal && cd ~/baikal
# Create data directories
mkdir -p config data Step 2: Docker Compose - SQLite Version
For single-user or small deployments, SQLite is perfect:
Create docker-compose.yml:
version: '3.8'
services:
baikal:
image: ckulka/baikal:latest
container_name: baikal
restart: unless-stopped
ports:
- "8800:80"
volumes:
- ./config:/var/www/baikal/config
- ./data:/var/www/baikal/Specific
environment:
# Baikal server name
- BAIKAL_SERVERNAME=baikal.yourdomain.com
# Optional: Set timezone
- TZ=America/New_York
networks:
- baikal-net
networks:
baikal-net:
driver: bridge Step 3: Docker Compose - MySQL/MariaDB Version
For better performance with multiple users:
version: '3.8'
services:
baikal-db:
image: mariadb:11
container_name: baikal-db
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=secure_root_password
- MYSQL_DATABASE=baikal
- MYSQL_USER=baikal
- MYSQL_PASSWORD=secure_baikal_password
volumes:
- baikal_db:/var/lib/mysql
networks:
- baikal-net
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
baikal:
image: ckulka/baikal:latest
container_name: baikal
restart: unless-stopped
depends_on:
baikal-db:
condition: service_healthy
ports:
- "8800:80"
volumes:
- ./config:/var/www/baikal/config
- ./data:/var/www/baikal/Specific
environment:
- BAIKAL_SERVERNAME=baikal.yourdomain.com
- TZ=America/New_York
# MySQL configuration (set during web setup)
networks:
- baikal-net
volumes:
baikal_db:
networks:
baikal-net:
driver: bridge Step 4: Initial Deployment
# Start services
docker-compose up -d
# Check logs
docker-compose logs -f baikal
# Verify containers are running
docker-compose ps Step 5: Web-Based Setup
Navigate to http://YOUR-SERVER-IP:8800 in your browser.
System Setup (First Screen):
- Timezone: Select your timezone
- Enable admin password protection: Yes
- Admin password: Create a strong password
For SQLite:
- Default settings work perfectly
For MySQL/MariaDB:
- Database type: MySQL
- Database host: baikal-db
- Database name: baikal
- Database username: baikal
- Database password: secure_baikal_password
- Click “Save Changes”
Initial Configuration (Second Screen):
- Server name:
baikal.yourdomain.com - CalDAV enabled: Yes
- CardDAV enabled: Yes
- WebDAV authentication: Digest (recommended)
- Admin password confirmation
Click “Save Changes” to complete setup.
Setting Up Reverse Proxy with SSL
Production deployments require HTTPS. Configure your preferred reverse proxy.
Option 1: Traefik Configuration
Update docker-compose.yml to add Traefik labels:
version: '3.8'
services:
baikal:
image: ckulka/baikal:latest
container_name: baikal
restart: unless-stopped
volumes:
- ./config:/var/www/baikal/config
- ./data:/var/www/baikal/Specific
environment:
- BAIKAL_SERVERNAME=baikal.yourdomain.com
- TZ=America/New_York
labels:
- "traefik.enable=true"
- "traefik.http.routers.baikal.rule=Host(`baikal.yourdomain.com`)"
- "traefik.http.routers.baikal.entrypoints=websecure"
- "traefik.http.routers.baikal.tls.certresolver=letsencrypt"
- "traefik.http.services.baikal.loadbalancer.server.port=80"
# Security headers
- "traefik.http.routers.baikal.middlewares=baikal-headers"
- "traefik.http.middlewares.baikal-headers.headers.sslredirect=true"
- "traefik.http.middlewares.baikal-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.baikal-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.baikal-headers.headers.customFrameOptionsValue=SAMEORIGIN"
networks:
- traefik-net
networks:
traefik-net:
external: true Option 2: Nginx Reverse Proxy
Create nginx/baikal.conf:
upstream baikal {
server baikal:80;
}
server {
listen 80;
server_name baikal.yourdomain.com;
# Redirect HTTP to HTTPS
return 301 https://baikal.yourdomain.com$request_uri;
}
server {
listen 443 ssl http2;
server_name baikal.yourdomain.com;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logging
access_log /var/log/nginx/baikal_access.log;
error_log /var/log/nginx/baikal_error.log;
# Client body size for large calendar files
client_max_body_size 50M;
location / {
proxy_pass http://baikal;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebDAV needs these
proxy_set_header Destination $http_destination;
proxy_set_header Depth $http_depth;
# Disable buffering for CalDAV/CardDAV
proxy_buffering off;
proxy_request_buffering off;
# Increase timeouts for sync operations
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
}
# CalDAV and CardDAV well-known URIs
location /.well-known/caldav {
return 301 https://baikal.yourdomain.com/dav.php;
}
location /.well-known/carddav {
return 301 https://baikal.yourdomain.com/dav.php;
}
} Add Nginx to docker-compose.yml:
nginx:
image: nginx:alpine
container_name: baikal-nginx
restart: unless-stopped
depends_on:
- baikal
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/baikal.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- baikal-net SSL Certificate Setup
# Install Certbot
sudo apt update && sudo apt install certbot -y
# Obtain certificate
sudo certbot certonly --standalone -d baikal.yourdomain.com --email [email protected] --agree-tos
# Copy certificates
mkdir -p ssl
sudo cp /etc/letsencrypt/live/baikal.yourdomain.com/fullchain.pem ssl/
sudo cp /etc/letsencrypt/live/baikal.yourdomain.com/privkey.pem ssl/
sudo chown $USER:$USER ssl/*.pem
# Set up automatic renewal
sudo certbot renew --dry-run Admin Panel Configuration
Access the admin panel at https://baikal.yourdomain.com/admin
Creating Users
- Log in with your admin password
- Navigate to “Users and resources”
- Click “Add user”
- Fill in user details:
- Username:
john - Display name:
John Doe - Email:
[email protected] - Password: Strong password
- Username:
- Click “Save changes”
Managing Calendars
Default Calendar: Each user automatically gets a default calendar.
Create Additional Calendars:
- Admin panel > Users and resources
- Select a user
- Click “Calendars” tab
- Add new calendar:
- Display name:
Work Calendar - URI:
work(auto-generated) - Description: Optional
- Color: Select a color
- Display name:
- Save
Managing Address Books
Similar to calendars:
- Select user
- Click “Address books” tab
- Create new address book
- Set name and description
Client Configuration
Configure devices to sync with your Baikal server.
iOS and macOS
CalDAV Configuration:
- Settings > Calendar > Accounts > Add Account
- Select “Other” > “Add CalDAV Account”
- Server:
baikal.yourdomain.com - Username: Your Baikal username
- Password: Your Baikal password
- Description:
Baikal Calendar - Advanced Settings:
- Use SSL: Yes
- Port: 443
- Account URL:
https://baikal.yourdomain.com/dav.php
CardDAV Configuration:
- Settings > Contacts > Accounts > Add Account
- Select “Other” > “Add CardDAV Account”
- Use same server details
- Account URL:
https://baikal.yourdomain.com/dav.php
Alternative: Direct URL Method
For more reliable connections, use the full CalDAV/CardDAV URL:
- CalDAV:
https://baikal.yourdomain.com/dav.php/calendars/username/default - CardDAV:
https://baikal.yourdomain.com/dav.php/addressbooks/username/default
Android with DAVx5
Setup:
- Install DAVx5 from F-Droid or Play Store
- Open DAVx5 > ”+” button
- Login with URL and credentials:
- Base URL:
https://baikal.yourdomain.com/dav.php - Username: Your username
- Password: Your password
- Base URL:
- DAVx5 will auto-discover calendars and address books
- Select which to sync
- Grant required Android permissions
Sync Configuration:
- Sync interval: 15 minutes (or customize)
- Sync contacts: Enable
- Sync calendars: Enable
- WiFi only: Optional
Thunderbird
Calendar Setup:
- Calendar tab > Right-click > “New Calendar”
- On the Network > Next
- Username: Your username
- Location:
https://baikal.yourdomain.com/dav.php/calendars/username/default - Offline Support: Recommended
- Enter password when prompted
Address Book Setup:
- Address Book > File > New > Remote Address Book
- Name:
Baikal Contacts - URL:
https://baikal.yourdomain.com/dav.php/addressbooks/username/default - Sync interval: 15 minutes
GNOME Evolution (Linux)
- Edit > Accounts > Add Account
- CalDAV for calendars / CardDAV for contacts
- Server details:
- Server:
baikal.yourdomain.com - Path:
/dav.php - Username: Your username
- Use secure connection: Yes
- Port: 443
- Server:
Outlook with CalDAV Synchronizer
Installation:
- Download CalDAV Synchronizer from GitHub
- Install and restart Outlook
- CalDAV Synchronizer > Synchronization Profiles
Configuration:
- Add new profile > Generic CalDAV/CardDAV
- Calendar settings:
- CalDAV URL:
https://baikal.yourdomain.com/dav.php/calendars/username/default/ - Username and password
- Sync interval: 15 minutes
- CalDAV URL:
- Map to Outlook calendar
Address Book:
- CardDAV URL:
https://baikal.yourdomain.com/dav.php/addressbooks/username/default/ - Map to Outlook contacts folder
Web Interface Access
Baikal includes a basic web interface at: https://baikal.yourdomain.com/dav.php
While functional, it’s minimal. For better web access, consider:
- InfCloud: Web CalDAV/CardDAV client
- AgenDAV: Alternative web interface
Advanced Configuration
Enabling Debug Mode
Edit config/baikal.yaml:
system:
dav_debug: true
invite_from: [email protected] Restart Baikal:
docker-compose restart baikal Performance Tuning
PHP Configuration:
Create config/php.ini:
; Memory limits
memory_limit = 512M
upload_max_filesize = 50M
post_max_size = 50M
; Execution time
max_execution_time = 300
max_input_time = 300
; OPcache for performance
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
opcache.revalidate_freq = 60 Mount in docker-compose.yml:
volumes:
- ./config/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro Database Optimization
For MySQL/MariaDB:
# Access database
docker exec -it baikal-db mysql -u baikal -p
# Optimize tables
USE baikal;
OPTIMIZE TABLE calendarobjects;
OPTIMIZE TABLE addressbookobjects;
# Check table sizes
SELECT
table_name AS 'Table',
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)'
FROM information_schema.TABLES
WHERE table_schema = 'baikal'
ORDER BY (data_length + index_length) DESC; Enable Query Cache:
Add to MariaDB configuration:
services:
baikal-db:
command: >
--query-cache-type=1
--query-cache-size=32M
--innodb-buffer-pool-size=256M Shared Calendars
Create shared calendars accessible by multiple users:
- Admin panel > Users and resources
- Create a dedicated user:
shared - Create calendars under this user
- Share credentials with team members
All team members can:
- Add events to shared calendars
- View others’ additions
- Get real-time sync
Backup and Disaster Recovery
Automated Backup Script
#!/bin/bash
# Baikal Backup Script
BACKUP_DIR="/mnt/backups/baikal"
DATE=$(date +%Y%m%d_%H%M%S)
COMPOSE_FILE="$HOME/baikal/docker-compose.yml"
echo "Starting Baikal backup: $DATE"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup configuration
echo "Backing up configuration..."
tar -czf "$BACKUP_DIR/config-$DATE.tar.gz" -C "$HOME/baikal" config/
# Backup data
echo "Backing up data..."
tar -czf "$BACKUP_DIR/data-$DATE.tar.gz" -C "$HOME/baikal" data/
# Backup database (MySQL/MariaDB)
if docker-compose -f "$COMPOSE_FILE" ps | grep -q baikal-db; then
echo "Backing up database..."
docker exec baikal-db mysqldump -u baikal -psecure_baikal_password baikal | gzip > "$BACKUP_DIR/db-$DATE.sql.gz"
fi
# Remove backups older than 30 days
echo "Cleaning old backups..."
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
echo "Backup completed: $DATE"
# Calculate backup size
du -sh "$BACKUP_DIR" Save as ~/baikal-backup.sh and make executable:
chmod +x ~/baikal-backup.sh
# Schedule daily backups
crontab -e
# Add: 0 3 * * * /home/user/baikal-backup.sh >> /var/log/baikal-backup.log 2>&1 Restore Procedure
#!/bin/bash
# Baikal Restore Script
BACKUP_DIR="/mnt/backups/baikal"
RESTORE_DATE="20250115_030000" # Set your backup date
echo "Restoring Baikal from backup: $RESTORE_DATE"
# Stop Baikal
cd ~/baikal
docker-compose stop baikal
# Restore configuration
echo "Restoring configuration..."
rm -rf config/*
tar -xzf "$BACKUP_DIR/config-$RESTORE_DATE.tar.gz" -C ~/baikal/
# Restore data
echo "Restoring data..."
rm -rf data/*
tar -xzf "$BACKUP_DIR/data-$RESTORE_DATE.tar.gz" -C ~/baikal/
# Restore database if using MySQL
if [ -f "$BACKUP_DIR/db-$RESTORE_DATE.sql.gz" ]; then
echo "Restoring database..."
docker-compose start baikal-db
sleep 10
gunzip < "$BACKUP_DIR/db-$RESTORE_DATE.sql.gz" | docker exec -i baikal-db mysql -u baikal -psecure_baikal_password baikal
fi
# Restart all services
echo "Starting Baikal..."
docker-compose up -d
echo "Restore completed!" Off-Site Backup with Rclone
# Install Rclone
curl https://rclone.org/install.sh | sudo bash
# Configure remote (e.g., Backblaze B2)
rclone config
# Sync backups to cloud
rclone sync /mnt/backups/baikal remote:baikal-backups --transfers 4 --checkers 8 --retries 3
# Add to backup script for automatic cloud backup Monitoring and Maintenance
Health Check Script
#!/bin/bash
# Baikal Health Monitor
BAIKAL_URL="https://baikal.yourdomain.com"
ADMIN_USER="admin"
ADMIN_PASS="your-admin-password"
# Check HTTP response
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BAIKAL_URL/dav.php")
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "401" ]; then
echo "✓ Baikal is responding (HTTP $HTTP_CODE)"
else
echo "✗ Baikal is down (HTTP $HTTP_CODE)"
# Send alert via email, ntfy, etc.
curl -d "Baikal is down!" ntfy.sh/your-topic
fi
# Check disk usage
DISK_USAGE=$(df -h ~/baikal | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 80 ]; then
echo "⚠ Warning: Disk usage at ${DISK_USAGE}%"
fi
# Check container status
if ! docker ps | grep -q baikal; then
echo "✗ Baikal container is not running"
docker-compose -f ~/baikal/docker-compose.yml up -d
fi
# Check database size (MySQL)
if docker ps | grep -q baikal-db; then
DB_SIZE=$(docker exec baikal-db mysql -u baikal -psecure_baikal_password -e "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size' FROM information_schema.TABLES WHERE table_schema='baikal';" -sN)
echo "Database size: ${DB_SIZE} MB"
fi Schedule with cron:
crontab -e
# Check every 5 minutes
*/5 * * * * /home/user/baikal-health-check.sh Log Management
# View Baikal logs
docker-compose logs -f baikal
# View last 100 lines
docker-compose logs --tail=100 baikal
# Search for errors
docker-compose logs baikal | grep -i error
# Enable detailed logging
# Edit config/baikal.yaml
system:
dav_debug: true Database Maintenance
# Weekly database optimization
cat > ~/baikal-db-optimize.sh <<'EOF'
#!/bin/bash
docker exec baikal-db mysql -u baikal -psecure_baikal_password -e "
USE baikal;
OPTIMIZE TABLE addressbookobjects;
OPTIMIZE TABLE addressbooks;
OPTIMIZE TABLE calendarobjects;
OPTIMIZE TABLE calendars;
OPTIMIZE TABLE users;
"
echo "Database optimization completed"
EOF
chmod +x ~/baikal-db-optimize.sh
# Schedule weekly
crontab -e
# Add: 0 4 * * 0 /home/user/baikal-db-optimize.sh Security Hardening
1. Strong Admin Password
# Access Baikal container
docker exec -it baikal /bin/bash
# Change admin password via web interface
# Or edit config/baikal.yaml (hashed password) 2. Fail2Ban Protection
Create /etc/fail2ban/filter.d/baikal.conf:
[Definition]
failregex = ^.*"username":".*","message":".*Authentication failed.*","remote_addr":"<HOST>".*$
ignoreregex = Create /etc/fail2ban/jail.d/baikal.conf:
[baikal]
enabled = true
port = http,https
filter = baikal
logpath = /home/user/baikal/data/log/*.log
maxretry = 5
bantime = 3600
findtime = 600 Restart Fail2Ban:
sudo systemctl restart fail2ban
sudo fail2ban-client status baikal 3. Firewall Configuration
# Allow only necessary ports
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# If using Traefik/Nginx as reverse proxy
sudo ufw deny 8800/tcp 4. Regular Security Updates
# Update Baikal
docker-compose pull baikal
docker-compose up -d baikal
# Update database
docker-compose pull baikal-db
docker-compose up -d baikal-db
# Check for security advisories
curl -s https://api.github.com/repos/sabre-io/Baikal/releases/latest | jq -r '.tag_name' 5. Two-Factor Authentication
While Baikal doesn’t have built-in 2FA, you can add it via reverse proxy:
Using Authelia:
# Add Authelia middleware to Traefik labels
- "traefik.http.routers.baikal.middlewares=authelia@docker" Troubleshooting
Issue: Cannot Access Admin Panel
Solution:
# Check container logs
docker-compose logs baikal
# Verify admin password in config/baikal.yaml
docker exec -it baikal cat /var/www/baikal/config/baikal.yaml
# Reset admin password (if needed)
docker exec -it baikal rm /var/www/baikal/Specific/INSTALL_DISABLED
# Then access web setup again Issue: Clients Cannot Connect
Diagnosis:
# Test CalDAV endpoint
curl -u username:password https://baikal.yourdomain.com/dav.php
# Should return XML with DAV capabilities
# Check user credentials in admin panel
# Verify firewall allows connections
sudo ufw status
# Check reverse proxy logs
docker-compose logs nginx Issue: Sync Conflicts
Solution:
# Check for duplicate events
docker exec baikal-db mysql -u baikal -p -e "
USE baikal;
SELECT calendarid, COUNT(*) as count
FROM calendarobjects
GROUP BY calendarid;
"
# Clear client cache on affected devices
# iOS: Settings > Passwords & Accounts > Remove and re-add
# Android: DAVx5 > Account > Force synchronization Issue: Database Connection Errors
Solution:
# Check database is running
docker-compose ps baikal-db
# Test database connection
docker exec baikal-db mysql -u baikal -psecure_baikal_password -e "SELECT 1;"
# Restart database
docker-compose restart baikal-db
# Check for corruption
docker exec baikal-db mysqlcheck -u baikal -p --auto-repair baikal Issue: High Memory Usage
Solution:
# Set memory limits in docker-compose.yml
services:
baikal:
deploy:
resources:
limits:
memory: 512M
baikal-db:
deploy:
resources:
limits:
memory: 512M Migration Guides
Migrating from Google Calendar/Contacts
Export from Google:
- Google Calendar > Settings > Import & Export > Export
- Download .ics files
- Google Contacts > Export > vCard format
Import to Baikal:
- Use Thunderbird or Evolution to import
- Or use caldavzap web interface
- Or write import script:
#!/bin/bash
# Import Google Calendar to Baikal
BAIKAL_URL="https://baikal.yourdomain.com/dav.php/calendars/username/default/"
USERNAME="username"
PASSWORD="password"
for ics_file in *.ics; do
echo "Importing $ics_file"
curl -u "$USERNAME:$PASSWORD" -X PUT -H "Content-Type: text/calendar" --data-binary "@$ics_file" "$BAIKAL_URL${ics_file}"
done Migrating from Radicale
# Export Radicale data
cd ~/radicale/data/collections/username
# Import each calendar
for calendar in */; do
for event in "$calendar"/*.ics; do
curl -u username:password -X PUT -H "Content-Type: text/calendar" --data-binary "@$event" "https://baikal.yourdomain.com/dav.php/calendars/username/default/$(basename $event)"
done
done Conclusion
You now have a fully functional Baikal server providing:
- ✓ CalDAV and CardDAV synchronization
- ✓ Web-based administration
- ✓ Multi-user support with access control
- ✓ HTTPS encryption via reverse proxy
- ✓ Automated backups
- ✓ Database-backed reliability
- ✓ Cross-platform client compatibility
Next Steps
- Configure all your devices with CalDAV/CardDAV
- Set up automated backups to off-site storage
- Create shared calendars for family or team
- Implement monitoring and alerting
- Consider adding web interface like InfCloud
Going Further
- High Availability: Deploy multiple Baikal instances with database replication
- Load Balancing: Use HAProxy for distributing client connections
- Custom Themes: Customize the Baikal admin interface
- API Integration: Build automation using SabreDAV API
Resources
Your self-hosted calendar and contacts server is ready! Enjoy complete control over your scheduling data with the convenience of a modern web interface.