feat(analytics): inject Sentry DSN + PostHog key from build env (#367)

* fix(analytics): bake real Sentry DSN and lower trace sampling

The bake script emitted a placeholder Sentry DSN even in on mode, so every
published image initialized Sentry against a dead endpoint and no events ever
reached the project. Point it at the real snapotter project DSN.

Also drop tracesSampleRate from 1 to 0.1. It governs only performance
transactions (errors are always captured), so 100% fleet-wide tracing would
drain Sentry quota for no benefit.

* fix(analytics): point baked Sentry DSN at the snapotter org

* refactor(analytics): inject Sentry DSN + PostHog key from build env

#336 replaced the analytics creds with placeholders but never added a way to
put real values back at build time, so any image built from the repo since then
ships dead analytics (the live fleet only still reports because publishing is
paused and it runs a pre-placeholder image).

Restore the pipeline the clean way: bake-analytics.mjs reads SNAPOTTER_SENTRY_DSN
and SNAPOTTER_POSTHOG_KEY from the environment; the official image's CI supplies
them from repo secrets via build args. A build with neither stays disabled, so
building from source never phones home. Both values are public (they ship in the
browser bundle), so this is about not making source builds report, not secrecy.

Supersedes the hardcoded DSN: real creds are no longer committed to the repo.
This commit is contained in:
SnapOtter
2026-06-29 10:41:58 +08:00
committed by GitHub
parent 5a51ae6a83
commit 9819c5885e
3 changed files with 26 additions and 9 deletions
+2
View File
@@ -239,6 +239,8 @@ jobs:
platforms: ${{ matrix.platform }}
build-args: |
SNAPOTTER_ANALYTICS=on
SNAPOTTER_POSTHOG_KEY=${{ secrets.SNAPOTTER_POSTHOG_KEY }}
SNAPOTTER_SENTRY_DSN=${{ secrets.SNAPOTTER_SENTRY_DSN }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=snapotter/snapotter,ghcr.io/snapotter-hq/snapotter",push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=ghcr.io/snapotter-hq/snapotter:cache-${{ env.PLATFORM_PAIR }}
+8 -1
View File
@@ -62,9 +62,16 @@ COPY apps/web/public ./apps/web/public
# Bake analytics config into the shared package before building the frontend.
# The published image ships with analytics ON; self-builders can override:
# docker compose build --build-arg SNAPOTTER_ANALYTICS=off
# The real Sentry DSN + PostHog key are supplied as build args (public values,
# sourced from CI secrets in the official build); a build without them stays
# silent, so building from source never phones home.
ARG SNAPOTTER_ANALYTICS=on
ARG SNAPOTTER_POSTHOG_KEY=
ARG SNAPOTTER_SENTRY_DSN=
COPY scripts/bake-analytics.mjs ./scripts/
RUN node scripts/bake-analytics.mjs ${SNAPOTTER_ANALYTICS}
RUN SNAPOTTER_POSTHOG_KEY="${SNAPOTTER_POSTHOG_KEY}" \
SNAPOTTER_SENTRY_DSN="${SNAPOTTER_SENTRY_DSN}" \
node scripts/bake-analytics.mjs ${SNAPOTTER_ANALYTICS}
# Build only the web frontend (API runs from TS source via tsx)
RUN --mount=type=cache,id=turbo-cache,target=/app/.turbo \
+16 -8
View File
@@ -1,5 +1,5 @@
import { writeFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -7,13 +7,21 @@ const outPath = resolve(__dirname, "../packages/shared/src/analytics/baked.ts");
const mode = process.argv[2] || "on";
const enabled = mode === "on";
const posthogApiKey = enabled ? "phc_REPLACE_WITH_REAL_KEY" : "";
const posthogHost = enabled ? "https://us.i.posthog.com" : "";
const sentryDsn = enabled
? "https://REPLACE_WITH_REAL_DSN@o0.ingest.us.sentry.io/0"
: "";
const sampleRate = enabled ? 1 : 0;
const on = mode === "on";
// Real telemetry creds are injected at build time from the environment -- the
// official image's CI supplies them from secrets, so they are NOT committed and
// a build from source stays silent. A Sentry DSN and a PostHog project key are
// public (they ship in the browser bundle), so this is about not making forks /
// source builds phone home by default, not about secrecy.
const posthogApiKey = on ? (process.env.SNAPOTTER_POSTHOG_KEY ?? "") : "";
const sentryDsn = on ? (process.env.SNAPOTTER_SENTRY_DSN ?? "") : "";
const posthogHost = posthogApiKey ? "https://us.i.posthog.com" : "";
// Enabled only when turned on AND there is somewhere to report to, so a
// credential-less source build never initializes the SDKs.
const enabled = on && (posthogApiKey !== "" || sentryDsn !== "");
// tracesSampleRate only governs performance transactions; errors are always
// captured. Keep low so fleet-wide tracing does not drain Sentry quota.
const sampleRate = sentryDsn ? 0.1 : 0;
const content = `// AUTO-GENERATED by scripts/bake-analytics.mjs -- do not edit manually
export const ANALYTICS_BAKED = {