Merge pull request #434 from trails-cool/fix/sentry-dsn-no-hardcoded-fallback
fix(sentry): remove hardcoded DSN fallbacks; supply via env in CI
This commit is contained in:
commit
1263372eef
8 changed files with 55 additions and 21 deletions
16
.github/workflows/cd-apps.yml
vendored
16
.github/workflows/cd-apps.yml
vendored
|
|
@ -13,6 +13,15 @@ concurrency:
|
|||
group: deploy-apps
|
||||
cancel-in-progress: true
|
||||
|
||||
# Public Sentry DSNs for the trails.cool flagship instance. Public by
|
||||
# design — Sentry DSNs are transmitted unencrypted from the client JS
|
||||
# bundle, embedding them in this workflow is no worse than embedding
|
||||
# them in the runtime env. Self-hosted forks should either replace
|
||||
# these with their own DSNs or remove the lines to ship without Sentry.
|
||||
env:
|
||||
SENTRY_DSN_JOURNAL: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728"
|
||||
SENTRY_DSN_PLANNER: "https://5215134cd78d5e6c199e29300b8425af@o4509530546634752.ingest.de.sentry.io/4511102546608208"
|
||||
|
||||
jobs:
|
||||
build-images:
|
||||
name: Build & Push Docker Images
|
||||
|
|
@ -48,8 +57,12 @@ jobs:
|
|||
tags: |
|
||||
ghcr.io/trails-cool/${{ matrix.app }}:latest
|
||||
ghcr.io/trails-cool/${{ matrix.app }}:${{ github.sha }}
|
||||
# VITE_SENTRY_DSN bakes the client-side DSN into the journal's
|
||||
# built bundle. Only journal has a client Sentry init; planner
|
||||
# ignores the build-arg if present.
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ github.sha }}
|
||||
VITE_SENTRY_DSN=${{ matrix.app == 'journal' && env.SENTRY_DSN_JOURNAL || '' }}
|
||||
secrets: |
|
||||
SENTRY_AUTH_TOKEN=/tmp/sentry_token
|
||||
|
||||
|
|
@ -70,6 +83,9 @@ jobs:
|
|||
echo "DOMAIN=trails.cool" >> infrastructure/app.env
|
||||
# Flagship marker — see cd-infra.yml for what this gates.
|
||||
echo "IS_FLAGSHIP=true" >> infrastructure/app.env
|
||||
# Sentry DSNs (public — see workflow top-level env for context).
|
||||
echo "SENTRY_DSN_JOURNAL=$SENTRY_DSN_JOURNAL" >> infrastructure/app.env
|
||||
echo "SENTRY_DSN_PLANNER=$SENTRY_DSN_PLANNER" >> infrastructure/app.env
|
||||
|
||||
- name: Copy files to server
|
||||
uses: appleboy/scp-action@v1
|
||||
|
|
|
|||
11
.github/workflows/cd-staging.yml
vendored
11
.github/workflows/cd-staging.yml
vendored
|
|
@ -30,6 +30,12 @@ concurrency:
|
|||
group: staging-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || 'main' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Public Sentry DSNs (same as cd-apps.yml). See that workflow for the
|
||||
# "public by design" rationale.
|
||||
env:
|
||||
SENTRY_DSN_JOURNAL: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728"
|
||||
SENTRY_DSN_PLANNER: "https://5215134cd78d5e6c199e29300b8425af@o4509530546634752.ingest.de.sentry.io/4511102546608208"
|
||||
|
||||
jobs:
|
||||
# ── Build ─────────────────────────────────────────────────────────────
|
||||
# Tags:
|
||||
|
|
@ -89,6 +95,7 @@ jobs:
|
|||
ghcr.io/trails-cool/${{ matrix.app }}:${{ steps.tags.outputs.sha }}
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ steps.tags.outputs.sha }}
|
||||
VITE_SENTRY_DSN=${{ matrix.app == 'journal' && env.SENTRY_DSN_JOURNAL || '' }}
|
||||
secrets: |
|
||||
SENTRY_AUTH_TOKEN=/tmp/sentry_token
|
||||
|
||||
|
|
@ -118,6 +125,8 @@ jobs:
|
|||
echo "JOURNAL_IMAGE_TAG=staging"
|
||||
echo "PLANNER_IMAGE_TAG=staging"
|
||||
echo "SENTRY_RELEASE=${{ github.sha }}"
|
||||
echo "SENTRY_DSN_JOURNAL=$SENTRY_DSN_JOURNAL"
|
||||
echo "SENTRY_DSN_PLANNER=$SENTRY_DSN_PLANNER"
|
||||
} >> infrastructure/staging.env
|
||||
|
||||
- name: Copy compose file + env to server
|
||||
|
|
@ -220,6 +229,8 @@ jobs:
|
|||
# PR-preview journals all share the persistent staging planner.
|
||||
echo "PLANNER_URL=https://planner.staging.trails.cool"
|
||||
echo "SENTRY_RELEASE=${{ github.event.pull_request.head.sha }}"
|
||||
echo "SENTRY_DSN_JOURNAL=$SENTRY_DSN_JOURNAL"
|
||||
echo "SENTRY_DSN_PLANNER=$SENTRY_DSN_PLANNER"
|
||||
} >> infrastructure/staging-pr-${{ steps.ports.outputs.pr }}.env
|
||||
|
||||
- name: Generate per-PR Caddyfile snippet
|
||||
|
|
|
|||
|
|
@ -23,11 +23,16 @@ RUN pnpm install --frozen-lockfile
|
|||
|
||||
FROM base AS build
|
||||
ARG SENTRY_RELEASE
|
||||
# Client-side Sentry DSN baked into the bundle at build time. Empty (or
|
||||
# unset) produces a Sentry-free client. Public-by-design: the DSN
|
||||
# appears in the shipped client JS regardless.
|
||||
ARG VITE_SENTRY_DSN=""
|
||||
COPY --from=deps /app/ ./
|
||||
COPY . .
|
||||
RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN \
|
||||
SENTRY_AUTH_TOKEN="$(cat /run/secrets/SENTRY_AUTH_TOKEN 2>/dev/null | tr -d '\n\r')" \
|
||||
SENTRY_RELEASE="$SENTRY_RELEASE" \
|
||||
VITE_SENTRY_DSN="$VITE_SENTRY_DSN" \
|
||||
pnpm --filter @trails-cool/journal build
|
||||
|
||||
FROM base AS runtime
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@ 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;
|
||||
// Build-time DSN injection. `VITE_SENTRY_DSN` (set during `pnpm build`,
|
||||
// see apps/journal/Dockerfile + cd-apps.yml) determines whether the
|
||||
// client emits Sentry events at all. Self-hosted builds without the
|
||||
// env var produce a Sentry-free client bundle.
|
||||
const CLIENT_DSN = (import.meta.env as Record<string, string | undefined>)
|
||||
.VITE_SENTRY_DSN;
|
||||
|
||||
export function initSentryClient() {
|
||||
if (initialized) return;
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ import { createBoss, startWorker } from "@trails-cool/jobs";
|
|||
import { getDatabaseUrl } from "@trails-cool/db";
|
||||
import postgres, { type Sql } from "postgres";
|
||||
|
||||
// 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 DSN is supplied via env. No hardcoded fallback — self-hosted
|
||||
// instances default to no Sentry. Flagship sets SENTRY_DSN via
|
||||
// docker-compose env (see infrastructure/docker-compose.yml).
|
||||
// SENTRY_DISABLED=true is an explicit opt-out even when a DSN is set
|
||||
// (useful for local prod-mode debugging).
|
||||
const sentryDsn = process.env.SENTRY_DSN;
|
||||
if (process.env.SENTRY_DISABLED !== "true" && sentryDsn) {
|
||||
Sentry.init({
|
||||
dsn: sentryDsn,
|
||||
...nodeSentryConfig("journal server"),
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@ import { getDatabaseUrl } from "@trails-cool/db";
|
|||
import postgres, { type Sql } from "postgres";
|
||||
|
||||
// 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 !== "") {
|
||||
const sentryDsn = process.env.SENTRY_DSN;
|
||||
if (process.env.SENTRY_DISABLED !== "true" && sentryDsn) {
|
||||
Sentry.init({
|
||||
dsn: sentryDsn,
|
||||
...nodeSentryConfig("planner server"),
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ services:
|
|||
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set}
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
SENTRY_DSN: ${SENTRY_DSN_JOURNAL:-}
|
||||
SENTRY_RELEASE: ${SENTRY_RELEASE:-}
|
||||
SMTP_URL: ""
|
||||
SMTP_FROM: trails.cool staging <noreply@staging.trails.cool>
|
||||
|
|
@ -89,6 +90,7 @@ services:
|
|||
DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/${STAGING_DATABASE}
|
||||
NODE_ENV: production
|
||||
PORT: 3001
|
||||
SENTRY_DSN: ${SENTRY_DSN_PLANNER:-}
|
||||
SENTRY_RELEASE: ${SENTRY_RELEASE:-}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:3001/health || exit 1"]
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ services:
|
|||
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set in SOPS secrets.app.env}
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
# SENTRY_DSN per service (journal + planner have separate Sentry
|
||||
# projects on the same org). Empty = no Sentry init (correct
|
||||
# default for self-hosted instances). See cd-apps.yml for how the
|
||||
# flagship populates this.
|
||||
SENTRY_DSN: ${SENTRY_DSN_JOURNAL:-}
|
||||
SENTRY_RELEASE: ${SENTRY_RELEASE:-}
|
||||
SMTP_URL: ${SMTP_URL:-}
|
||||
SMTP_FROM: ${SMTP_FROM:-trails.cool <noreply@trails.cool>}
|
||||
|
|
@ -87,6 +92,7 @@ services:
|
|||
DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/trails
|
||||
NODE_ENV: production
|
||||
PORT: 3001
|
||||
SENTRY_DSN: ${SENTRY_DSN_PLANNER:-}
|
||||
SENTRY_RELEASE: ${SENTRY_RELEASE:-}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:3001/health || exit 1"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue