diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c72ad5c8..508d0e6d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 }} diff --git a/docker/Dockerfile b/docker/Dockerfile index eb41df78..7270fcd8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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 \ diff --git a/scripts/bake-analytics.mjs b/scripts/bake-analytics.mjs index 42e368d6..5f03c4fd 100644 --- a/scripts/bake-analytics.mjs +++ b/scripts/bake-analytics.mjs @@ -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 = {