mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-08 16:02:24 +00:00
feat: read the dashboard's Sentry DSN from the container-injected runtime config instead of a literal DSN in web/src/main.tsx, so a self-hosted install reports its users' browser errors, URLs and IPs nowhere unless the operator sets WARMBLY_SENTRY_DSN, and document the variable in configuration.mdx and the no-reporting-by-default stance in data-control.mdx
This commit is contained in:
@@ -561,6 +561,9 @@ services:
|
||||
# captcha is off server-side. Set WARMBLY_TURNSTILE_KEY to your site key
|
||||
# when you set CAPTCHA_PROVIDER=turnstile.
|
||||
WARMBLY_TURNSTILE_KEY: ${WARMBLY_TURNSTILE_KEY:-1x00000000000000000000AA}
|
||||
# Unset means the dashboard initialises no error reporting and contacts
|
||||
# no Sentry host. Point it at your own project to collect browser errors.
|
||||
WARMBLY_SENTRY_DSN: ${WARMBLY_SENTRY_DSN:-}
|
||||
depends_on:
|
||||
backend: { condition: service_healthy }
|
||||
|
||||
|
||||
@@ -380,8 +380,11 @@ Delayed sends run through the local poller, so the backend must be running for s
|
||||
| Variable | What it does | Default |
|
||||
|---|---|---|
|
||||
| `SENTRY_DSN` | Error reporting. Optional in every environment, including `prod` | unset |
|
||||
| `WARMBLY_SENTRY_DSN` | Browser error reporting for the dashboard container, read at container start like the other `WARMBLY_*` values. Unset means the dashboard initialises no reporting SDK and contacts no Sentry host | unset |
|
||||
| `APNS_KEY` or `APNS_KEY_PATH`, `APNS_KEY_ID`, `APNS_TEAM_ID`, `APNS_TOPIC` | Mobile push on backend and consumer. Partial configuration disables push with a warning, never a crash | unset |
|
||||
|
||||
Error reporting is off by default everywhere. A DSN is the operator's choice, and each of these is read by one process, so pointing the backend at a project does not make the dashboard report too. Both accept Sentry Cloud, a self-hosted Sentry or any Sentry-compatible server.
|
||||
|
||||
## Updates
|
||||
|
||||
| Variable | What it does | Default | Restart needed |
|
||||
|
||||
@@ -206,6 +206,18 @@ A self-hosted Warmbly makes no outbound call of its own except one, and it is of
|
||||
|
||||
Everything else is you: mail through the mailboxes you connect, DNS lookups for the domains you check, and whatever integrations you configure. There is no telemetry, no phone-home, and no license check.
|
||||
|
||||
### Error reporting
|
||||
|
||||
An instance reports errors nowhere unless you point it somewhere. Every service reads its own DSN and none of them ship with one, so a default install sends no crash, no stack trace and no browser error to anybody, including us.
|
||||
|
||||
| Service | Variable |
|
||||
|---|---|
|
||||
| Backend, consumer, worker | `SENTRY_DSN` |
|
||||
| Dashboard container | `WARMBLY_SENTRY_DSN` |
|
||||
| Realtime | `SENTRY_DSN` |
|
||||
|
||||
Set one and that service reports to whatever Sentry Cloud project, self-hosted Sentry or Sentry-compatible server you name. Leave it unset, which is the default the installer writes, and the SDK is never initialised: there is no host to contact and nothing to opt out of.
|
||||
|
||||
## See also
|
||||
|
||||
- [Install](/development/install/): the wizard that asks all of this up front
|
||||
|
||||
@@ -7,7 +7,8 @@ cat > /usr/share/nginx/html/config.js <<EOF
|
||||
window.__WARMBLY_ENV__ = {
|
||||
API_URL: "${WARMBLY_API_URL:-}",
|
||||
APP_URL: "${WARMBLY_APP_URL:-}",
|
||||
TURNSTILE_KEY: "${WARMBLY_TURNSTILE_KEY:-}"
|
||||
TURNSTILE_KEY: "${WARMBLY_TURNSTILE_KEY:-}",
|
||||
SENTRY_DSN: "${WARMBLY_SENTRY_DSN:-}"
|
||||
};
|
||||
EOF
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import type Session from "@/lib/api/models/auth/Session";
|
||||
import beginSSO from "@/lib/api/client/auth/beginSSO";
|
||||
import type { AppError } from "@/lib/api/client/normalizeError";
|
||||
import buildError from "@/lib/helper/buildError";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { captureException } from "@/lib/observability";
|
||||
import type Token from "@/lib/api/models/auth/Token";
|
||||
import {
|
||||
beginPasskeyLogin,
|
||||
@@ -311,7 +311,7 @@ export default function LoginPage() {
|
||||
await completeSession(token);
|
||||
} catch (e) {
|
||||
// Cancel / no-passkey is expected here; report only real failures.
|
||||
if (!(e instanceof PasskeyCancelled)) Sentry.captureException(e);
|
||||
if (!(e instanceof PasskeyCancelled)) captureException(e);
|
||||
}
|
||||
}, [completeSession]);
|
||||
|
||||
@@ -327,7 +327,7 @@ export default function LoginPage() {
|
||||
})
|
||||
.catch((e) => {
|
||||
setPasskeyStatus("error");
|
||||
Sentry.captureException(e);
|
||||
captureException(e);
|
||||
})
|
||||
.finally(() => {
|
||||
explicitPasskeyChallengePendingRef.current = false;
|
||||
|
||||
@@ -9,6 +9,8 @@ export const API_URL = runtimeEnv("API_URL", import.meta.env.VITE_API_URL);
|
||||
// (no path), so this is the single place the /v1 prefix is applied.
|
||||
export const API_BASE_URL = `${API_URL}/v1`;
|
||||
export const TURNSTILE_KEY = runtimeEnv("TURNSTILE_KEY", import.meta.env.VITE_TURNSTILE_KEY);
|
||||
// Empty means browser error reporting is never initialised. See lib/observability.
|
||||
export const SENTRY_DSN = runtimeEnv("SENTRY_DSN", import.meta.env.VITE_SENTRY_DSN);
|
||||
export const HUMAN_VERIFICATION_FAIL = "We couldn’t verify you’re human. Please try the security check again or reload the page.";
|
||||
export const PASSWORD_FAIL = "The password must be at least 8 characters long and contain both uppercase and lowercase letters, as well as a number."
|
||||
export const TOKEN_KEY = "auth_token";
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Browser error reporting.
|
||||
//
|
||||
// The DSN is the operator's choice, not a requirement of the software: the
|
||||
// dashboard image is the same for the hosted service and for a self-host, so a
|
||||
// literal DSN in the bundle would make every self-hosted install report its
|
||||
// users' errors, URLs and IPs to somebody else's Sentry. It comes from the
|
||||
// container-injected runtime config instead, and an unset DSN means the SDK is
|
||||
// never initialised, so nothing is ever sent anywhere.
|
||||
//
|
||||
// The SDK is imported statically rather than lazily so that its global handlers
|
||||
// are installed before the first render: a broken deploy fails during boot, and
|
||||
// a chunk still in flight would miss exactly that error. Not initialising it
|
||||
// costs a self-hoster some dead bundle weight and zero network calls.
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { SENTRY_DSN } from "./information";
|
||||
|
||||
let reporting = false;
|
||||
|
||||
// initErrorReporting is called once, before the app renders.
|
||||
export function initErrorReporting(): void {
|
||||
if (!SENTRY_DSN) return;
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
sendDefaultPii: true,
|
||||
environment: import.meta.env.MODE,
|
||||
});
|
||||
reporting = true;
|
||||
}
|
||||
|
||||
// captureException reports an error the app handled itself. A no-op when no DSN
|
||||
// is configured.
|
||||
export function captureException(error: unknown): void {
|
||||
if (!reporting) return;
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
+2
-6
@@ -65,13 +65,9 @@ import NotFound from './app/not-found';
|
||||
|
||||
import { Toaster } from '@/components/ui/toaster';
|
||||
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { initErrorReporting } from "@/lib/observability";
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://412466daced4b1d85ee040eef66efc95@o4510248538472448.ingest.us.sentry.io/4510248563113984",
|
||||
sendDefaultPii: true,
|
||||
environment: import.meta.env.MODE
|
||||
})
|
||||
initErrorReporting();
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||
|
||||
Reference in New Issue
Block a user