Skip to content

Configuration

calit is configured entirely through environment variables. In a Docker Compose deployment these come from your .env file (loaded via env_file). Copy .env.example to .env and edit it before starting the stack.

VariableDescriptionDefault
DB_NAMEPostgres database namecalit
DB_USERPostgres usercalit
DB_PASSWORDPostgres passwordrequired

The compose file derives DB_URL=jdbc:postgresql://db:5432/${DB_NAME} automatically — you do not set DB_URL in .env.

VariableDescriptionDefault
APP_PORTHost port to expose (the container always listens on 8080)8080
APP_BASE_URLPublic origin users hit — e.g. https://book.example.com. Used as the base for invitee manage links and the Google OAuth redirect URIs.required

On first run with an empty database every request redirects to /setup to create the first admin user. There is no default password.

VariableDescriptionDefault
MAIL_HOSTSMTP hostnamerequired
MAIL_USERNAMESMTP usernamerequired
MAIL_PASSWORDSMTP passwordrequired
MAIL_FROMSender address (e.g. calit@example.com)required
MAIL_PORTSMTP portrequired
MAIL_START_TLSSTARTTLS mode: REQUIRED, OPTIONAL, or DISABLEDrequired
MAIL_TLSImplicit TLS (SMTPS): true or falserequired

The port number does not automatically select an encryption mode. You must set MAIL_PORT, MAIL_START_TLS, and MAIL_TLS together. There are two valid combinations:

Option A — port 587, STARTTLS (most common)

Connection starts plaintext and upgrades to TLS via STARTTLS.

MAIL_PORT=587
MAIL_START_TLS=REQUIRED
MAIL_TLS=false

Option B — port 465, implicit TLS / SMTPS

Connection is encrypted from the first byte.

MAIL_PORT=465
MAIL_TLS=true
MAIL_START_TLS=OPTIONAL

MAIL_START_TLS is an enum (REQUIRED / OPTIONAL / DISABLED), not a boolean. MAIL_TLS is a boolean.

Mail is sent synchronously. If a send fails (SMTP down, refused, timing out), the message is parked in a database outbox instead of being lost, and a background tick retries it — every 60 s, on every replica, claimed with SELECT … FOR UPDATE SKIP LOCKED so it is multi-node-safe with no leader election. Retries use exponential backoff (1 min, doubling, capped at 1 h) and stop after 10 attempts; failed rows are kept for inspection.

Booking and password-reset flows therefore never fail just because SMTP is unavailable. Time-sensitive mail carries a deadline: a queued password-reset email is dropped (not delivered) once its 30-minute reset token has expired, so a recovered SMTP server never hands out a dead reset link. No configuration is required — the outbox is always on.

VariableDescriptionDefault
REMINDER_LEAD_MINUTESMinutes before a meeting to send the reminder email1440 (24 h)
APPROVAL_HOLD_HOURSHow long a pending (approval-required) booking is held before it expires24
SCHEDULER_GRACE_SECONDSTreat reminder / pending-expiry rows as due up to this many seconds early, so replicas on unsynchronised tick timers fire on time instead of a tick late. 0 = exact30
PER_EMAIL_DAILY_CAPMaximum bookings an invitee email address may make per day (abuse protection)10
SIGNUP_ENABLEDAllow public self-service sign-up at /signup, and gate auto-provisioning of new accounts via OIDC SSO. false returns 404 on /signup and rejects unmatched SSO logins instead of creating an account.false

No configuration is required. English and German are built in and always available. See Language & localization for how visitors and account owners select their preferred language.

calit exposes standard MicroProfile Health endpoints — point your orchestrator or load balancer at these:

EndpointPurpose
GET /q/health/liveLiveness — the process is up. Does not check SMTP or Google, so a flapping external dependency can never get a healthy replica restarted.
GET /q/health/readyReadiness — safe to route traffic. Includes informational SMTP and Google checks.

The SMTP and Google checks are informational: they always report UP and expose reachability under data.state (reachable, unreachable, mocked-or-unconfigured, or not-configured). They never mark a replica DOWN — a down mail server does not pull the replica out of rotation, because outgoing mail falls back to the outbox. Use data.state for observability, not as a gate.

VariableDescriptionDefault
SESSION_ENCRYPTION_KEYSigns and encrypts the login session cookie. At least 16 characters. Must be identical on every replica.required
TOKEN_ENCRYPTION_KEYAES-256-GCM key that encrypts Google OAuth tokens at rest. Must be exactly 64 hex characters. Must be identical on every replica.required in prod

Generate both keys with:

Terminal window
openssl rand -hex 32

Leave GOOGLE_OAUTH_CLIENT_ID blank to run calit in degraded mode without Google Calendar integration. See Google OAuth setup for full instructions.

VariableDescriptionDefault
GOOGLE_OAUTH_CLIENT_IDGoogle OAuth client ID(blank — disables Google)
GOOGLE_OAUTH_CLIENT_SECRETGoogle OAuth client secret(blank)
GOOGLE_OAUTH_REDIRECT_URIOverride the calendar sync redirect URIDerived: ${APP_BASE_URL}/api/google/callback
GOOGLE_OAUTH_LOGIN_REDIRECT_URIOverride the sign-in redirect URIDerived: ${APP_BASE_URL}/api/google/login/callback
GOOGLE_OAUTH_STATE_SECRETStrong random string shared by all replicas. Generate: openssl rand -hex 32. Required when Google is enabled.(blank)
GOOGLE_PROBE_INTERVALHow often each connected Google account is probed for a still-valid connection (a duration string, e.g. 30m, 1h, 2h). This also keeps refresh tokens warm and sets how often a reconnect alert is evaluated. See disconnect detection.1h

Register both derived redirect URIs in your Google OAuth client even if you do not override them.

These let a hosted instance pass Google OAuth verification. calit serves a privacy policy at /privacy and terms at /terms; each deployment is its own data controller, so the operator details are configurable. All three are optional and the feature is off/safe when unset.

VariableDescriptionDefault
GOOGLE_SITE_VERIFICATIONGoogle Search Console domain-verification token. When set, every page renders <meta name="google-site-verification">. Leave blank to verify ownership via a DNS TXT record instead.(blank — no tag)
OPERATOR_NAMELegal entity running this instance, shown as the data controller on /privacy and /terms.APP_BASE_URL
PRIVACY_CONTACT_EMAILContact address shown on /privacy for privacy/data requests. Hidden when blank.(blank — line hidden)

See Google OAuth setup → OAuth verification for when these are needed.

calit can add a bot challenge to the public booking form. Pick one provider with CAPTCHA_PROVIDER.

VariableDescriptionDefault
CAPTCHA_PROVIDERWhich CAPTCHA to use: none, turnstile, or altcha. When unset, falls back to turnstile if TURNSTILE_ENABLED=true, otherwise none.none

Hosted by Cloudflare (needs a Cloudflare account). See Turnstile setup.

VariableDescriptionDefault
TURNSTILE_ENABLEDEnable Turnstile (also selects it when CAPTCHA_PROVIDER is unset)false
TURNSTILE_SITE_KEYTurnstile site key from the Cloudflare dashboard(blank)
TURNSTILE_SECRET_KEYTurnstile secret key(blank)

Self-hosted proof-of-work — no external service or account. See ALTCHA setup.

VariableDescriptionDefault
ALTCHA_HMAC_KEYSecret that signs ALTCHA challenges. Required when CAPTCHA_PROVIDER=altcha (startup fails without it). Generate: openssl rand -hex 32.(blank)
ALTCHA_MAX_NUMBERProof-of-work difficulty — max number the browser brute-forces100000

Leave OIDC_ENABLED=false to run with form login only. See OIDC / SSO setup for the redirect URI, required scopes/claims, and worked examples (generic provider + Authelia).

VariableDescriptionDefault
OIDC_ENABLEDEnable the “Sign in with SSO” button and the /api/oidc/login code-flow endpointfalse
OIDC_ISSUER_URLBase issuer URL; endpoints are discovered from ${OIDC_ISSUER_URL}/.well-known/openid-configuration(blank)
OIDC_CLIENT_IDClient ID registered with the provider(blank)
OIDC_CLIENT_SECRETClient secret registered with the provider (plaintext, even if the provider stores it hashed)(blank)
OIDC_ADMIN_GROUPGroup whose members get calit admin on login. Grant-only — never demotes a locally-granted admin.(blank — no OIDC admin)

New-account provisioning via OIDC is gated by SIGNUP_ENABLED (see Behaviour above).