Providers
Each provider is a separate worker process in its own repository. Core never imports worker code — the contract is the proto messages + the NATS subjects (see Provider workers). You run only the workers you need.
Enrollment
In production the Strava worker authenticates to NATS with a one-time enrollment token (NATS auth-callout mints scoped credentials). Create one as an admin. The request is authenticated like any other API call: an admin's session cookie or personal access token.
curl -X POST https://cairn.example/api/admin/worker-enrollments \
-H "Authorization: Bearer cairn_pat_..." \
-H "Content-Type: application/json" \
-d '{"name": "strava-fetcher", "expires_in_hours": 720, "note": "prod Strava"}'
name(required): the worker name — lowercase letters, digits and hyphens, no leading or trailing hyphen. The worker'sCAIRN_WORKER_NAMEmust equal it.expires_in_hours(optional): defaults to8760(365 days), which is also the cap.note(optional): free text shown in the admin list.
The 201 response carries the enrollment record and the plaintext token,
shown once. Pass it to the worker as CAIRN_WORKER_ENROLLMENT_TOKEN.
GET /api/admin/worker-enrollments lists enrollments,
POST /api/admin/worker-enrollments/{id}/revoke revokes one and
POST /api/admin/worker-enrollments/{id}/prolong moves its expiry, even after
it has lapsed. Both take an optional JSON body: {"reason": "..."} for revoke,
{"expires_in_hours": 720} for prolong, with the same default and cap of
8760. The body can be left out; one that is not valid JSON is answered with
400.
Strava (Go)
Repo: cairn-provider-strava. Strava is read-only for Cairn. Each user brings their own Strava API app (per-user OAuth) for rate-limit isolation and webhook ownership; users connect their account from the Connections screen.
docker run --rm \
-e CAIRN_NATS_URL=nats://nats:4222 \
-e CAIRN_WORKER_NAME=strava-fetcher \
-e CAIRN_WORKER_ENROLLMENT_TOKEN=... \
ghcr.io/johnnycube/cairn-provider-strava:latest
Only one Strava worker instance should own the webhook subject — set
CAIRN_STRAVA_WEBHOOK_VERIFY_TOKEN on exactly one.
| Variable | Default | Notes |
|---|---|---|
CAIRN_NATS_URL | nats://localhost:4222 | CAIRN_NATS_USER / CAIRN_NATS_PASSWORD for authenticated servers |
CAIRN_WORKER_ENROLLMENT_TOKEN | — | one-time enrollment token from POST /api/admin/worker-enrollments |
CAIRN_WORKER_NAME | strava-fetcher | must equal the enrollment's name |
CAIRN_WORKER_INSTANCE_ID | generated | stable id when you run several instances |
CAIRN_STRAVA_WEBHOOK_VERIFY_TOKEN | — | set on exactly one instance |
CAIRN_STRAVA_TOKEN_URL | Strava's | override for testing against a mock |
CAIRN_LOG_LEVEL | info | debug, info, warn or error |
Garmin (Python)
Repo: cairn-provider-garmin. Garmin has no public activity OAuth API, so the
worker uses the garminconnect library (Garth SSO). Per-user credentials are
delivered over NATS. The worker does not use an enrollment token; it connects
with CAIRN_NATS_USER / CAIRN_NATS_PASSWORD when set, otherwise anonymously.
docker run --rm \
-e CAIRN_NATS_URL=nats://nats:4222 \
ghcr.io/johnnycube/cairn-provider-garmin:latest
| Variable | Default | Notes |
|---|---|---|
CAIRN_NATS_URL | nats://localhost:4222 | CAIRN_NATS_USER / CAIRN_NATS_PASSWORD are used when set |
CAIRN_WORKER_NAME | garmin-fetcher | shared by every instance of the worker |
CAIRN_WORKER_INSTANCE_ID | generated | unique per instance; in Kubernetes set it to the pod name so a restart reuses its presence key |
CAIRN_HEARTBEAT_INTERVAL_S | 20 | presence heartbeat to the cairn_worker_presence KV |
CAIRN_PULL_BATCH | 4 | jobs pulled per fetch |
CAIRN_GARTH_HOME | /tmp/cairn-garth | Garth token store, keeps the Garmin session between jobs; mount a volume so MFA accounts are not re-prompted on every restart |
CAIRN_GARMIN_EMAIL / ..._PASSWORD | — | dev fallback only — in production each user's credentials are delivered over NATS |
CAIRN_PROTO_PATH | <repo>/proto | location of the committed proto stubs |
Writing your own provider
A provider only needs to: register via enrollment, consume
cairn.jobs.*.<provider>, and publish cairn.worker.v1.JobResult events. Use
the committed proto stubs (Go or Python) as your contract. See
Provider workers.