Corvus is a self-hosted service that automatically syncs commits and contributions from repositories on GitHub, GitLab, Bitbucket, Gitea, Forgejo, and local filesystem and serves a contribution calendar that you can embed on your favorite websites.
The calendar is rendered server-side to SVG using D3 and Observable Plot.
- Run Corvus with Docker Compose
- Configure integrations in
data/integrations.yaml - Trigger a manual sync with
docker compose exec app corvus syncor wait for the cron scheduled sync
Self-host using docker compose:
services:
app:
image: ghcr.io/patrikelfstrom/corvus:latest
ports:
- "3000:3000"
volumes:
- data:/app/data
restart: unless-stopped
volumes:
data:or run with plain Docker:
docker run --rm -p 3000:3000 -v corvus-data:/app/data ghcr.io/patrikelfstrom/corvus:latestIntegrations are configured in data/integrations.yaml.
You can add multiple integrations with different providers and filters.
Example minimal config:
integrations:
- id: my-github-integration
provider: github
auth:
username: octocat
token: ghp_xxxAdditional options and filters are available:
| Option | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique identifier for the integration |
provider |
string | Yes | Provider type: github, gitlab, bitbucket, gitea, forgejo, or filepath |
enabled |
boolean | No | Default: true Enable or disable the integration |
auth.username |
string | Conditional | Username for authentication. Used for author matching (required for remote providers) |
auth.token |
string | Conditional | API token for authentication (required for remote providers) |
source.base_url |
string | Conditional | Custom API endpoint (required for forgejo, optional for others) |
source.path |
string | Conditional | Local filesystem path (required for filepath) |
source.depth |
number | No | Default: 1 Directory depth to scan (used with filepath). |
filters.author_include |
array | Conditional | Include commits from specific authors (required for filepath) |
filters.repository_exclude |
array | No | Exclude repositories by name |
integrations:
- id: github-main
provider: github
auth:
username: octocat
token: ghp_xxx
filters:
author_include:
- octocat
- octo@example.com
repository_exclude:
- experimental
- id: gitea-personal
provider: gitea
source:
base_url: http://localhost:3123
auth:
username: yorkshire
token: a942xxx
filters:
author_include:
- yorkshire
- yorkshire@example.com
repository_exclude:
- experimental
- id: local-work
provider: filepath
source:
path: /Users/example/projects
depth: 2
filters:
author_include:
- your.name@example.com
repository_exclude:
- archive| Provider | Required scopes |
|---|---|
| GitHub | repo |
| GitLab | read_api |
| Bitbucket | read:pullrequest:bitbucket, read:workspace:bitbucket, read:user:bitbucket, read:repository:bitbucket |
| Gitea | read:repository, read:user |
| Forgejo | read:repository, read:user |
Corvus creates data/config.yaml automatically on first run with the default settings and built-in themes. Edit that generated file to change the defaults or add your own theme entries.
settings:
title: true
week_start: sunday
theme: corvus
language: auto
fallback_language: en| Setting | Possible values | Default | Description |
|---|---|---|---|
title |
true, false |
true |
Show summary title above the calendar |
week_start |
sunday, monday, tuesday, wednesday, thursday, friday, saturday |
sunday |
Day to start the week on |
theme |
corvus, github, ylgnbu or custom theme name |
corvus |
Set the default theme |
language |
auto or a locale tag matching a translation file such as en or sv |
auto |
Pick a translation automatically or force one |
fallback_language |
A locale tag matching a translation file such as en |
en |
Translation to use when language: auto has no match |
The title, week_start, and theme settings can also be overridden with query parameters, for example /year.svg?title=false&week_start=monday&theme=github&dark_mode=true.
You can also edit or create custom themes under the themes property in data/config.yaml:
themes:
fuchsia:
light:
- "#eff2f5"
- "#fbb4b9"
- "#f768a1"
- "#c51b8a"
- "#7a0177"
dark:
- "#151b23"
- "#7a0177"
- "#c51b8a"
- "#f768a1"
- "#fbb4b9"Every theme listed under themes can be selected with the theme query parameter, for example /year.svg?theme=fuchsia.
Corvus supports dark mode and the calendar will automatically switch between light and dark themes using CSS prefers-color-scheme inside the generated SVG. This allows embedded SVGs to follow the surrounding page's color scheme.
You can control dark mode with the dark_mode query parameter. Use auto to follow prefers-color-scheme, true to force dark mode, or false to force light mode. For example: /year.svg?dark_mode=true.
Corvus stores translation files in data/translations.
The app creates data/translations/en.yaml automatically on first run.
To add another language:
- Copy
data/translations/en.yamlto a new file named with the locale tag, such asdata/translations/sv.yaml - Translate the values
- Set
settings.language: svor keepsettings.language: auto
SYNC_CRON=0 0 * * *(daily)PORT=3000DB_PATH=dataCONFIG_PATH=dataLOG_FILE_PATH=data/app.logLOG_LEVEL=errorHOST=0.0.0.0
Corvus includes a CLI for manual sync inside the container.
Usage: corvus sync [INTEGRATION...] [--partial]
corvus synctriggers all enabled integrations asynchronously and returns immediately.- Passing one or more integration IDs only syncs those integrations.
- Passing
--partialonly fetches commits since the last successful sync.
For progress and final results, check data/app.log (or container logs).
bun install
bunx lefthook install
bun run dev- Create
src/providers/<provider>/manifest.tswith provider options schema and normalization. - Implement provider adapter/types/client/fetch logic in the same provider directory.
- Export the manifest in
src/providers/index.ts. - Add manifest and flow tests.