mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
350 lines
18 KiB
Bash
350 lines
18 KiB
Bash
# =============================================================================
|
||
# DeskcommCRM — template de ambiente para self-host na HostGator (VPS + Docker).
|
||
# Copie para `.env` e preencha. O install.sh gera os segredos automaticamente.
|
||
#
|
||
# Regra de ouro:
|
||
# - NEXT_PUBLIC_* = embutidas no BUILD (baked na imagem). Trocar = REBUILD.
|
||
# - as demais = injetadas em RUNTIME pelo docker-compose (trocar = restart).
|
||
# =============================================================================
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 0) AS TRÊS IMAGENS (genéricas e pré-buildadas — o leigo só puxa, não builda)
|
||
# -----------------------------------------------------------------------------
|
||
# O install.sh preenche isto com a ÚLTIMA VERSÃO PUBLICADA, não com um canal
|
||
# móvel, e as três sempre na MESMA versão. Regra em docs/doctrine/packaging.md.
|
||
#
|
||
# Sobre as tags:
|
||
# 1.2.1 → uma release, para sempre. É o que uma instalação de verdade usa.
|
||
# stable → a última release publicada (move a cada release)
|
||
# latest → o TOPO DA MAIN. Apesar do nome, NÃO é a última release: é código
|
||
# ainda não lançado. Use só para avaliar o projeto — nunca num
|
||
# servidor que atende cliente.
|
||
#
|
||
# pull_policy acompanha a tag: imutável usa `missing`, móvel usa `always`.
|
||
# Não é preferência — foi medido que, com `always` e o registro sem responder
|
||
# para aquela referência, o `up -d` FALHA e o contêiner não sobe, mesmo com a
|
||
# imagem já no disco. Numa tag imutável isso só amarra a subida do seu CRM à
|
||
# disponibilidade do GHCR.
|
||
# Os valores abaixo são o PISO seguro para quem preenche à mão (stable = a
|
||
# última release). O install.sh sobrescreve com o NÚMERO da versão, que é o que
|
||
# uma instalação de verdade usa — ver a regra de ouro na doutrina.
|
||
# troque pelo seu fork se publicar o seu
|
||
APP_IMAGE=ghcr.io/melgarafael/deskcommcrm:stable
|
||
APP_PULL_POLICY=always
|
||
|
||
# O worker (agente de IA 24/7) e o scheduler (crons) acompanham a versão do app.
|
||
# Até 2026-08-13 eles não tinham imagem: eram compilados na sua VPS no install e
|
||
# nenhum update.sh jamais os reconstruía — o agente ficava congelado no código
|
||
# do dia da instalação. Deixe-os na mesma versão do APP_IMAGE acima.
|
||
WORKER_IMAGE=ghcr.io/melgarafael/deskcomm-worker:stable
|
||
WORKER_PULL_POLICY=always
|
||
SCHEDULER_IMAGE=ghcr.io/melgarafael/deskcomm-scheduler:stable
|
||
SCHEDULER_PULL_POLICY=always
|
||
|
||
# ── Chamada de voz WhatsApp (spec 18) — DESLIGADA, e é assim que ela nasce ────
|
||
#
|
||
# Ligá-la vincula um SEGUNDO APARELHO ao mesmo número que já atende, por um
|
||
# caminho que não é o oficial: o risco é a CONTA ser bloqueada pelo WhatsApp, e
|
||
# com ela o canal onde você vende. Nenhuma atualização liga isto sozinha.
|
||
#
|
||
# COMO LIGAR, se você aceitar esse risco:
|
||
# 1. troque a linha abaixo por COMPOSE_PROFILES=voz
|
||
# 2. troque a de baixo por WACALLS_API_BASE_URL=http://wacalls:8080
|
||
# 3. rode ./update.sh
|
||
# 4. na tela, em Configurações › Segurança, ligue e aceite o aviso.
|
||
#
|
||
# COMO DESLIGAR: faça o contrário do passo 4 (a tela desconecta o aparelho de
|
||
# verdade), depois esvazie as duas linhas e rode ./update.sh.
|
||
#
|
||
# Esta chave é lida pelo próprio `docker compose`: vazia, o serviço `wacalls`
|
||
# nem é criado.
|
||
COMPOSE_PROFILES=
|
||
WACALLS_API_BASE_URL=
|
||
# Gerados pelo instalador. O serviço de voz NÃO SOBE sem usuário e senha de
|
||
# administrador, e o app só fala com ele pelo token. Não precisa mexer.
|
||
WACALLS_ADMIN_USER=
|
||
WACALLS_ADMIN_PASSWORD=
|
||
WACALLS_API_TOKEN=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 1) DOMÍNIO / HTTPS (você define; aponte o A-record deste domínio pro IP do VPS)
|
||
# -----------------------------------------------------------------------------
|
||
DOMAIN=crm.seudominio.com.br
|
||
# e-mail do Let's Encrypt (avisos de cert)
|
||
ACME_EMAIL=voce@seudominio.com.br
|
||
|
||
# Quem responde nas portas 80/443:
|
||
# caddy → o kit sobe o próprio Caddy (padrão; VPS "cru")
|
||
# traefik → o VPS JÁ tem um Traefik nessas portas (Hostinger, Coolify,
|
||
# Dokploy...). O install.sh detecta isso sozinho e preenche aqui.
|
||
# Em modo traefik é preciso subir com o override:
|
||
# docker compose -f docker-compose.prod.yml -f docker-compose.traefik.yml up -d
|
||
# (os scripts do kit já fazem isso sozinhos ao ler esta variável).
|
||
#
|
||
# DEIXE COMENTADO. O install.sh só detecta quando a variável está VAZIA — se você
|
||
# copiar este arquivo com `REVERSE_PROXY=caddy` preenchido, a detecção não roda e a
|
||
# instalação numa VPS com proxy próprio volta a morrer no bind da porta.
|
||
# Descomente apenas para forçar um modo à mão.
|
||
#REVERSE_PROXY=caddy
|
||
|
||
# Só lidas quando REVERSE_PROXY=traefik, e o install.sh preenche sozinho.
|
||
# TRAEFIK_NETWORK é a bridge onde o Traefik encontra o app — o app é anexado a ela.
|
||
# Se o Traefik da hospedagem tem rede própria, é a rede DELE (o nome varia por
|
||
# painel: `coolify`, `dokploy-network`, `traefik`). Se ele roda em `--network host`
|
||
# (Hostinger), ele não tem rede no Docker e o install.sh cria uma bridge do projeto:
|
||
# `<nome do projeto no compose>_proxy` — que NÃO é o nome cru da pasta, e sim o que
|
||
# o compose faz com ele (minúsculas, só [a-z0-9_-], `_`/`-` iniciais aparados). Numa
|
||
# pasta `CRM.Host_Teste` a rede é `crmhost_teste_proxy`. Nos dois casos o install.sh
|
||
# descobre e preenche sozinho.
|
||
# DEIXE COMENTADO: valor preenchido aqui PULA a detecção. O instalador recusa o
|
||
# que não é bridge ou não existe, mas uma bridge que existe e não é a do proxy ele
|
||
# não tem como desmentir — e aí o site sobe sem responder.
|
||
#TRAEFIK_NETWORK=traefik
|
||
#TRAEFIK_ENTRYPOINT=websecure
|
||
#TRAEFIK_ENTRYPOINT_HTTP=web
|
||
#TRAEFIK_CERTRESOLVER=letsencrypt
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 2) SUPABASE (crie um projeto grátis em supabase.com → Settings → API/Database)
|
||
# -----------------------------------------------------------------------------
|
||
# BUILD-TIME (baked na imagem):
|
||
NEXT_PUBLIC_SUPABASE_URL=https://SEU-PROJETO.supabase.co
|
||
# anon key (Settings → API)
|
||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||
# RUNTIME:
|
||
# service_role key — BYPASSA RLS, só server
|
||
SUPABASE_SERVICE_ROLE_KEY=
|
||
# connection string (Settings → Database) — a do APP (vai para os contêineres)
|
||
SUPABASE_DB_URL=
|
||
# Só para Supabase PRÓPRIO: a conexão do DONO do banco, que roda o schema
|
||
# (create extension, baseline.sql, promoção do dono, pg_dump do backup). Na
|
||
# nuvem não preencha — a de cima já é privilegiada. O install.sh NÃO grava esta
|
||
# chave; descomentar aqui é uma escolha sua, e vale a pena para que o update.sh
|
||
# (que roda sozinho pelo cron) continue conseguindo aplicar migration nova.
|
||
# Atenção: o compose entrega o .env inteiro ao app e ao worker (env_file), então
|
||
# declará-la aqui a expõe aos contêineres — se preferir não expor, passe-a só no
|
||
# comando: SUPABASE_DB_ADMIN_URL='...' bash hostgator-setup-kit/update.sh
|
||
#SUPABASE_DB_ADMIN_URL=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 3) APP URLs (BUILD-TIME — precisam bater com o DOMAIN acima; trocar = rebuild)
|
||
# -----------------------------------------------------------------------------
|
||
NEXT_PUBLIC_APP_URL=https://crm.seudominio.com.br
|
||
NEXT_PUBLIC_ADMIN_URL=https://crm.seudominio.com.br
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 4) IA (Anthropic via AI Gateway) — paga por uso, chave sua
|
||
# -----------------------------------------------------------------------------
|
||
# ou ANTHROPIC_API_KEY abaixo. As chaves de IA são OPCIONAIS: o CRM sobe sem
|
||
# nenhuma delas e você cadastra a sua depois pela tela, em IA › Credenciais
|
||
# (fica cifrada no banco). Sem chave, o agente de IA não responde.
|
||
AI_GATEWAY_API_KEY=
|
||
ANTHROPIC_API_KEY=
|
||
|
||
# OpenRouter (OPCIONAL) — alternativa ao gateway da Vercel, compatível com a API
|
||
# da OpenAI, com catálogo grande de modelos num único faturamento.
|
||
# Ordem de resolução do chat: AI_GATEWAY_API_KEY > OPENROUTER_API_KEY > provider
|
||
# direto (ANTHROPIC_API_KEY / OPENAI_API_KEY). Vazio = nada muda.
|
||
#
|
||
# ALCANCE — leia antes de trocar. Esta chave roteia a classificação de
|
||
# sentimento, o bot de resposta E o AGENTE do CRM quando a organização está em
|
||
# OpenRouter sem credencial cadastrada na tela — que é exatamente o estado de
|
||
# quem instalou pelo kit escolhendo a opção [1] e nunca abriu Agente de IA →
|
||
# Provedores. O agente aceita os quatro provedores (Anthropic, OpenAI, Google,
|
||
# OpenRouter); a credencial cadastrada por organização vence esta chave.
|
||
# Ela NÃO escolhe o provedor: quem escolhe é o instalador (AI_PROVIDER) ou a
|
||
# tela. Preencher aqui com a organização em Anthropic não migra o agente.
|
||
# Isso importa porque o agente opera por FERRAMENTAS (contatos, leads, funil,
|
||
# catálogo, transferência para humano), e modelo sem tool calling sólido não dá
|
||
# erro: responde texto plausível e nunca cria o lead nem move o card. Por isso o
|
||
# painel RECUSA salvar modelo sem ferramentas nos pontos "Responder o cliente" e
|
||
# "Trabalhar o funil", pelo que o fabricante declara no catálogo.
|
||
OPENROUTER_API_KEY=
|
||
# vazio = https://openrouter.ai/api/v1
|
||
OPENROUTER_BASE_URL=
|
||
# opcional — transcrição de áudio; embeddings do RAG também aceitam OpenRouter
|
||
OPENAI_API_KEY=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 5) SEGREDOS GERADOS PELO install.sh (openssl). NÃO precisa preencher à mão.
|
||
# (comando de cada um ao lado, se quiser gerar manualmente)
|
||
# -----------------------------------------------------------------------------
|
||
# openssl rand -hex 32
|
||
INTERNAL_SECRET=
|
||
# opcional; vazio = usa INTERNAL_SECRET
|
||
INTERNAL_CRON_SECRET=
|
||
|
||
# Provisionamento de organizações por um sistema externo
|
||
# (POST /api/v1/tenants/provision). DESLIGADO por padrão: vazio, a rota
|
||
# responde 404. Para ligar, gere um segredo de 32+ caracteres
|
||
# (`openssl rand -hex 32`) e entregue-o só ao sistema que vai criar empresas —
|
||
# com ele, esse sistema cria organizações nesta instalação sem passar pela tela.
|
||
TENANT_PROVISIONING_SECRET=
|
||
# openssl rand -base64 32
|
||
CPF_ENCRYPTION_KEY=
|
||
# openssl rand -base64 32 (32 bytes)
|
||
AI_CRED_AES_KEY=
|
||
# openssl rand -base64 32
|
||
WAHA_BYO_ENCRYPTION_KEY=
|
||
# openssl rand -hex 32 (>=32 chars)
|
||
IMPERSONATE_COOKIE_SECRET=
|
||
# openssl rand -hex 32
|
||
LGPD_SIGNING_KEY=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 6) WhatsApp / WAHA (Core grátis por padrão; ver docker-compose.prod.yml)
|
||
# -----------------------------------------------------------------------------
|
||
# nome do serviço na rede docker (não mexer)
|
||
WAHA_API_BASE_URL=http://waha:3000
|
||
# WAHA→app interno; sem domínio público (não mexer)
|
||
WAHA_WEBHOOK_BASE_URL=http://app:3000
|
||
# retoma sessão pareada quando o contêiner do transporte reinicia
|
||
WHATSAPP_RESTART_ALL_SESSIONS=True
|
||
# gerado pelo install.sh (ver Task 6: hash vs plaintext)
|
||
WAHA_API_KEY=
|
||
# hash SHA512 hex de WAHA_API_KEY (o container usa este)
|
||
WAHA_API_KEY_SHA512=
|
||
# openssl rand -hex 32 (assinatura do webhook)
|
||
WAHA_HMAC_SECRET=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 7) Redis (roda no VPS via serverless-redis-http; sem conta Upstash)
|
||
# -----------------------------------------------------------------------------
|
||
# container srh (não mexer)
|
||
UPSTASH_REDIS_REST_URL=http://srh:80
|
||
# = SRH_TOKEN abaixo (mesmo valor)
|
||
UPSTASH_REDIS_REST_TOKEN=
|
||
# openssl rand -hex 32 (igual ao de cima)
|
||
SRH_TOKEN=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 8) FLAGS
|
||
# -----------------------------------------------------------------------------
|
||
NODE_ENV=production
|
||
# template genérico; true p/ ligar e-commerce
|
||
NUVEMSHOP_ENABLED=false
|
||
# false = IA real (true devolveria trace fake!)
|
||
INTERNAL_AGENT_RUN_STUB=false
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 9) Nuvemshop (só preencher se NUVEMSHOP_ENABLED=true — partners.tiendanube.com)
|
||
# -----------------------------------------------------------------------------
|
||
NUVEMSHOP_APP_ID=
|
||
NUVEMSHOP_CLIENT_ID=
|
||
NUVEMSHOP_CLIENT_SECRET=
|
||
NUVEMSHOP_OAUTH_ENCRYPTION_KEY=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 10) E-mail transacional (Resend — opcional; sem ele, convites mostram link na UI)
|
||
# -----------------------------------------------------------------------------
|
||
# RESEND_FROM_EMAIL tem de ser um endereço de domínio VERIFICADO na SUA conta
|
||
# Resend. Vazio = e-mail desligado, e é melhor assim: um domínio que não é seu
|
||
# faz todo envio ser recusado pela Resend com uma mensagem que não diz que o
|
||
# problema é configuração.
|
||
RESEND_API_KEY=
|
||
RESEND_FROM_EMAIL=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 10-b) E-mail transacional (SMTP — a alternativa à Resend acima)
|
||
# -----------------------------------------------------------------------------
|
||
# Os dois caminhos convivem: preenchido, o envio sai pelo SEU servidor de
|
||
# e-mail; vazio, continua pela Resend. Quem já roda com Resend não mexe em nada.
|
||
# É o caminho de quem não quer depender de serviço externo para mandar convite.
|
||
#
|
||
# A tela /admin/email grava os mesmos campos no banco, e o BANCO PREVALECE
|
||
# sobre estas variáveis — elas existem para provisionar a VPS sem abrir a
|
||
# interface, e são o piso de rollback.
|
||
#
|
||
# Use somente o hostname, sem smtp:// e sem :porta.
|
||
# SSL/TLS implícito: 465 + tls. STARTTLS: 587 + starttls.
|
||
# Host ou remetente vazio mantém o envio desligado sem falhar.
|
||
SMTP_HOST=
|
||
SMTP_PORT=587
|
||
SMTP_SECURITY=starttls
|
||
SMTP_USERNAME=
|
||
SMTP_PASSWORD=
|
||
SMTP_FROM_EMAIL=
|
||
SMTP_FROM_NAME=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 11) Observabilidade (Sentry) — relatórios de erro
|
||
# -----------------------------------------------------------------------------
|
||
# O install.sh pergunta na primeira instalação. A ausência desta chave no
|
||
# template é intencional: `SENTRY_DSN=""` passa a existir só DEPOIS que a pessoa
|
||
# aceita o Sentry da comunidade, e assim uma reexecução preserva essa escolha.
|
||
# Para pré-configurar sem pergunta, descomente UMA opção:
|
||
# SENTRY_DSN=off → desliga TODA a telemetria (nada é enviado)
|
||
# SENTRY_DSN= → aceita o Sentry da comunidade (erros anonimizados)
|
||
# SENTRY_DSN=<seu-dsn> → envia os erros pro SEU próprio Sentry
|
||
# SENTRY_DSN=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 12) Bootstrap do 1º dono (usado só uma vez pelo install.sh)
|
||
# -----------------------------------------------------------------------------
|
||
OWNER_EMAIL=voce@seudominio.com.br
|
||
# senha forte do primeiro admin
|
||
OWNER_PASSWORD=
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# AGENT ENGINE (fusão Vendaval) — worker 24/7 do agente SDR (serviço `worker`)
|
||
# -----------------------------------------------------------------------------
|
||
# O worker usa o SUPABASE_DB_URL acima (recomendado: role dedicada agent_worker
|
||
# — ver docs/deploy-selfhost/README.md §2). Defaults seguros; descomente p/ mudar.
|
||
# dono único do despacho: engine (worker) | native (sem worker)
|
||
AGENT_DISPATCH_CONSUMER=engine
|
||
# Teto de gasto de IA — a alavanca de emergência da INSTALAÇÃO. Quem escolhe o
|
||
# teto é cada organização, na tela Uso de IA › Orçamento; esta chave só AFROUXA.
|
||
# on (default) respeita a escolha de cada organização
|
||
# avisar rebaixa "parar a IA" para só avisar
|
||
# off desliga a proteção nesta instalação inteira (também: false, 0, nao)
|
||
# A IA parou e você precisa dela de volta agora? ponha off aqui e reinicie o app
|
||
# e o worker — sem psql, sem SQL. Valor não reconhecido cai em `on`: uma alavanca
|
||
# de emergência não pode derrubar o app por um typo. Deixe comentado para o padrão.
|
||
#AI_BUDGET_ENFORCEMENT=on
|
||
#WATCHDOG_INTERVAL_MS=60000 # reconciliador de sessão + redrive de mensagens presas
|
||
#FLYWHEEL_INTERVAL_MS=21600000 # auto-melhoria (propostas; aplicar é sempre manual). 0=off
|
||
#FLYWHEEL_BATCH_LIMIT=10
|
||
|
||
# ── Marca da instalação (white-label) ───────────────────────────────────────
|
||
# Quem instala o CRM para clientes (agência, revendedor) troca a marca aqui, sem
|
||
# editar código — patch em código se perde no próximo update. Vazio = padrão.
|
||
# APP_LOGO_URL aceita qualquer URL pública de imagem; sem ela, o nome aparece
|
||
# como texto. Ver lib/branding.ts.
|
||
APP_NAME=
|
||
APP_LOGO_URL=
|
||
# Cor da marca: cerquilha + 6 dígitos hex (`#7a5cd6`). O install.sh pergunta e
|
||
# valida essa forma; formas que o app aceita mas o e-mail não (`#abc`, `7a5cd6`)
|
||
# pintariam a tela e deixariam o verde do produto no e-mail de acesso.
|
||
# Depois da primeira leitura o banco manda (platform_branding, tela /admin/marca)
|
||
# — esta chave é a SEMENTE e o piso de rollback, e a única fonte dos e-mails de
|
||
# acesso, que o hostgator-setup-kit/marca-emails.sh empurra para o GoTrue.
|
||
APP_ACCENT_HEX=
|
||
# Endereço de suporte que o CLIENTE FINAL vê (conta suspensa, cobrança). Ponha o
|
||
# SEU — quem suspende a conta de um cliente é quem hospeda, não o produto.
|
||
# Vazio = a tela não mostra endereço nenhum.
|
||
SUPPORT_EMAIL=
|
||
|
||
# ─── Agenda · Google Calendar (BYO) — OPCIONAL ────────────────────────────────
|
||
# Sem as duas, a Agenda funciona inteira: some o botão "Conectar Google" e a tela
|
||
# explica que falta configuração — sem culpar quem está usando.
|
||
#
|
||
# Onde obter: console.cloud.google.com > APIs e Serviços > Credenciais >
|
||
# ID do cliente OAuth, tipo "Aplicativo da Web".
|
||
#
|
||
# ⚠️ E REGISTRE O ENDEREÇO DE RETORNO, que é onde todo mundo tropeça. No console,
|
||
# em "URIs de redirecionamento autorizados", ponha a URL do seu app MAIS o
|
||
# caminho do callback — o Google compara BYTE A BYTE:
|
||
#
|
||
# https://SEU-DOMINIO/api/v1/agenda/google/callback
|
||
#
|
||
# Registrar só `https://SEU-DOMINIO` é o palpite natural e o Google recusa com
|
||
# `redirect_uri_mismatch`, um erro que aponta para ele e não para a divergência.
|
||
# A própria tela da Agenda mostra o endereço exato, pronto para copiar, enquanto
|
||
# a instalação estiver sem as chaves.
|
||
GOOGLE_CALENDAR_CLIENT_ID=
|
||
GOOGLE_CALENDAR_CLIENT_SECRET=
|
||
# Web Push — bandeja do SO com a aba fechada. Sem o par, alertas só com o site aberto.
|
||
# Gerar: npx web-push generate-vapid-keys
|
||
VAPID_PUBLIC_KEY=
|
||
VAPID_PRIVATE_KEY=
|