From f05165c594ab1f47f0e8a36a398d116d4d10feee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 24 May 2026 12:24:54 +0200 Subject: [PATCH] fix(sentry): make DSN env-driven so self-hosters can opt out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- apps/journal/app/lib/sentry.client.ts | 13 ++++++++++++- apps/journal/server.ts | 19 ++++++++++++++----- apps/planner/server.ts | 16 +++++++++++----- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/apps/journal/app/lib/sentry.client.ts b/apps/journal/app/lib/sentry.client.ts index f4ea454..f68cba3 100644 --- a/apps/journal/app/lib/sentry.client.ts +++ b/apps/journal/app/lib/sentry.client.ts @@ -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).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, diff --git a/apps/journal/server.ts b/apps/journal/server.ts index 5e4f980..abb58ff 100644 --- a/apps/journal/server.ts +++ b/apps/journal/server.ts @@ -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"); diff --git a/apps/planner/server.ts b/apps/planner/server.ts index f1b072c..c480bce 100644 --- a/apps/planner/server.ts +++ b/apps/planner/server.ts @@ -11,11 +11,17 @@ import { createBoss, startWorker } from "@trails-cool/jobs"; import { expireSessionsJob } from "./app/jobs/expire-sessions.ts"; import postgres from "postgres"; -Sentry.init({ - dsn: "https://5215134cd78d5e6c199e29300b8425af@o4509530546634752.ingest.de.sentry.io/4511102546608208", - ...nodeSentryConfig("planner server"), - beforeSend: drop404s, -}); +// See apps/journal/server.ts for the SENTRY_DSN / SENTRY_DISABLED contract. +const FLAGSHIP_PLANNER_SENTRY_DSN = + "https://5215134cd78d5e6c199e29300b8425af@o4509530546634752.ingest.de.sentry.io/4511102546608208"; +const sentryDsn = process.env.SENTRY_DSN ?? FLAGSHIP_PLANNER_SENTRY_DSN; +if (process.env.SENTRY_DISABLED !== "true" && sentryDsn !== "") { + Sentry.init({ + dsn: sentryDsn, + ...nodeSentryConfig("planner server"), + beforeSend: drop404s, + }); +} const port = Number(process.env.PORT ?? 3001); const CLIENT_DIR = resolve(import.meta.dirname, "build", "client");