chore(oss): Sentry opt-in configurável + licença MIT + política de suporte/LGPD

Sentry (modelo comunidade com guardrails):
- DSN deixa de ser hardcoded; resolveSentryDsn() lê SENTRY_DSN em runtime
  (server/edge via process.env; browser via window.__PUBLIC_ENV__)
- default = Sentry da comunidade; SENTRY_DSN=off desliga; <dsn> aponta pro próprio
- opt-out vale no browser sem rebuild (via PublicEnvScript)
- beforeSend sanitizado (CPF/telefone/e-mail) mantido

Licença: adiciona LICENSE MIT (Copyright 2026 Rafael Melgaço).

README: política de suporte 'as-is', responsabilidades self-host, disclaimer
LGPD (quem hospeda é o controlador) e como ligar/desligar a telemetria.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHfaYtmvsCjiKfdWDLsMdK
This commit is contained in:
Rafael Melgaço
2026-07-06 21:10:55 -03:00
co-authored by Claude Opus 4.8
parent c890b4030f
commit 07eb106303
9 changed files with 87 additions and 5 deletions
+5 -1
View File
@@ -92,8 +92,12 @@ RESEND_API_KEY=
RESEND_FROM_EMAIL=
# -----------------------------------------------------------------------------
# 11) Observabilidade (opcional)
# 11) Observabilidade (Sentry) — relatórios de erro
# -----------------------------------------------------------------------------
# Deixe VAZIO para enviar erros anonimizados ao Sentry da comunidade (ajuda a
# melhorar o CRM; CPF/telefone/e-mail são removidos antes do envio).
# SENTRY_DSN=off → desliga TODA a telemetria (nada é enviado)
# SENTRY_DSN=<seu-dsn> → envia os erros pro SEU próprio Sentry
SENTRY_DSN=
# -----------------------------------------------------------------------------
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Rafael Melgaço
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+26 -1
View File
@@ -206,7 +206,32 @@ Detalhe wave-by-wave: [`docs/stories/epics/MASTER.md`](docs/stories/epics/MASTER
## 📜 Licença
> ⚠️ **A definir.** O projeto está sendo liberado pra comunidade — a licença final (MIT, Apache-2.0, AGPL-3.0 ou outra) será definida antes do release público. Por enquanto, considere "all rights reserved" até o `LICENSE` ser commitado.
Distribuído sob a licença **MIT** — veja [`LICENSE`](LICENSE). Você pode usar, modificar
e distribuir livremente, inclusive comercialmente. O software é fornecido **"como está",
sem garantias** (ver cláusula de isenção no `LICENSE`).
---
## 🛟 Suporte & responsabilidades (self-host)
Este é um projeto **self-host**: cada pessoa roda o CRM na **própria infraestrutura**
(VPS, banco Supabase e chave de IA próprios). Isso implica:
- **Suporte é comunitário e "as-is".** Dúvidas e bugs entram como
[Issues](https://github.com/melgarafael/DeskcommCRM/issues) ou
[Discussions](https://github.com/melgarafael/DeskcommCRM/discussions). Não há SLA nem
suporte garantido — é open source mantido por boa vontade.
- **Você é responsável pela sua instalação.** Atualizações não são automáticas
(`bash hostgator-setup-kit/update.sh` quando quiser), e manter/backup do seu servidor
é com você.
- **LGPD — atenção:** quem **hospeda** a instância é o **controlador** dos dados pessoais
ali tratados (clientes, conversas, pedidos), com as obrigações legais decorrentes. Os
mantenedores do projeto **não têm acesso** aos seus dados e **não são** controladores
nem operadores da sua instância.
- **Telemetria (Sentry):** por padrão, erros **anonimizados** (CPF/telefone/e-mail
removidos) são enviados ao Sentry da comunidade pra ajudar a corrigir bugs que afetam
todos. Para **desligar**, use `SENTRY_DSN=off` no `.env`; para enviar ao **seu** Sentry,
use `SENTRY_DSN=<seu-dsn>`. Ver [`lib/sentry/dsn.ts`](lib/sentry/dsn.ts).
---
+3
View File
@@ -19,6 +19,9 @@ export async function PublicEnvScript() {
const payload = JSON.stringify({
NEXT_PUBLIC_SUPABASE_URL: env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
// Exposto pro Sentry do browser respeitar o opt-out (SENTRY_DSN=off) em runtime,
// sem rebuild. DSN não é segredo. Ver lib/sentry/dsn.ts.
SENTRY_DSN: env.SENTRY_DSN,
})
// Evita quebrar o </script> se algum valor contiver a sequência.
.replace(/</g, "\\u003c");
+4 -1
View File
@@ -3,6 +3,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
import { resolveSentryDsn } from "./lib/sentry/dsn";
const SENSITIVE_HEADERS = [
"authorization",
@@ -21,7 +22,9 @@ function scrubMessage(input: string): string {
}
Sentry.init({
dsn: "https://58fabf8ad54504863d404a3647ef3714@o4509908078559232.ingest.us.sentry.io/4509908083212288",
dsn: resolveSentryDsn(
typeof window !== "undefined" ? window.__PUBLIC_ENV__?.SENTRY_DSN : undefined,
),
integrations: [Sentry.replayIntegration()],
+23
View File
@@ -0,0 +1,23 @@
/**
* DSN do Sentry com opt-out em runtime — modelo "telemetria de comunidade".
*
* Por padrão, erros vão pro Sentry do projeto (DEFAULT_SENTRY_DSN): num open source
* self-host, é o que dá visibilidade pra corrigir bugs que afetam todo mundo. Quem
* hospeda controla isso pelo `.env`, SEM rebuild da imagem:
*
* SENTRY_DSN=off → desliga toda a telemetria (nada é enviado)
* SENTRY_DSN=<seu-dsn> → manda os erros pro SEU Sentry
* SENTRY_DSN= (vazio) → usa o Sentry da comunidade (padrão)
*
* Vale para servidor (process.env) e navegador (window.__PUBLIC_ENV__.SENTRY_DSN,
* injetado em runtime pelo <PublicEnvScript/>). O DSN não é segredo — DSNs do Sentry
* são públicos por design.
*/
export const DEFAULT_SENTRY_DSN =
"https://58fabf8ad54504863d404a3647ef3714@o4509908078559232.ingest.us.sentry.io/4509908083212288";
export function resolveSentryDsn(value: string | undefined | null): string | undefined {
const v = (value ?? "").trim().toLowerCase() === "off" ? "off" : (value ?? "").trim();
if (v === "off" || v === "false" || v === "0") return undefined;
return v.length > 0 ? v : DEFAULT_SENTRY_DSN;
}
+2 -1
View File
@@ -2,6 +2,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
import { resolveSentryDsn } from "./lib/sentry/dsn";
const SENSITIVE_HEADERS = [
"authorization",
@@ -20,7 +21,7 @@ function scrubMessage(input: string): string {
}
Sentry.init({
dsn: "https://58fabf8ad54504863d404a3647ef3714@o4509908078559232.ingest.us.sentry.io/4509908083212288",
dsn: resolveSentryDsn(process.env.SENTRY_DSN),
tracesSampleRate: 1,
enableLogs: true,
+2 -1
View File
@@ -3,6 +3,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
import { resolveSentryDsn } from "./lib/sentry/dsn";
const SENSITIVE_HEADERS = [
"authorization",
@@ -21,7 +22,7 @@ function scrubMessage(input: string): string {
}
Sentry.init({
dsn: "https://58fabf8ad54504863d404a3647ef3714@o4509908078559232.ingest.us.sentry.io/4509908083212288",
dsn: resolveSentryDsn(process.env.SENTRY_DSN),
tracesSampleRate: 1,
enableLogs: true,
+1
View File
@@ -9,6 +9,7 @@
interface PublicEnv {
NEXT_PUBLIC_SUPABASE_URL?: string;
NEXT_PUBLIC_SUPABASE_ANON_KEY?: string;
SENTRY_DSN?: string;
}
interface Window {