mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 09:34:46 +08:00
A stack local entregava o comando da sessão de WhatsApp a qualquer máquina
da rede da VM, por duas portas independentes.
**A chave.** `WAHA_API_KEY=deskcomm-local-key` nascia literal em
`ubuntu-local-installer.sh` e em `scripts/local-env.sh` — isto é, publicada
neste repositório. É o mesmo argumento que já tornou a senha do dono
aleatória nesta fatia ("qualquer máquina da mesma rede alcançaria o CRM com
uma credencial que está no GitHub"), só que esta credencial comanda o
WhatsApp. Agora ela vem de `openssl rand -hex 24`.
Sem knob de propósito: nenhum terceiro precisa conhecê-la (o app manda o
plaintext, o WAHA compara o sha512 derivado dela), e um
`${WAHA_API_KEY:-...}` herdaria em silêncio a chave da nuvem de quem tiver
a variável exportada no shell — o `.env.local` deste repo é lido por 93
scripts. Em `local-env.sh` o valor é gerado UMA vez numa variável, porque o
heredoc é sem aspas e dois `$(openssl rand)` dariam chave e hash
divergentes, que rendem 401.
**A porta.** `docker-compose.local.yml` publicava `"3030:3000"` sem
endereço de bind, com `WAHA_DASHBOARD_ENABLED: "true"`. O dashboard do WAHA
tem autenticação própria, com o padrão do WAHA — a chave da API não o
protege —, então a chave aleatória sozinha não fecharia essa porta. Agora é
`"127.0.0.1:3030:3000"`.
Isto alinha o compose local com o que a casa já pratica e com o que o
próprio texto do PR já prometia: `docker-compose.prod.yml` desliga o
dashboard ("nunca expor o dashboard num template público", linha 156) e não
publica porta nenhuma para o WAHA (`networks: [internal]`); e o bloco novo
de `docs/SETUP.md` já dizia "o WAHA em `http://127.0.0.1:3030`". Nada
alcança essa porta de fora do compose — nenhum dos quatro scripts a
referencia —, e de outra máquina o túnel `ssh -L 3030:localhost:3030`
resolve, como `docs/vendaval-vps-deploy-comandos.md:64` já faz.
O app (3000) continua em 0.0.0.0: é o produto, e é o que a VM serve.
**A guarda.** Cinco verificações novas em `tests/shell/stack-local.test.sh`
(12 → 17). Três delas EXECUTAM as linhas em vez de as lerem, com um stub de
`sha512sum` (que é do GNU e não existe no macOS, onde o teste roda antes de
chegar ao Ubuntu do CI), porque o que se prova é a procedência do hash.
Sabotagem A — os três consertos desfeitos: 4 vermelhos, exatamente os
previstos, e a checagem de controle ("a sonda enxerga o bloco do WAHA")
seguiu verde, então os vermelhos são medição e não sonda morta.
Sabotagem B — chave e hash vindos de dois `openssl rand` diferentes: os
dois greps ingênuos PASSAM (`deskcomm-local-key` → 0 ocorrências; o grep de
aleatoriedade casa) e só a checagem executável fica vermelha. É o caso que
justifica ela existir.
Restaurado: `md5` dos três arquivos igual ao de antes da sabotagem, e o
conserto conferido por presença — `grep -c` devolve 1 em cada um dos quatro
pontos, e `deskcomm-local-key` devolve 0 no repositório inteiro.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RccZbdYe8URAiWCpyvZimQ
155 lines
5.1 KiB
Bash
Executable File
155 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
ENV_FILE="${LOCAL_ENV_FILE:-.env.local}"
|
|
|
|
json_value() {
|
|
local key="$1"
|
|
node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => {
|
|
const value = JSON.parse(s)[process.argv[1]];
|
|
process.stdout.write(value == null ? "" : String(value));
|
|
})' "$key"
|
|
}
|
|
|
|
env_value() {
|
|
local key="$1"
|
|
[[ -f "$ENV_FILE" ]] || return 0
|
|
awk -F= -v key="$key" '$1 == key { sub(/^[^=]*=/, ""); print; exit }' "$ENV_FILE"
|
|
}
|
|
|
|
ensure_vapid_keys() {
|
|
local public_key private_key keys_json
|
|
public_key="$(env_value VAPID_PUBLIC_KEY)"
|
|
private_key="$(env_value VAPID_PRIVATE_KEY)"
|
|
if [[ -n "$public_key" && -n "$private_key" ]]; then
|
|
return
|
|
fi
|
|
keys_json="$(node -e 'const webpush = require("web-push"); process.stdout.write(JSON.stringify(webpush.generateVAPIDKeys()))')"
|
|
public_key="$(printf '%s' "$keys_json" | node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => process.stdout.write(JSON.parse(s).publicKey))')"
|
|
private_key="$(printf '%s' "$keys_json" | node -e 'let s=""; process.stdin.on("data", c => s += c).on("end", () => process.stdout.write(JSON.parse(s).privateKey))')"
|
|
printf '\n# Geradas automaticamente por local-env.sh; mantenha estas chaves para preservar as inscrições.\nVAPID_PUBLIC_KEY=%s\nVAPID_PRIVATE_KEY=%s\n' "$public_key" "$private_key" >> "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
printf 'Chaves VAPID geradas automaticamente em %s\n' "$ENV_FILE"
|
|
}
|
|
|
|
has_local_env() {
|
|
[[ "$(env_value DESKCOMM_ENV_MODE)" == "local" ]] || return 1
|
|
local key
|
|
for key in NEXT_PUBLIC_SUPABASE_URL NEXT_PUBLIC_SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY \
|
|
SUPABASE_DB_URL INTERNAL_SECRET CPF_ENCRYPTION_KEY WAHA_BYO_ENCRYPTION_KEY AI_CRED_AES_KEY \
|
|
WAHA_API_BASE_URL WAHA_API_KEY WAHA_WEBHOOK_BASE_URL UPSTASH_REDIS_REST_URL \
|
|
UPSTASH_REDIS_REST_TOKEN WAHA_API_KEY_SHA512 SRH_TOKEN; do
|
|
[[ -n "$(env_value "$key")" ]] || return 1
|
|
done
|
|
}
|
|
|
|
local_ip() {
|
|
hostname -I 2>/dev/null | awk '{print $1}' || true
|
|
}
|
|
|
|
choose_port() {
|
|
local port="${APP_PORT:-3000}"
|
|
if command -v ss >/dev/null 2>&1; then
|
|
while ss -ltn | awk '{print $4}' | grep -Eq "(^|:)${port}$"; do
|
|
port=$((port + 1))
|
|
done
|
|
fi
|
|
printf '%s' "$port"
|
|
}
|
|
|
|
generate() {
|
|
local status vm_ip app_port api_url app_url db_url anon_key service_role_key
|
|
status="$(./scripts/local-supabase.sh status)"
|
|
anon_key="$(printf '%s' "$status" | json_value ANON_KEY)"
|
|
service_role_key="$(printf '%s' "$status" | json_value SERVICE_ROLE_KEY)"
|
|
db_url="$(printf '%s' "$status" | json_value DB_URL)"
|
|
[[ -n "$anon_key" && -n "$service_role_key" && -n "$db_url" ]] || {
|
|
printf 'Erro: o Supabase local não retornou as credenciais necessárias.\n' >&2
|
|
exit 1
|
|
}
|
|
|
|
vm_ip="$(local_ip)"
|
|
[[ -n "$vm_ip" ]] || vm_ip="127.0.0.1"
|
|
db_url="${db_url/127.0.0.1/$vm_ip}"
|
|
app_port="$(choose_port)"
|
|
api_url="http://${vm_ip}:54321"
|
|
app_url="http://${vm_ip}:${app_port}"
|
|
|
|
if [[ -f "$ENV_FILE" ]] && [[ "$(env_value DESKCOMM_ENV_MODE)" != "local" ]]; then
|
|
local backup
|
|
backup="${ENV_FILE}.cloud-backup"
|
|
if [[ ! -e "$backup" ]]; then
|
|
cp --preserve=mode "$ENV_FILE" "$backup"
|
|
chmod 600 "$backup"
|
|
printf 'Backup do ambiente anterior: %s\n' "$backup"
|
|
fi
|
|
fi
|
|
|
|
# A chave do WAHA nasce ALEATÓRIA e é derivada UMA vez: o heredoc abaixo é
|
|
# sem aspas, então dois `$(openssl rand)` produziriam valores diferentes na
|
|
# chave e no hash, e o app tomaria 401. Sem knob: nenhum terceiro precisa
|
|
# conhecê-la, e um `${WAHA_API_KEY:-...}` herdaria a chave da nuvem de quem
|
|
# tiver a variável exportada no shell.
|
|
local waha_key
|
|
waha_key="$(openssl rand -hex 24)"
|
|
|
|
local tmp
|
|
tmp="$(mktemp "${ENV_FILE}.XXXXXX")"
|
|
umask 077
|
|
cat > "$tmp" <<EOF
|
|
DESKCOMM_ENV_MODE=local
|
|
NODE_ENV=production
|
|
NEXT_PUBLIC_SUPABASE_URL=$api_url
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=$anon_key
|
|
SUPABASE_SERVICE_ROLE_KEY=$service_role_key
|
|
SUPABASE_DB_URL=$db_url
|
|
SUPABASE_DB_ADMIN_URL=$db_url
|
|
|
|
NEXT_PUBLIC_APP_URL=$app_url
|
|
NEXT_PUBLIC_ADMIN_URL=$app_url
|
|
WAHA_WEBHOOK_BASE_URL=$app_url
|
|
|
|
INTERNAL_SECRET=$(openssl rand -hex 32)
|
|
INTERNAL_CRON_SECRET=$(openssl rand -hex 32)
|
|
CPF_ENCRYPTION_KEY=$(openssl rand -base64 32)
|
|
WAHA_BYO_ENCRYPTION_KEY=$(openssl rand -base64 32)
|
|
AI_CRED_AES_KEY=$(openssl rand -base64 32)
|
|
NUVEMSHOP_OAUTH_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|
|
|
WAHA_API_BASE_URL=http://waha:3000
|
|
WAHA_API_KEY=$waha_key
|
|
WAHA_API_KEY_SHA512=$(printf '%s' "$waha_key" | sha512sum | awk '{print $1}')
|
|
WAHA_HMAC_SECRET=$(openssl rand -hex 32)
|
|
WAHA_WEBHOOK_REQUIRE_SIGNATURE=false
|
|
WHATSAPP_RESTART_ALL_SESSIONS=True
|
|
|
|
UPSTASH_REDIS_REST_URL=http://srh:80
|
|
UPSTASH_REDIS_REST_TOKEN=deskcomm-local-redis
|
|
SRH_TOKEN=deskcomm-local-redis
|
|
LGPD_SIGNING_KEY=$(openssl rand -hex 32)
|
|
APP_PORT=$app_port
|
|
SENTRY_DSN=
|
|
EOF
|
|
mv "$tmp" "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
printf 'Ambiente local gerado em %s\n' "$ENV_FILE"
|
|
printf 'Aplicação: %s\n' "$app_url"
|
|
}
|
|
|
|
case "${1:-ensure}" in
|
|
ensure)
|
|
if ! has_local_env; then generate; fi
|
|
ensure_vapid_keys
|
|
;;
|
|
generate|force)
|
|
generate
|
|
;;
|
|
*)
|
|
printf 'Uso: %s {ensure|generate}\n' "$0"
|
|
exit 2
|
|
;;
|
|
esac
|