Get started
One rule, syncing end to end, on your laptop. No Kubernetes and no calendar you care about: you’ll sync a real calendar into a scratch one you create for the occasion and delete afterwards.
Note
Only Google Calendar and CalDAV are supported — no Exchange, Outlook, or Microsoft 365. See Limitations for the full list before you invest more time.
Install
go install github.com/islerfab/meridian/cmd/meridian@latestOr grab a binary from the releases page. Both work; the binary skips needing Go 1.27+.
Note
Or skip the install. Substitute docker run --rm -v "$PWD:/work" -w /work ghcr.io/islerfab/meridian:latest <args> for meridian <args> below. Unsupported as a deployment target.
Create a scratch destination calendar
Make a new, empty calendar on your provider. This is where the copies land, so it wants to be somewhere you won’t mind seeing filled with duplicates of your own events.
xxxxxxxx@group.calendar.google.com.Set up credentials
Meridian never stores secrets in its config. It stores the names of environment variables that hold them, so the config stays safe to commit.
You need a Google Cloud project with the Calendar API enabled and an OAuth client of type Desktop app (Cloud Console → APIs & Services → Credentials).
Publish the consent screen to Production. Apps left in Testing mode hand out refresh tokens that expire after seven days, which is a memorable way to discover your sync stopped a week ago.
meridian oauth --client-id "<id>.apps.googleusercontent.com" \
--client-secret "<secret>" \
--write-env .envThis runs a browser consent flow and writes the refresh token straight into .env, never to your terminal. Add the other two by hand:
MERIDIAN_GOOGLE_CLIENT_ID="<id>.apps.googleusercontent.com"
MERIDIAN_GOOGLE_CLIENT_SECRET="<secret>"
MERIDIAN_GOOGLE_REFRESH_TOKEN="..." # written by the command aboveWrite rules.yaml
instance: quickstart # unique per instance, and never change it once shadows exist
accounts:
- name: google
type: google
clientIDEnv: MERIDIAN_GOOGLE_CLIENT_ID
clientSecretEnv: MERIDIAN_GOOGLE_CLIENT_SECRET
refreshTokenEnv: MERIDIAN_GOOGLE_REFRESH_TOKEN
calendars:
- name: main
id: primary
- name: scratch
id: "xxxxxxxx@group.calendar.google.com" # from the previous step
rules:
- id: main-to-scratch
from: google/main
to: [google/scratch]
# No filter, no transform: a faithful copy of everything.Validate, then run once
meridian validate --config rules.yaml # parse, check references, compile rules
meridian run --once --config rules.yaml # one cycle, then exitvalidate touches no provider and no credential, so it’s safe to run anywhere, including CI.
Run it again
Open the scratch calendar. Your events are there, each carrying a meridian.* ownership marker that’s visible through the API but not in any calendar UI.
Now run meridian run --once a second time.
Nothing happens, and that’s the entire point. Meridian compares a content hash against the one stored in each shadow’s marker, so an unchanged event produces no write. You can run it a thousand times and the calendar won’t notice.
Cleaning up
When you’re done playing, remove everything Meridian created:
meridian wipe rule main-to-scratch --config rules.yaml --yesThen delete the scratch calendar. Meridian only ever deletes events carrying its own marker, so your source calendar is never at risk from this command.