Files
melgarafaelandClaude Opus 5 9da23dea56 fix(dev): a chave do WAHA nasce aleatória, e o painel dele não atende a rede
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
2026-09-20 23:51:05 -03:00

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