A containerized service that automatically syncs birthdays from CardDAV to CalDAV servers with built-in scheduling.
- Automated Birthday Sync: Fetches contacts from CardDAV and creates recurring birthday events in CalDAV
- Multi-Server Support: Compatible with Nextcloud, Baikal, Radicale, SOGo, and other CardDAV/CalDAV servers
- Flexible Scheduling: Cron-style scheduling or interval-based syncing
- Customizable Events: Template-based event titles, descriptions, and reminders
- Container-Native: Built for Docker with health checks and graceful shutdown
- Multi-Architecture: Supports AMD64 and ARM64 platforms
# Create environment file
cp .env.template .env
# Edit .env with your server credentials
# Run with docker-compose
docker-compose up -d
# Check logs
docker-compose logs -f birthday-syncgit clone https://github.com/anatosun/bdaysync.git
cd bdaysync
docker-compose build
docker-compose up -d| Variable | Required | Default | Description |
|---|---|---|---|
CARDAV_SERVER_URL |
✅ | - | CardDAV server URL |
CARDAV_USERNAME |
✅ | - | CardDAV username |
CARDAV_PASSWORD |
✅ | - | CardDAV password |
CALDAV_SERVER_URL |
✅ | - | CalDAV server URL |
CALDAV_USERNAME |
✅ | - | CalDAV username |
CALDAV_PASSWORD |
✅ | - | CalDAV password |
| Variable | Default | Description |
|---|---|---|
RUN_MODE |
daemon |
Run mode: daemon, once |
SYNC_SCHEDULE |
0 6 * * * |
Cron schedule for sync |
DIAGNOSTIC_SCHEDULE |
0 7 * * 0 |
Cron schedule for diagnostics |
SYNC_INTERVAL_HOURS |
0 |
Alternative: sync every N hours |
STARTUP_DELAY |
30 |
Seconds to wait before first sync |
| Variable | Default | Description |
|---|---|---|
BIRTHDAY_EVENT_TITLE |
🎂 {name}'s Birthday |
Event title template |
BIRTHDAY_EVENT_DESCRIPTION |
Birthday of {name} |
Event description template |
BIRTHDAY_REMINDER_DAYS |
1,7 |
Reminder days (comma-separated) |
BIRTHDAY_REMINDER_MESSAGE |
Reminder: {name}'s birthday is in {days} days! |
Reminder message template |
BIRTHDAY_EVENT_CATEGORY |
Birthday |
Event category |
BIRTHDAY_UPDATE_EXISTING |
true |
Update existing events |
| Variable | Default | Description |
|---|---|---|
DEBUG |
false |
Enable debug logging |
LOG_LEVEL |
INFO |
Log level (DEBUG, INFO, WARNING, ERROR) |
LOG_TO_FILE |
false |
Write logs to file |
TZ |
UTC |
Container timezone |
CARDAV_SERVER_URL=https://nextcloud.example.com/remote.php/dav/addressbooks/username/
CALDAV_SERVER_URL=https://nextcloud.example.com/remote.php/dav/calendars/username/personal/CARDAV_SERVER_URL=https://baikal.example.com/dav.php/addressbooks/username/
CALDAV_SERVER_URL=https://baikal.example.com/dav.php/calendars/username/default/CARDAV_SERVER_URL=https://radicale.example.com/username/
CALDAV_SERVER_URL=https://radicale.example.com/username/docker run -d \
--name birthday-sync \
--env-file .env \
-v birthday-logs:/var/log/birthday-sync \
--restart unless-stopped \
ghcr.io/anatosun/bdaysync:latest# Manual sync
docker exec birthday-sync python bdaysync/main.py --once
# Run diagnostics
docker exec birthday-sync python bdaysync/main.py --diagnose
# Health check
docker exec birthday-sync python bdaysync/main.py --health-check
# View logs
docker-compose logs -f birthday-sync
# Skip ASCII banner
docker exec birthday-sync python bdaysync/main.py --no-banner --once# Install dependencies
pip install -r requirements.txt
# Set environment variables
export CARDAV_SERVER_URL="https://your-server.com/dav/"
export CARDAV_USERNAME="username"
# ... other variables
# Run directly
cd bdaysync
python main.py --diagnose
python main.py --once# Daily at 6 AM
SYNC_SCHEDULE=0 6 * * *
# Every 12 hours
SYNC_SCHEDULE=0 */12 * * *
# Weekdays at 9 AM
SYNC_SCHEDULE=0 9 * * 1-5
# Monthly on 1st at 6 AM
SYNC_SCHEDULE=0 6 1 * *# Every 24 hours
SYNC_INTERVAL_HOURS=24
# Every 6 hours
SYNC_INTERVAL_HOURS=6bdaysync/
├── main.py # Entry point
├── config.py # Environment validation & logging
├── cardav_client.py # CardDAV operations
├── caldav_client.py # CalDAV operations
├── scheduler.py # Cron-like scheduling
└── __init__.py # Package initialization
- CardDAV Client: Discovers addressbooks and fetches contacts with birthdays
- CalDAV Client: Creates/updates birthday events with customizable templates
- Scheduler Service: Handles cron-style or interval-based scheduling
- Configuration Manager: Validates environment and sets up logging
DEBUG=true
LOG_LEVEL=DEBUGAuthentication Failed:
- Verify server URLs (check trailing slashes)
- Ensure credentials are correct
- Check if app passwords are required (Nextcloud, etc.)
No Contacts Found:
- Verify addressbook URL structure
- Check contacts have birthday fields populated
- Enable debug logging for detailed discovery info
Events Not Created:
- Verify calendar permissions
- Check CalDAV server supports event creation
- Review calendar URL format
Template Errors:
- Ensure proper escaping of special characters
- Use simple templates without complex formatting
- Check logs for format string errors
# View all logs
docker-compose logs birthday-sync
# Follow logs in real-time
docker-compose logs -f birthday-sync
# View last 50 lines
docker-compose logs --tail=50 birthday-sync
# Search for errors
docker-compose logs birthday-sync | grep -i errorImages are built for multiple architectures:
linux/amd64(Intel/AMD 64-bit)linux/arm64(ARM 64-bit, Apple Silicon, Raspberry Pi 4+)
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes using Conventional commits (
git commit -m 'feat: added amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone repository
git clone https://github.com/anatosun/bdaysync.git
cd bdaysync
# Install development dependencies
pip install -r requirements.txt
# Run the program
python -m bdaysync/main.py
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: Report security issues via GitHub Security Advisories