Configuration reference
Server configuration and operator settings, with defaults and accepted values.
These environment variables configure the server and its operator commands. Defaults belong to the application; the notes identify enterprise bundle overrides. Validation depends on the setting: use the listed values. Saved SMTP settings and workspace AI settings can override deployment defaults.
Core
| Variable | Default | Notes |
|---|---|---|
VITRINKA_ADDR | :8080 | Listen address. |
BASE_URL | http://127.0.0.1:8080 | Public URL of the deployment, scheme included (the boot refuses one without), used to build share URLs and cookies. Must be https in production, or session cookies lose their Secure flag and HSTS is not sent. |
DATA_DIR | ./data | Where the control database, every workspace's SQLite file and (with the fs backend) the blobs live. |
VITRINKA_TOKEN | — | Dev-mode write token; empty means writes are unauthenticated. Never set on a real deployment — the control plane mints per-workspace tokens. Secret. |
VITRINKA_TENANT_POOL_MAX | 64 | How many workspace instances stay open at once. |
VITRINKA_MIGRATE_WORKERS | min(8, CPUs) | How many behind-schema workspace databases the boot migration sweep upgrades in parallel. /readyz answers 503 with the sweep's progress until it finishes; /healthz stays up throughout. |
VITRINKA_LISTENER_TTL | 90 | Seconds a work-queue listener lease survives between long-poll heartbeats before its in-flight work reverts to the queue. |
VITRINKA_AI_REVIEW_PASS_TIMEOUT | 1800 | Seconds an AI review pass may stay running with no result before the server reaps it as failed. |
VITRINKA_WAITLIST | unset | 1 gates new sign-ups on admin approval; existing users always sign in. Values: 1 |
VITRINKA_OPS_ORG | — | Slug of the organization that runs the install. Its admins — and platform admins, always — manage the deployment-wide settings: the deployment mailer, SSO email-domain claims, with VITRINKA_SSO_DOMAINS=on turning forced SSO on, and making an SSO connection deployment-managed (the only kind that reaches VITRINKA_SSO_PRIVATE_NETWORKS) by saving it as an admin of that connection's organization; and only a target they save — an issue tracker, a workspace AI backend, an organization's AI endpoint — may reach a private, loopback or internal address. It grants no data access. Name an organization that already exists: a sign-up is never given this slug. Empty means platform admins alone; an ordinary org admin never qualifies. |
VITRINKA_DEV_MODE_EMAILS | — | Comma-separated eligible emails for the account-menu Dev mode toggle; empty disables eligibility. Parsed once at startup, so changes require restart. Mode defaults off, persists per account across workspaces and grants no admin authority. Enabled accounts see running-binary provenance without GitHub API calls. |
VITRINKA_ROLE | writer | standby boots a warm standby: /healthz answers 200, /readyz 503 {"standby":true} and every other route 503 with a quiet page. A standby opens no database and runs no background loop, so a replica being followed under DATA_DIR is never touched. Promotion is clearing the role (and passing the fence) and restarting. Values: writer · standby |
VITRINKA_FENCE_URL | — | Write fencing for a two-host deployment. Before the first write at boot the server fetches this small document (plain text, or JSON {"writer":"…"}) and refuses to start as a writer unless it names VITRINKA_FENCE_HOST; an unreachable fence also refuses (fail closed — two writers must be impossible by construction). Empty disables fencing. A standby never consults it. |
VITRINKA_FENCE_HOST | — | The name this host expects to read from the fence document (e.g. produlinka). Required when VITRINKA_FENCE_URL is set; compared case-insensitively. |
Blob storage
| Variable | Default | Notes |
|---|---|---|
BLOB_BACKEND | fs | Where uploads, screenshots and recordings are stored. Values: fs · s3 |
S3_ENDPOINT | — | S3-compatible endpoint. Required with BLOB_BACKEND=s3; the bucket is validated at startup. |
S3_REGION | — | Bucket region. Required with BLOB_BACKEND=s3. |
S3_BUCKET | — | Bucket name. Required with BLOB_BACKEND=s3. |
S3_ACCESS_KEY_ID | — | Credential for that bucket. Required with BLOB_BACKEND=s3. Secret. |
S3_SECRET_ACCESS_KEY | — | Credential for that bucket. Required with BLOB_BACKEND=s3. Secret. |
VITRINKA_S3_SSE | aes256 | Server-side encryption requested on every object write, as defense in depth under the app-layer envelope encryption. A backend that rejects the header degrades to off with one boot log, never a crash. Values: aes256 · off · kms:<key-id> |
BLOB_CACHE_MAX_GB | 20 | S3 only: size of the local read-through cache under DATA_DIR/blobcache, pre-warmed at boot, oldest-first eviction. 0 disables it. Cache entries are ciphertext. |
MAX_SET_MB | 200 | Request body limit and unpacked-size limit per published set. |
CLAMD_ADDR | — | Address of a clamd daemon (e.g. clamd:3310). Set, every blob write is scanned fail-closed before it is persisted; unset disables scanning. |
CLAMD_MAX_MB | 100 | Largest blob clamd accepts for scanning. Larger files are refused, never persisted unscanned. |
Encryption and keys
| Variable | Default | Notes |
|---|---|---|
VITRINKA_MASTER_KEY | — | 64 hex characters. Unlocks app-layer secret encryption (AI provider keys, connector secrets). vitrinka dev generates one for local use. Required under VITRINKA_KEYS_PROVIDER=env. Under openbao it is optional: set it for one boot so it is adopted into the key store, then remove it from the environment. Secret. |
VITRINKA_KEK | falls back to VITRINKA_MASTER_KEY | 64 hex characters. The key-encryption key that wraps every workspace's data-encryption key. Unset falls back to the master key so sealing is on with zero configuration; set it to separate the bulk-data key from the secret master key. A malformed value refuses the boot. Must be unset with VITRINKA_KEYS_PROVIDER=openbao — the transit key is the KEK there. Secret. |
VITRINKA_KEYS_PROVIDER | env | Which key store wraps workspace data-encryption keys and the master key. env keeps the KEK in the environment and never makes an outbound call (self-hosted and air-gapped installs). openbao keeps the KEK in an OpenBao/Vault transit engine on a separate key box: every unwrap is a transit call, audited on both sides, and the boot refuses when the store cannot be reached. Values: env · openbao |
VITRINKA_OPENBAO_ADDR | — | Address of the OpenBao server, e.g. https://keystore.internal:8200. Required with VITRINKA_KEYS_PROVIDER=openbao. |
VITRINKA_OPENBAO_MOUNT | transit | Mount path of the transit secrets engine. |
VITRINKA_OPENBAO_KEY | vitrinka-kek | Name of the transit key (non-exportable, deletion_allowed=false). Rows carry the provider id openbao:<key>, so renaming the key is a rewrap, never a data re-encryption. |
VITRINKA_OPENBAO_ROLE_ID | — | AppRole role id the server logs in with. Together with the secret id this is the only key-related credential the environment holds once the master key is adopted. |
VITRINKA_OPENBAO_SECRET_ID | — | AppRole secret id. The client token it mints is renewed by logging in again whenever the store answers 403. Secret. |
VITRINKA_OPENBAO_TOKEN | — | A static OpenBao token instead of the AppRole pair — for development and tests only. Setting both refuses the boot. Secret. |
VITRINKA_OPENBAO_CA | unset (system roots) | Optional CA bundle for an OpenBao certificate signed by a private CA: a PEM file path, or the PEM text itself when the value starts with -----BEGIN (for deployments that render the environment from variables and mount no host file; literal \n sequences are unescaped, so a renderer that flattens multi-line values still works). Parsed at boot, so a bad bundle fails the boot rather than the first unwrap. |
VITRINKA_DEK_REVERIFY | 15m | How often every open workspace's wrapped data-encryption key is unwrapped again through the key store and compared with the key in memory, a Go duration. A mismatch or a failed unwrap evicts the workspace, so a revoked or rotated key stops serving within this window. 0 disables the loop. |
VITRINKA_SEAL_STRICT | off | on refuses to serve any row or object still stored as legacy plaintext instead of passing it through. Flip only after vitrinka seal-existing -verify passes for every workspace. Values: on · off |
VITRINKA_AUDIT_EXPORT | off | Periodic export of the audit log as hash-chained, Ed25519-signed JSONL batches to storage the operator cannot rewrite. dir writes a local WORM directory (air-gapped installs); s3 puts each batch into an Object Lock bucket. Nothing leaves the host unless this is s3. Verify offline with vitrinka audit-verify. Values: off · dir · s3 |
VITRINKA_AUDIT_EXPORT_DIR | DATA_DIR/audit-worm | Destination of the dir target. Batches are written atomically, made read-only and never rewritten; an existing name refuses the write. |
VITRINKA_AUDIT_EXPORT_EVERY | 5m | Export cadence, a Go duration of at least 1m. A run exports only when new audit rows exist and drains everything pending. |
VITRINKA_AUDIT_EXPORT_RETAIN_DAYS | 0 (no lock headers) | s3 only: Object Lock COMPLIANCE retention stamped on every batch (x-amz-object-lock-mode + retain-until-date, now plus this many days). A bucket that rejects the lock fails the put loudly — the lock is never dropped silently. |
VITRINKA_AUDIT_EXPORT_S3_ENDPOINT | — | S3-compatible endpoint of the audit bucket. Required with VITRINKA_AUDIT_EXPORT=s3; same grammar as S3_ENDPOINT. |
VITRINKA_AUDIT_EXPORT_S3_REGION | — | Audit bucket region. Required with VITRINKA_AUDIT_EXPORT=s3. |
VITRINKA_AUDIT_EXPORT_S3_BUCKET | — | Audit bucket name — versioned, with Object Lock enabled. Required with VITRINKA_AUDIT_EXPORT=s3. |
VITRINKA_AUDIT_EXPORT_S3_ACCESS_KEY_ID | — | Credential for the audit bucket — its own key, write-only from the data host's point of view, never the blob credential. Required with VITRINKA_AUDIT_EXPORT=s3. Secret. |
VITRINKA_AUDIT_EXPORT_S3_SECRET_ACCESS_KEY | — | Credential for the audit bucket. Required with VITRINKA_AUDIT_EXPORT=s3. Secret. |
VITRINKA_AUDIT_EXPORT_S3_PREFIX | audit/ | Object key prefix inside the audit bucket. |
Sessions, identity and access
| Variable | Default | Notes |
|---|---|---|
VITRINKA_SESSION_TTL | 720h | Absolute lifetime of a browser session, a Go duration. The enterprise bundle's compose sets 12h. |
VITRINKA_SESSION_IDLE | 0 (off) | Sliding idle window after which an untouched session expires, a Go duration. 0 disables it; a set value must be at least 5m and shorter than the lifetime. The enterprise bundle's compose sets 30m. |
VITRINKA_SSO_DOMAINS | off | on lets organizations claim email domains for home-realm routing and per-domain forced SSO — an enterprise self-host feature; the hosted service keeps it off. Values: on · off |
VITRINKA_SSO_PRIVATE_NETWORKS | unset (public addresses only) | Comma-separated CIDRs (a bare IP counts as its /32 or /128) the SSO client may reach besides public addresses — for an identity provider on your own network, such as an internal Keycloak or ADFS, and on every air-gapped install. Discovery, the key set, the token exchange and the settings page's health check dial through it; no other outbound client does. Only a deployment-managed connection reaches a listed range: one whose issuer, client ID and client secret were last saved by a platform admin or an admin of the VITRINKA_OPS_ORG organization. When an ordinary organization admin saves any of those three, the connection is public-only again; connections that existed before this setting are public-only until such an admin saves them once. List the provider's own address rather than a whole subnet. Loopback is reachable only when listed. Link-local (169.254.0.0/16, fe80::/10), unspecified, multicast and broadcast addresses never are: an entry overlapping them refuses the boot, as does an unparseable one. Known cloud-metadata addresses stay closed even inside a listed range. A NAT64 address (64:ff9b::/96) is judged as the IPv4 address it reaches, so list that IPv4 range: an entry overlapping the NAT64 prefix refuses the boot. AIR_GAP=1 opens nothing by itself and takes public addresses away: a listed range inside the internal network (loopback, private, CGNAT, class E — never link-local, which no entry may list) is then all SSO reaches — an entry reaching past those ranges refuses the boot — and a connection no deployment authority saved reaches nothing. Read at boot; a change needs a restart. |
VITRINKA_SSO_CA | unset (system roots) | Optional CA bundle for an identity provider whose certificate is signed by a private CA, in the VITRINKA_OPENBAO_CA shape: a PEM file path, or the PEM text itself when the value starts with -----BEGIN (literal \n sequences are unescaped). Its certificates are trusted in addition to the system roots, never instead of them, and only by the SSO client — discovery, the key set, the token exchange and the settings page's health check. Parsed at boot, so a bad bundle fails the boot rather than the first sign-in. |
VITRINKA_BREAK_GLASS | owners | Who may take the forced-SSO emergency door ?local=1: nobody, only owners of the forcing organization, or anyone the posture binds. Decided after the credential is proven, so membership stays un-enumerable. Values: off · owners · all |
Retention and limits
| Variable | Default | Notes |
|---|---|---|
VITRINKA_RETENTION_GRACE | 720h | How long a soft-deleted recorded session stays restorable before the sweeper purges its events, row and blobs for good. |
VITRINKA_RETENTION_SWEEP | 1h | How often each warm workspace runs its retention sweep. Minimum 1m. |
VITRINKA_RECORDER_FULL_FIDELITY | deny | allow lets a workspace's recorder policy switch redaction off entirely — a self-host escape hatch. The hosted service never sets it and refuses to store such a policy. Values: allow · deny |
Headers, origins and embedding
| Variable | Default | Notes |
|---|---|---|
VITRINKA_CSP | report-only | Whether the application-wide Content-Security-Policy is enforced or only reported (violations post to /csp-report). Flip to enforce after the report stream has proven quiet. Values: report-only · enforce |
VITRINKA_FRAME_ANCESTORS | — | Space-separated origins allowed to embed workspace pages in an iframe. Empty emits frame-ancestors 'self'. |
VITRINKA_SHARE_ORIGIN | — | Dedicated public origin for board share links, scheme included (e.g. https://share.vitrinka.ai; the boot refuses one without). Requests on that host are served by a share-only surface that never accepts sessions, tokens or the control plane. Empty keeps share links on BASE_URL. |
VITRINKA_REFERENCE_ORIGINS | — | Space-separated exact HTTPS origins allowed to access private reference collections with the existing browser session (e.g. https://vzornik.vitrinka.ai). Empty disables cross-origin access. Applies only to /api/v1/reference-library; never shares the session cookie domain or exposes workspace APIs. |
Air-gap, licensing and release feed
| Variable | Default | Notes |
|---|---|---|
AIR_GAP | unset | 1 blanks every integration that reaches the public internet (email provider, Telegram, GitHub, the eve AI channel — the deployment's and every organization's — and the vendor-hosted AI providers, Plane, Stripe, web push, the breach check, Sentry, the OTLP trace exporter), turns link-card previews off (a pasted link keeps its bare URL) and confines the admin-configured targets that are left (issue trackers, a workspace AI backend, an organization's AI endpoint) to internal addresses (loopback, private, link-local, CGNAT, class E — never a known cloud metadata or credential endpoint such as 169.254.169.254) — and only those deployment authority saved (see VITRINKA_OPS_ORG); any other reaches nothing. Sign-in loses the public internet too: SSO reaches an identity provider only inside the internal ranges VITRINKA_SSO_PRIVATE_NETWORKS lists, on a connection deployment authority saved (any other connection reaches nothing), and the MCP connector sign-in fetches no client metadata document and stops advertising them, so MCP clients register through dynamic client registration. S3, SMTP and the capture sidecar stay reachable — they point at your own network. There is no server-side update ping to disable. Values: 1 |
LICENSE_FILE | DATA_DIR/license.json | Where the server looks for the vendor-signed licence. Missing means unrestricted; present but invalid refuses the boot. The enterprise bundle's compose sets /run/license/license.json. |
VITRINKA_RELEASE_FEED | the public feed | Points vitrinka update on every workstation at an internal release mirror instead of the public feed. Read by the CLI, not the server. Read by the CLI. |
Observability
| Variable | Default | Notes |
|---|---|---|
VITRINKA_LOG_FORMAT | text | Shape of the process log on stderr. json is what a log shipper wants; text keeps a terminal readable. Either way only opaque ids reach the log — never an email, name, IP, user agent, query string, header or payload. Values: text · json Read by the process bootstrap (internal/obs). |
VITRINKA_LOG_LEVEL | info | Lowest level that is written. Values: debug · info · warn · error Read by the process bootstrap (internal/obs). |
VITRINKA_METRICS_ADDR | unset (off) | Listen address of a second, metrics-only listener serving Prometheus /metrics (e.g. :9464). Keep it on the private network and never expose it through the reverse proxy — it sits outside the license gate and the session layer. |
OTEL_EXPORTER_OTLP_ENDPOINT | unset (off) | OTLP/HTTP endpoint that receives traces (e.g. http://otel-collector:4318). The app decides at the head which share of traces it exports (VITRINKA_TRACE_SAMPLE); a collector's tail policy can only choose among those. Blanked under AIR_GAP=1. |
VITRINKA_TRACE_SAMPLE | 0.1 | Head sampling ratio for traces, 0 to 1, decided per request before anything is known about it: 0 exports nothing, 1 exports every trace. Only matters once the OTLP endpoint is set. A collector's tail policy (keep the slow or failed ones) applies only to the exported share — a trace dropped here is gone, so keeping every slow or failed trace needs 1. |
SENTRY_DSN | unset (off) | Sentry DSN for error reports from the server and, through the same-origin /api/v1/telemetry tunnel, from the browser. Request headers, cookies, bodies and IPs are scrubbed before anything is sent; the user is an opaque id. Point it at your own Sentry instance when self-hosting. Blanked under AIR_GAP=1. Secret. |
AI backend
| Variable | Default | Notes |
|---|---|---|
AI_BACKEND_URL | derived from EVE_URL | API base of the AI backend that serves the run view and chat (protocol-conforming, e.g. http://ai:3110/api/v1). Unset, it is derived from EVE_URL by dropping the trailing channel segment. EVE_API_URL is the documented legacy spelling. |
AI_BACKEND_TOKEN | — | Machine token presented upstream as a Bearer header on every proxied call. Empty sends no header (only for a backend on a private network that authenticates by other means). EVE_API_TOKEN is the legacy spelling. Secret. |
EVE_API_URL | — | Legacy spelling of AI_BACKEND_URL; the canonical name wins when both are set. |
EVE_API_TOKEN | — | Legacy spelling of AI_BACKEND_TOKEN; the canonical name wins when both are set. Secret. |
EVE_URL | — | The AI channel for board mentions and review passes, e.g. https://eve.example.com/eve/v1/vitrinka. Both EVE_URL and EVE_SECRET set activates it. |
EVE_SECRET | — | HMAC-SHA256 key that signs every push to the AI channel. Secret. |
VITRINKA_MANAGED_AI | off | Enable prepaid AI through Eve's Vitrinka tenant after its financial capability check succeeds. on or off; provider keys remain in Eve. |
VITRINKA_AI_DAILY_LIMIT_CENTS | 2000 | Deployment-wide provider expense limit per UTC day, in USD cents. Outstanding reservations count against it. Range 1–100000. |
VITRINKA_AI_ORG_CONCURRENCY | 2 | Maximum outstanding managed AI calls per organization. Range 1–10. |
Integrations
| Variable | Default | Notes |
|---|---|---|
PLANE_API_URL | — | Plane API base. All three Plane variables set activates the integration: one upserted comment per set on the referenced issue. |
PLANE_API_TOKEN | — | Plane API token. Secret. |
PLANE_WORKSPACE | — | Plane workspace slug. |
VITRINKA_CAPTURE_URL | — | Optional screenshot sidecar (browserless/chrome) for link-card previews, press renders and the stills of web-recorder sessions (rendered from their rrweb stream, with no network access), e.g. http://capture:3000. Unset, link cards fall back to og:image metadata, and a web-recorder session's projection fails, retryably, naming this setting. Link previews hand it only URLs whose host resolves to public addresses, the capture job blocks IP-literal, single-label and internal-suffix hosts, and a screenshot whose page loaded anything from a non-public address is discarded. |
VITRINKA_CAPTURE_TOKEN | — | Token for the screenshot sidecar, if it needs one. Secret. |
VITRINKA_STT_URL | — | Optional speech-to-text sidecar (whisper.cpp server) for server-side meeting transcription, e.g. http://vitrinka-stt:8080. Unset, the organization setting *Server transcription* is unavailable and meeting recordings keep the transcript made on the device. The sidecar is an internal service: it stays on under AIR_GAP=1 and audio never leaves the compose network (docs/runbooks/stt-sidecar.md). |
VITRINKA_REWIND_DEFAULT | allow where plans meter, else forbid | Whether Rewind (Snap's opt-in all-day screen capture into each person's private workspace) is allowed for organizations that have not set their own Rewind policy. Unset, the hosted service (plans meter: VITRINKA_METERED=1 or Stripe billing) allows it and a self-hosted install forbids it until an organization admin allows it; set it to pin either. Each person still turns it on themselves; frames never leave their personal workspace. Values: allow · forbid |
VITRINKA_EMBED_URL | — | Optional text-embedding sidecar for hybrid task search: an OpenAI-compatible base URL the server posts /embeddings to, e.g. http://vitrinka-embed/v1 (Hugging Face TEI serving BAAI/bge-m3). Unset, task search stays purely lexical and nothing is called. Set, each task's title and the start of its body are embedded in the background (vectors sealed under the workspace key) and a search whose words match no task whole is fused with the nearest tasks by meaning. An internal service: it stays on under AIR_GAP=1. |
VITRINKA_EMBED_MODEL | BAAI/bge-m3 | Model name sent to the embedding sidecar and recorded beside every vector; changing it re-embeds every task in the background. |
VITRINKA_EMBED_API_KEY | — | Bearer token for the embedding sidecar, if it needs one. Secret. |
VITRINKA_CONNECTOR_POLL | 300 | Interval in seconds for polling external tickets linked to promoted reports; 0 disables polling. Invalid values fall back to 300 seconds. This does not schedule project task-mirror sync. Read by internal/web. |
GitHub
| Variable | Default | Notes |
|---|---|---|
GITHUB_APP_ID | — | Numeric id of the GitHub App that powers commit pills, the per-workspace repository connect and the pull-request webhook. GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY together enable the App path. |
GITHUB_APP_PRIVATE_KEY | — | The App's private key, PEM (literal or \n-escaped). Secret. |
GITHUB_APP_SLUG | — | The App's slug, used to build its install URL. |
GITHUB_APP_CLIENT_ID | — | The App's OAuth client id — required for the installation-ownership proof before a workspace binding, and for Sign in with GitHub. |
GITHUB_APP_CLIENT_SECRET | — | The App's OAuth client secret. Secret. |
GITHUB_TOKEN | — | Deployment-wide personal access token, the fallback for commit pills when no App is configured. Secret. |
GITHUB_WEBHOOK_SECRET | — | Verifies the App's webhook deliveries (pull requests attach task refs). Secret. |
GITHUB_LOGIN | unset | 1 shows *Continue with GitHub* on the sign-in pages; needs the App's OAuth credentials. Values: 1 |
Billing
| Variable | Default | Notes |
|---|---|---|
VITRINKA_METERED | unset | 1 enforces every organization's plan limits (storage, seats, boards per workspace) — the hosted service sets it. Configured Stripe billing enforces them too. With neither, no plan meters: every organization runs on Enterprise limits (500 GiB storage, unlimited seats, 250,000 active boards per workspace), and stored plans apply again once metering is on. Any other value refuses startup. Values: 1 |
STRIPE_API_KEY | — | Billing requires this key, STRIPE_WEBHOOK_SECRET, and all three membership price IDs together. Leave all Stripe settings unset to disable billing; partial configuration refuses startup. Configured billing also enforces plan limits (see VITRINKA_METERED). Secret. |
STRIPE_WEBHOOK_SECRET | — | Stripe webhook signing secret. Secret. |
STRIPE_PRICE_PRO | — | Stripe price id of the Pro plan. |
STRIPE_PRICE_STARTER | — | Stripe price id of the Starter plan. |
STRIPE_PRICE_TEAM | — | Stripe price id of the Team plan. |
STRIPE_PACKS | — | Optional prepaid AI packs as price_x=1000,price_y=2500 (USD 10/USD 25; 100 credits = USD 1). Purchases remain disabled until managed AI is available. |
Mail and notifications
| Variable | Default | Notes |
|---|---|---|
SMTP_HOST | — | SMTP relay host. Set, SMTP wins over Resend. Survives AIR_GAP=1 — an air-gapped install's relay is internal by definition. |
SMTP_PORT | 587 | SMTP port. |
SMTP_TLS | starttls | How the relay connection is secured. Authentication over none is refused at boot. Values: starttls · implicit · none |
SMTP_USER | — | SMTP user; empty means an anonymous relay. Must be set together with the password. |
SMTP_PASSWORD | — | SMTP password. Secret. |
SMTP_CA_FILE | — | Optional PEM bundle for a relay certificate signed by a private CA. Parsed at boot, so a bad bundle fails the boot rather than the first send. |
RESEND_API_KEY | — | Resend API key — the hosted-provider alternative to SMTP. Blanked under AIR_GAP=1. Secret. |
EMAIL_FROM | vitrinka <notify@vitrinka.ai> | Sender address of every notification email. Validated at boot when a mailer is configured. |
VITRINKA_SALES_EMAIL | sales@vitrinka.ai | Inbox that receives enterprise-lead notifications. |
TELEGRAM_BOT_TOKEN | — | Enables the Telegram notification channel. Secret. |
TELEGRAM_WEBHOOK_SECRET | — | Verifies Telegram webhook deliveries. Secret. |
VAPID_PUBLIC_KEY | — | Enables web push notifications, together with the private key. |
VAPID_PRIVATE_KEY | — | Web push signing key. Secret. |
VAPID_SUBSCRIBER | mailto:admin@lovinka.com | The mailto: contact push services may use to reach the operator. Set it to your own address; the default is a placeholder. |
Operator verbs
| Variable | Default | Notes |
|---|---|---|
VITRINKA_BACKUP_DIR | <DATA_DIR>-backups, a sibling of the data directory | Destination for automatic sealed safety snapshots before pruning. A path inside DATA_DIR is refused. The vitrinka backup command uses its required -out argument instead. Read by operator safety snapshots. |
VITRINKA_REWRAP_OLD_KEK | — | The outgoing key-encryption key for a rotation, so it never appears in argv. Read by vitrinka rewrap-deks. Secret. |
VITRINKA_REWRAP_NEW_KEK | — | The incoming key-encryption key for a rotation. Read by vitrinka rewrap-deks. Secret. |
VITRINKA_TRUSTED_PROXIES | — | CIDRs of the reverse proxies whose X-Real-Ip header the rate limiter and every audit-log row trust for the client address. Loopback is always trusted; from any other peer the header is ignored and the socket address is used. Read by internal/ratelimit. |