Installation
Resolução de Problemas de Instalação
Problemas comuns de deployment do QuoteNode com ligações, CORS, SMTP, DNS, health checks Docker e segredos perdidos.
Resolução de Problemas de Instalação
A maioria dos problemas de instalação vem de mismatch de URL, SMTP em falta, portos bloqueados ou segredos perdidos. Começa pelo URL visível no teu browser e trabalha para dentro.
Links públicos abrem no meu computador mas não para clientes
Verifica qual URL foi copiado ou enviado.
http://localhost/...funciona apenas na mesma máquina.http://192.168.x.x/...funciona apenas na LAN ou VPN. VPN pode ser uma ponte válida para equipas internas, mas não é uma estratégia de links públicos para clientes.https://crm.example.com/...funciona publicamente apenas quando DNS, HTTPS, proxy e firewall estão corretos.
Para links públicos de clientes, faz deploy com um domínio público usando servidor público Ubuntu ou Coolify.
O browser mostra erros de CORS
O CORS_ALLOWED_ORIGINS deve corresponder exatamente à origem do browser. Estas são origens diferentes:
https://crm.example.com
https://www.crm.example.com
http://crm.example.com
https://crm.example.com:8443
Corrige a variável, faz redeploy e tenta novamente numa sessão de browser fresca.
O email não é entregue
Verifica isto primeiro:
SMTP_HOSTeSMTP_PORTestão corretos.SMTP_AUTH=truequando o teu provider requer autenticação.SMTP_STARTTLS=truepara o porto 587.SMTP_USERNAMEeSMTP_PASSWORDsão válidos.- O provider permite login SMTP/app-password.
NOTIFICATIONS_EMAIL_ENABLED=truequando emails de notificação devem ser enviados.
A partilha manual de links públicos e o download de PDF ainda funcionam sem SMTP.
Os links de preferências de notificação estão errados
Define:
NOTIFICATIONS_PUBLIC_PREFERENCES_URL=https://<frontend-domain>/notifications/preferences
Não os deixes a apontar para localhost num deployment público.
Os contentores Docker não estão saudáveis
Executa:
docker compose ps
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f gotenberg
Causas comuns:
- O PostgreSQL ainda está a arrancar.
- O
DB_PASSWORDdifere entre o PostgreSQL e o backend. - O
DB_ENCRYPTION_KEYestá em falta ou malformado. - O Gotenberg não está saudável, logo a geração de PDF não pode arrancar.
- Portos 80/443 do host já estão a ser usados por outro serviço.
Após corrigir a configuração, reinicia o serviço afetado ou a stack:
docker compose restart backend
docker compose up -d
O meu IP está bloqueado — não consigo iniciar sessão
Existem duas causas comuns: a lista de IPs permitidos bloqueou o acesso ou a proteção contra força bruta bloqueou o teu IP após demasiadas tentativas falhadas de início de sessão.
Bloqueio pela lista de IPs permitidos
O QuoteNode tem uma lista de IPs permitidos configurável em Definições > Segurança > Lista de IPs permitidos (ou através da variável SECURITY_IP_WHITELIST). Quando ativa, protege toda a superfície de administração — incluindo os ecrãs de início de sessão e 2FA — pelo que um endereço excluído recebe 403 IP_NOT_ALLOWED e nem sequer consegue iniciar sessão. (Os health checks e os links públicos de propostas para clientes permanecem acessíveis.)
Primeiro aguarda dois minutos se acabaste de a alterar na aplicação. Uma ativação ou restrição na aplicação está protegida contra bloqueios: tem de ser reconfirmada pela tua sessão dentro de uma janela curta (120 s por omissão) e, se o teu endereço atual não estiver abrangido, a lista reverte automaticamente para a anterior. Assim, um autobloqueio acidental a partir do ecrã de Segurança resolve-se sozinho — aguarda e tenta novamente. (Esta rede de segurança não se aplica à variável SECURITY_IP_WHITELIST, que é aplicada no arranque.)
Recuperação (substituição do operador): se continuas bloqueado — p. ex. uma SECURITY_IP_WHITELIST do operador, uma lista guardada inválida ou não podes esperar — adiciona esta variável ao ambiente do contentor backend (.env ou secção environment: no docker-compose) e reinicia:
SECURITY_IP_WHITELIST_FORCE_DISABLE=true
docker compose restart backend
Isto desativa completamente a lista de permitidos e permite iniciar sessão. Após entrar, corrige a lista em Definições > Segurança, depois remove SECURITY_IP_WHITELIST_FORCE_DISABLE e reinicia novamente.
Não deixes
SECURITY_IP_WHITELIST_FORCE_DISABLE=trueem produção. Desativa todo o controlo de acesso por IP.
Bloqueio pela proteção contra força bruta
O QuoteNode bloqueia endereços IP após demasiadas tentativas falhadas de início de sessão numa hora:
| Limiar | Falhas em 1 h | Efeito |
|---|---|---|
| Soft | 10 | Atraso progressivo (1 s por tentativa adicional, máx. 30 s) |
| Bloqueio temporário | 20 | Bloqueado durante 5 minutos |
| Bloqueio permanente | 50 | Bloqueado até desbloqueio manual |
Como desbloquear:
- Painel de administração — outro administrador abre Definições > Painel de Segurança > IPs bloqueados e clica em Desbloquear.
- Aguardar — os bloqueios temporários expiram após
SECURITY_TEMP_LOCK_MINUTES(5 minutos por defeito). Os bloqueios permanentes não expiram por si. - Elevar limiares temporariamente se estiveres bloqueado e nenhum outro admin puder ajudar. Adiciona ao
.env, reinicia, inicia sessão e depois remove:
SECURITY_MAX_FAILED_LOGINS_SOFT=9999
SECURITY_MAX_FAILED_LOGINS_TEMP_LOCK=9999
SECURITY_MAX_FAILED_LOGINS_HARD_LOCK=9999
Restaura os valores predefinidos após recuperar o acesso.
Bloqueios de IP em links públicos
O limitador de taxa dos links públicos bloqueia endereços IP que excedem os limites de tentativas. Estes bloqueios estão em memória e expiram após PUBLIC_RATE_LIMIT_IP_BLOCK_MINUTES (60 minutos por defeito) ou após um reinício do backend.
A atualização para com “cannot enforce offer-section ownership”
Depois de uma atualização o backend não arranca e docker compose logs backend mostra:
Migration V145 cannot enforce offer-section ownership: 3 offer_items row(s) point at a
section of another offer or tenant.
O que isto significa
Uma proposta é formada por secções (“Equipamento”, “Serviços”) e pelas linhas colocadas nelas. Esta versão torna essa ligação estrita: uma linha só pode estar numa secção da sua própria proposta. Algumas linhas antigas na sua base de dados violam essa regra, por isso a atualização para e pede-lhe uma decisão, em vez de reescrever as suas propostas por iniciativa própria.
Nada está danificado e a própria recusa não alterou nada. Esta migração foi revertida por completo: não há alterações aplicadas pela metade nem nada a reparar no histórico de migrações. Note, no entanto, que migrações anteriores da mesma atualização podem já ter sido aplicadas: a base de dados está imediatamente antes deste passo, não necessariamente na versão de partida. Isso significa também que os contentores anteriores podem funcionar com ela ou não, consoante o que essas migrações mudaram — não conte com reverter as imagens como saída.
Escolha o que deve acontecer
| Opção | O que faz | O que muda para si |
|---|---|---|
| Verificar o problema (predefinição) | Lista as propostas e linhas afetadas. | Nada. Apenas leitura. |
| Desassociar as atribuições erradas | Limpa a secção apenas dessas linhas. | As linhas continuam na sua proposta com o mesmo preço, quantidade e descrição. Perdem o cabeçalho de secção e passam a surgir como linhas sem agrupamento. |
| Escolher uma secção para cada linha | Pergunta linha a linha e apresenta apenas secções da própria proposta. | Mantém o agrupamento que pretende. As linhas ignoradas ficam por resolver e a atualização volta a parar. |
As propostas já enviadas a clientes continuam a mostrar exatamente o que o cliente viu — o conteúdo de uma proposta enviada é congelado no momento do envio, pelo que nem a ligação pública nem o PDF mudam.
Identifique primeiro a instalação certa (Coolify ou vários stacks num servidor)
Cada comando abaixo tem de atingir a sua instalação. Um servidor pode alojar várias, e mais do que uma pode ter um serviço chamado backend, por isso liste primeiro as candidatas — cada linha é uma instalação, com o nome do projeto e o ficheiro Compose a partir do qual foi iniciada:
docker inspect $(docker ps -aq --filter "label=com.docker.compose.service=backend" \
--filter "label=com.docker.compose.oneoff=False") \
--format '{{index .Config.Labels "com.docker.compose.project"}} | {{index .Config.Labels "com.docker.compose.project.config_files"}}'
Se obtiver mais do que uma linha, escolha a sua; não adivinhe. Depois execute cada comando nomeando explicitamente esse projeto e esse ficheiro, em vez de um docker compose simples, para não atuar sobre a instalação errada:
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections
Substitua <project> e <compose-file> pelos valores da sua linha e mantenha-os em todos os comandos seguintes — é isso que faz cada um atuar na sua instalação e não noutra. Se a linha mostrar vários ficheiros separados por vírgulas, repita -f para cada um, por essa ordem. Se a lista estiver vazia, os contentores não foram iniciados pelo Docker Compose — use o diretório indicado pelo seu fornecedor ou pelo guia de instalação, ou pergunte a quem configurou o servidor. No Coolify, faça-o num terminal do servidor, não do contentor: o terminal do contentor precisa de um backend em funcionamento, e o seu não está. (As mensagens da própria ferramenta mostram um docker compose … curto sem essas opções, para se lerem bem — acrescente o seu -p e -f ao copiá-las.)
Faça primeiro uma cópia de segurança
O PostgreSQL continua a funcionar mesmo quando o backend não arranca, por isso pode fazer a cópia sem a aplicação. O comando lê o nome da base de dados e o utilizador do próprio contentor, pelo que funciona independentemente dos nomes que escolheu, e nenhuma palavra-passe aparece no ecrã:
docker compose -p <project> -f <compose-file> exec -T postgres sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom -f /tmp/before-repair.dump \
&& pg_restore --list /tmp/before-repair.dump | head -3'
docker compose -p <project> -f <compose-file> cp postgres:/tmp/before-repair.dump ./before-repair.dump
ls -lh before-repair.dump
A linha pg_restore --list imprime as primeiras entradas do arquivo: se as vir e ls -lh mostrar um ficheiro não vazio, a cópia é legível. Guarde before-repair.dump junto do seu docker-compose.yml — está no disco do servidor, não dentro de um contentor, pelo que sobrevive a tudo o que se segue.
Verificar o problema
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections
Este comando apenas lê. Mostra as propostas afetadas, o que está errado em cada linha e as opções:
Found 3 offer item(s) in 2 offer(s) assigned to a section that does not belong to that offer.
Offer OF-2026-014 [DRAFT]
item: Mounting frame
section: section "Installation" — section belongs to another offer
Pré-visualizar e depois aplicar
Sem --apply=true cada comando é uma pré-visualização: mostra exatamente o que faria e termina sem ter alterado nada, em qualquer terminal.
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink
Acrescente --apply=true --acknowledge-backup=true para o executar. É a única forma que escreve:
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink --apply=true --acknowledge-backup=true
Para decidir cada linha por si, use --plan=choose. A ferramenta faz uma pergunta por linha e lista as secções dessa proposta, pelo que nunca escreve um identificador da base de dados. Sem --apply=true continua apenas a mostrar-lhe o plano:
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=choose --apply=true --acknowledge-backup=true
Concluir a atualização
Quando a ferramenta indicar que nenhuma linha aponta para uma secção errada:
docker compose -p <project> -f <compose-file> up -d
A migração bloqueada não deixou nada pendente, por isso é simplesmente executada no arranque seguinte. Não precisa de qualquer comando flyway repair ou baseline — e não deve usá-los, porque esconderiam o problema de dados em vez de o resolver.
Verifique como habitualmente: abra /health e confirme a nova versão em Admin > Licença.
Se indicar que os dados mudaram desde a sua pré-visualização
A ferramenta aplica o plano que aprovou, e não o que encontrar no momento de escrever. Se durante a confirmação surgiu uma atribuição errada, alguma se moveu ou uma secção que escolheu foi eliminada, recusa toda a reparação e não altera nada. Execute a verificação novamente para ver o estado atual e volte a escolher.
Se indicar que a reparação foi guardada — ou que não conseguiu confirmar
A reparação corre numa única transação, por isso ou é guardada por inteiro ou não é guardada de todo. Dois desfechos exigem atenção. Se falhar um passo posterior — escrever o registo ou a releitura que confirma o resultado — a ferramenta mantém a palavra SAVED na mensagem: a reparação resultou, por isso não restaure a sua cópia de segurança. Se, em vez disso, surgir UNCONFIRMED, a base de dados nunca respondeu e ninguém consegue dizer como terminou; não restaure a cópia nem repita a reparação. Em ambos os casos volte a executar a verificação só de leitura: se indicar que não há nada a reparar, terminou e pode arrancar o stack; se continuar a listar as mesmas linhas, nada foi guardado e pode repetir a sua escolha.
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections
Uma coisa a ler com atenção: se usou Escolher uma secção para cada linha e ignorou algumas, essas linhas ignoradas continuam a ser listadas pela verificação. É o esperado e não significa que a reparação falhou. Compare a lista com as linhas que escolheu efetivamente corrigir: se só restarem as que ignorou, a reparação resultou e o que falta é a decisão que adiou — volte a executar a ferramenta para elas quando quiser. Se ainda aparecerem linhas que escolheu corrigir, nada foi guardado.
O que foi alterado e o que fazer se continuar a falhar
Cada execução que altera algo escreve os identificadores da linha, proposta e secção afetadas em /app/data/backups/ do volume do backend. Não contém dados de clientes e pode ser anexado a um pedido de suporte. Se o ficheiro não puder ser escrito, a ferramenta imprime o mesmo registo no ecrã — copie-o antes de fechar o terminal:
docker compose -p <project> -f <compose-file> run --rm --entrypoint sh backend \
-c 'ls -1 /app/data/backups/offer-section-repair-*.json'
Se a atualização continuar a parar:
- execute a verificação novamente — as linhas ignoradas continuam listadas;
- se a mensagem indicar outra migração, esta ferramenta não se aplica. Repara apenas atribuições de secção de linhas de proposta;
- se a mensagem não mudar e a verificação não indicar nada a reparar, comunique o problema com o registo do backend e o registo acima.
O certificado HTTPS não é emitido
Para deployments públicos:
- O DNS deve apontar para o servidor de deployment.
- Os portos 80 e 443 devem ser acessíveis a partir da internet.
- Apenas um proxy deve terminar HTTPS para o mesmo domínio.
- Na Coolify, atribui o domínio ao serviço
frontend.
Perdi o .env
Se perderes o .env, para e avalia antes de rodar valores.
- O
DB_PASSWORDpode normalmente ser redefinido com acesso administrativo ao PostgreSQL. - O
TIMING_TOKEN_SECRETe oPUBLIC_LINK_PASSWORD_SESSION_SECRETpodem ser regenerados, mas sessões/tokens existentes podem expirar. - O
DB_ENCRYPTION_KEYé crítico. Sem a chave original, valores cifrados não podem ser desencriptados. - Se a cifragem de PII estiver ativada, campos de cliente/contacto cifrados dependem do
DB_ENCRYPTION_KEY.
Restaura o .env a partir de backup sempre que possível.
Recuperação se .env for perdido mas existe cópia de segurança da base de dados
- Cria um novo
.envcom novos segredos. - Restaura a base de dados a partir da tua cópia de segurança (
pg_dump/pg_restore). - Todos os dados de negócio (clientes, propostas, produtos, utilizadores) serão totalmente acessíveis.
- Apenas alguns campos são perdidos: códigos MFA (utilizadores re-inscrevem-se), password SMTP (reinserir nas definições), chave API FX (reinserir). Se
ENCRYPT_PII=trueestava ativo, os dados de clientes cifrados também são perdidos.
Se as tuas cópias de segurança estão cifradas com age (o padrão), precisas do ficheiro de identidade age do teu kit de recuperação para as desencriptar primeiro. Consulta o guia Backup e Recuperação.
Esqueci a password do administrador
Usa um caminho de recuperação não destrutivo:
- Se o SMTP estiver configurado, usa a página de início de sessão: Esqueceste a tua password?
- Se outro administrador consegue iniciar sessão, abre Utilizadores no painel de administração e redefine a password.
- Se nenhum administrador consegue iniciar sessão e o fluxo de reposição por email não está disponível, restaura o acesso a partir de uma cópia de segurança em bom estado ou usa o teu procedimento de manutenção interno.
docker compose down -vnão é um procedimento de reposição de password. Elimina todos os dados — utilizadores, propostas, clientes, definições e cópias de segurança. Usa-o apenas para ambientes de teste descartáveis.
A verificação da licença falha
Para deployments reais, mantém:
LICENSE_SKIP_SIGNATURE_CHECK=false
Se uma licença válida ainda falhar, verifica que o deployment tem a versão esperada da app e um relógio de sistema sincronizado com NTP. Confirma que a licença corresponde à edição/versão deployada, depois contacta o suporte ou substitui o ficheiro de licença através do caminho documentado de operador. Não definas o bypass de assinatura como true como workaround de produção.