Minimal daemon for synchronizing time tracking data from Jira Tempo to Solidtime.
- π Intelligent Synchronization: CREATE/UPDATE/DELETE operations with change detection
- π Rich Descriptions: Includes Epic names, Jira issue summaries and worklog comments
- π― Epic Integration: Automatically extracts and displays work package (Epic) information
- β‘ High Performance: Batch API calls and smart caching for 5-10x faster syncs
- π Deduplication: Prevents duplicate entries with persistent mapping
- π‘οΈ Recovery: Automatically recreates manually deleted entries (404 detection)
- π Scheduled Sync: Configurable cron expressions for automatic syncing
- π Web UI: Simple dashboard for configuration and sync history
- π History Tracking: SQLite database for persistent sync history
- π Security-First: No hardcoded credentials, security scanning with pre-commit hooks
- π³ Docker Ready: Minimal deployment (~50MB image)
- πΎ Minimal I/O: Batch file writes reduce disk operations
- Docker & Docker Compose (recommended)
- Or: Python 3.11+ with uv
- Pull latest image:
docker pull cddsab/jira2solidtime:latest
# or specific version
docker pull cddsab/jira2solidtime:0.2.0- Create configuration:
cp config.json.example config.json
# Edit config.json with your API credentials and mappings- Start the daemon:
docker-compose up -d- Access web UI:
- Dashboard: http://localhost:8080
- Sync history and statistics
- Manual sync trigger
- Install dependencies:
uv sync- Configure:
cp config.json.example config.json
# Fill in your API credentials- Run daemon:
uv run src/jira2solidtime/main.pyFor local testing and development, use Docker Compose:
docker-compose up -dFor production-ready local deployment with health checks, logging, and restart policies, see the Local Deployment Guide.
Deploy as a managed web app with auto-scaling, SSL, and monitoring:
Quick deploy with Azure CLI:
# Create web app
az webapp create \
--resource-group rg-jira2solidtime \
--plan plan-jira2solidtime \
--name jira2solidtime-app \
--deployment-container-image-name cddsab/jira2solidtime:0.2.0
# Configure app
az webapp config appsettings set \
--resource-group rg-jira2solidtime \
--name jira2solidtime-app \
--settings WEBSITES_PORT=8080Infrastructure as Code with Terraform:
See the Azure Deployment Guide for complete setup with both Azure CLI and Terraform examples.
- Local (Docker Compose): Free (own hardware)
- Azure App Service (B1): ~12β¬/month
Configuration uses a single config.json file:
{
"jira": {
"base_url": "https://your-domain.atlassian.net",
"user_email": "[email protected]",
"api_token": "your-token"
},
"tempo": {
"api_token": "your-tempo-token"
},
"solidtime": {
"base_url": "https://solidtime.yourinstance.com",
"api_token": "your-solidtime-token",
"organization_id": "org-id"
},
"sync": {
"schedule": "0 8 * * *",
"days_back": 30
},
"mappings": {
"JIRA-KEY": "Solidtime Project Name"
},
"web": {
"port": 8080
}
}| Field | Description |
|---|---|
jira.base_url |
Your Jira instance URL |
jira.user_email |
Jira user email for API authentication |
jira.api_token |
Jira API token |
tempo.api_token |
Tempo API authentication token |
solidtime.base_url |
Solidtime instance URL |
solidtime.api_token |
Solidtime API token |
solidtime.organization_id |
Solidtime organization ID |
sync.schedule |
Cron expression for sync timing (default: daily 8 AM) |
sync.days_back |
Days to sync back (default: 30) |
mappings |
Map Jira project keys to Solidtime project names |
web.port |
Web UI port (default: 8080) |
The dashboard provides:
- Configuration: View and edit sync settings
- Sync History: Last 50 syncs with status, duration, and entry counts
- Statistics: Total syncs, success rate, time entries created
- Manual Sync: Trigger sync immediately
- Fetch Worklogs: Retrieves worklogs from Tempo API for configured time range
- Batch Fetch Issues: Fetches all unique Jira issues in a single API call using enhanced search API
- Uses new
/rest/api/3/search/jqlendpoint (POST) - Automatic fallback to legacy v2 API for older instances
- Resilient against Atlassian API deprecations
- Uses new
- Extract Epic Data: Retrieves Epic (parent) information for work package context
- Build Descriptions: Creates formatted descriptions:
Epic Name > ISSUE-KEY: Summary - Commentor[No Epic] > ISSUE-KEY: Summary - Comment - Intelligent Sync:
- CREATE: New worklogs are created in Solidtime
- UPDATE: Changed worklogs are updated (only when duration, description, or date changed)
- DELETE: Worklogs removed from Tempo are deleted from Solidtime
- SKIP: Unchanged entries are skipped entirely (with periodic 24h existence check)
- Track Mappings: Maintains persistent mapping between Tempo and Solidtime entry IDs
- Recovery: Detects manually deleted entries (404) and recreates them automatically
- Batch Write: Saves all mapping changes in two operations (after Phase 1 and Phase 2)
The sync intelligently handles updates:
- Changes detected: UPDATE is performed immediately
- Duration has changed
- Description has changed (Epic, issue summary, or worklog comment)
- Date/time has changed
- No changes, but >24h since last check: UPDATE for existence verification
- No changes, recently verified: SKIP entirely (no API call)
This approach minimizes API calls while still detecting manually deleted entries.
Each Tempo worklog ID is mapped to its corresponding Solidtime time entry ID in data/worklog_mapping.json. This ensures:
- No duplicate entries are created
- Updates target the correct entry
- Deleted worklogs can be cleaned up
- Change detection data is persisted (last duration, description, date)
- Last existence check timestamp is tracked for smart UPDATE logic
# Format code
uv run ruff format src/
# Lint
uv run ruff check src/
# Type checking
uv run mypy src/src/jira2solidtime/
βββ config.py # JSON configuration loader
βββ daemon.py # APScheduler background daemon
βββ history.py # SQLite history tracking
βββ main.py # Application entrypoint
βββ api/ # API clients (Tempo, Jira, Solidtime)
βββ sync/ # Synchronization logic
βββ web/ # Flask web UI
- Service layer: Clean separation of concerns
- Daemon: APScheduler for reliable scheduling
- History: SQLite for persistent tracking
- Web UI: Simple Flask application
- Configuration: Single JSON file, no environment variables needed
Licensed under the Apache License 2.0. See LICENSE file for details.
For issues, questions, or contributions, please visit the GitHub repository.