fix(sentry): make DSN env-driven so self-hosters can opt out

The Sentry DSNs were hardcoded in journal/server.ts, planner/server.ts,
and journal/app/lib/sentry.client.ts. Self-hosted instances inheriting
the trails.cool flagship DSN would silently ship their errors to our
Sentry account.

Now each init site reads its DSN from env:

- journal/server.ts: SENTRY_DSN (server runtime env)
- planner/server.ts: SENTRY_DSN (server runtime env)
- journal/app/lib/sentry.client.ts: VITE_SENTRY_DSN (build-time bake)

The flagship DSN is kept as the fallback so the production deploy keeps
reporting without an infra change — but self-hosters can:
- set SENTRY_DSN=\"\" / VITE_SENTRY_DSN=\"\" to ship their own builds
  without Sentry, or
- set SENTRY_DISABLED=true to skip init entirely at runtime, or
- set SENTRY_DSN=/VITE_SENTRY_DSN= to their own DSN.

Follow-up: a future PR can remove the hardcoded fallbacks once
infrastructure/docker-compose.yml + the cd-apps.yml workflow are wired
to pass SENTRY_DSN explicitly. That requires SOPS edits + workflow
changes I want isolated from this purely-code change.

Full repo: pnpm typecheck, pnpm lint, pnpm test all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-05-24 12:24:54 +02:00
parent 9614b68a13
commit f05165c594
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
3 changed files with 37 additions and 11 deletions

View file

@ -5,12 +5,23 @@ import { browserSentryConfig } from "@trails-cool/sentry-config";
let initialized = false;
// Build-time DSN injection: `VITE_SENTRY_DSN` (if set during `pnpm build`)
// overrides the flagship default so self-hosters can ship their own
// Sentry project. Set it to `""` (empty string) to disable Sentry on
// the client entirely.
const FLAGSHIP_JOURNAL_DSN =
"https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728";
const CLIENT_DSN =
(import.meta.env as Record<string, string | undefined>).VITE_SENTRY_DSN ??
FLAGSHIP_JOURNAL_DSN;
export function initSentryClient() {
if (initialized) return;
initialized = true;
if (!CLIENT_DSN) return;
Sentry.init({
dsn: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728",
dsn: CLIENT_DSN,
integrations: [
Sentry.reactRouterV7BrowserTracingIntegration({
useEffect,

View file

@ -10,11 +10,20 @@ import { createBoss, startWorker } from "@trails-cool/jobs";
import { getDatabaseUrl } from "@trails-cool/db";
import postgres from "postgres";
Sentry.init({
dsn: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728",
...nodeSentryConfig("journal server"),
beforeSend: drop404s,
});
// Sentry DSN is read from env so self-hosted instances don't ship their
// errors to the trails.cool flagship Sentry by default. The flagship
// keeps its DSN as the fallback; setting SENTRY_DSN="" (or any other
// truthy value) overrides. SENTRY_DISABLED=true skips init entirely.
const FLAGSHIP_JOURNAL_SENTRY_DSN =
"https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728";
const sentryDsn = process.env.SENTRY_DSN ?? FLAGSHIP_JOURNAL_SENTRY_DSN;
if (process.env.SENTRY_DISABLED !== "true" && sentryDsn !== "") {
Sentry.init({
dsn: sentryDsn,
...nodeSentryConfig("journal server"),
beforeSend: drop404s,
});
}
const port = Number(process.env.PORT ?? 3000);
const CLIENT_DIR = resolve(import.meta.dirname, "build", "client");