A Docker container that syncs anniversary and "other" dates from Baikal contacts (via CardDAV) to calendar events (via CalDAV).
- ✅ Connects to CardDAV to fetch contacts
- ✅ Extracts anniversary and custom date fields (excludes birthdays)
- ✅ Supports Apple Contacts label format (itemN.X-ABLabel)
- ✅ Creates whole-day calendar events with format: "Contact Name - Label"
- ✅ Digest authentication support (required for Baikal)
- ✅ Separate authentication for CardDAV and CalDAV (works with any DAV server, not just Baikal)
- ✅ Delete-and-recreate sync strategy (ensures removed/updated dates are reflected)
- ✅ Configurable date range (only create events within N days from today)
- ✅ Run-once or continuous sync modes
- ✅ Lightweight Docker container with non-root user
- Docker and Docker Compose installed
- Baikal server (or any CardDAV/CalDAV compatible server) running and accessible
- A dedicated calendar in Baikal for anniversaries (will be cleared on each sync)
- Valid credentials for CardDAV and CalDAV access
cd /path/to/baikal-anniversariesCopy the example environment file and edit it with your settings:
cp .env.example .env
nano .env # or use your preferred editorRequired variables:
CARDDAV_URL- Base URL of your CardDAV serverCARDDAV_USERNAME- Username for CardDAV authenticationCARDDAV_PASSWORD- Password for CardDAV authenticationCARDDAV_ADDRESSBOOK- Path to your addressbook (e.g.,/addressbooks/username/default/)CALDAV_URL- Base URL of your CalDAV serverCALDAV_USERNAME- Username for CalDAV authenticationCALDAV_PASSWORD- Password for CalDAV authenticationCALDAV_CALENDAR- Path to your calendar (e.g.,/calendars/username/anniversaries/)
Optional variables:
DAYS_IN_FUTURE- Number of days into the future to create events for (default: 365)SYNC_INTERVAL- If set, runs continuously and resyncs every N seconds. If unset, runs once and exitsLOG_LEVEL- Logging verbosity: DEBUG, INFO, WARNING, ERROR (default: INFO)
Using Docker Compose (recommended):
# Build the image
docker-compose build
# Run once and exit
docker-compose run --rm baikal-anniversaries
# Run continuously (if SYNC_INTERVAL is set in .env)
docker-compose up -dUsing Docker directly:
# Build the image
docker build -t baikal-anniversaries .
# Run with environment file
docker run --rm --env-file .env baikal-anniversariesSet up a cron job to run the sync daily:
# .env file
SYNC_INTERVAL= # Leave unset or comment out
# Crontab entry (runs daily at 3 AM)
0 3 * * * cd /path/to/baikal-anniversaries && docker-compose run --rm baikal-anniversariesRun the container continuously with automatic resyncing:
# .env file
SYNC_INTERVAL=86400 # Resync every 24 hours (in seconds)
# Start the container
docker-compose up -d
# View logs
docker-compose logs -fThis tool supports separate authentication for CardDAV and CalDAV, allowing you to sync from one server to another:
# .env file
CARDDAV_URL=https://contacts-server.com
CARDDAV_USERNAME=user1
CARDDAV_PASSWORD=pass1
CALDAV_URL=https://calendar-server.com
CALDAV_USERNAME=user2
CALDAV_PASSWORD=pass2- Connect to CardDAV server and fetch all contacts from the specified addressbook
- Parse vCard data to extract:
ANNIVERSARYfields- Custom date fields (X-* properties)
- Excludes
BDAY(birthday) fields
- Filter dates to only include those occurring within
DAYS_IN_FUTUREdays from today - Clear all existing events in the target CalDAV calendar
- Create new whole-day events with format: "Contact Name - Label"
- Repeat if running in continuous mode
The application extracts the following date types from vCards:
- ANNIVERSARY - Standard vCard 4.0 anniversary field
- Custom fields - Any X-* properties containing dates (e.g., X-ANNIVERSARY, X-OTHER, X-ABDATE)
- Labeled dates - Dates with TYPE or X-ABLABEL parameters for custom labels
- Apple Contacts format - Properly parses Apple's itemN.X-ABLabel format (e.g., "Anniversary", "Nameday")
Excluded:
- BDAY (birthday) fields are explicitly excluded
Enable debug logging to see detailed connection information:
LOG_LEVEL=DEBUG- Verify your credentials are correct
- Check that the CardDAV/CalDAV URLs are accessible
- Ensure the addressbook and calendar paths are correct (include leading and trailing slashes)
- Check that your contacts have ANNIVERSARY or custom date fields
- Verify dates fall within the
DAYS_IN_FUTURErange - Enable DEBUG logging to see which dates are being extracted
- Ensure the CalDAV user has write permissions on the target calendar
- Verify the calendar path is correct
- Check logs for deletion errors
To find the correct paths for your Baikal installation:
- Log into Baikal web interface
- Navigate to your addressbook or calendar
- Look at the URL in your browser
- The path typically follows this pattern:
- Addressbook:
/addressbooks/{username}/{addressbook-name}/ - Calendar:
/calendars/{username}/{calendar-name}/
- Addressbook:
Alternatively, use a CardDAV/CalDAV client (like Thunderbird) to inspect the URLs.
- The Docker container runs as a non-root user (UID 1000) for security
- Store your
.envfile securely and never commit it to version control - Consider using Docker secrets for production deployments
- Use HTTPS URLs for CardDAV/CalDAV connections
This project is provided as-is for personal and homelab use.
Feel free to submit issues or pull requests for improvements!