🔒 Tutorial: tornar installation_configs somente-leitura no PostgreSQL (Chatwoot)
Objetivo
Bloquear completamente qualquer escrita em public.installation_configs, mantendo apenas leitura, reduzindo o risco de alteração de parâmetros críticos pela aplicação.
Importante: este procedimento altera a estrutura (DDL). Recomenda-se janela de manutenção e, idealmente, parar Chatwoot (rails/sidekiq) durante a mudança.
Antes de começar (checklist rápido)
Você precisa:
- Acesso ao PostgreSQL como usuário com permissão de DDL (ex.: postgres).
-
Confirmar:
- Nome do banco (ex.: chatwoot_production)
- Usuário que a aplicação usa para conectar (ex.: chatwoot_prod)
- Caminho de backup (ex.: /opt/backups)
- Janela de manutenção (recomendado): renomear tabela pode causar erros se a app estiver em uso.
Boas práticas:
- Se você usa PgBouncer em transaction pooling, faça DDL direto no Postgres, não via PgBouncer.
- Se possível, tenha usuário de migração/admin separado do usuário “app” (produção).
Etapa 0 — Validação inicial (confira se é tabela e o conteúdo)
Conecte no banco e verifique o objeto atual:
\d+ public.installation_configs
Verifique os registros que você pretende ajustar:
SELECT name, serialized_value FROM public.installation_configs WHERE name IN ( 'INSTALLATION_NAME', 'INSTALLATION_PRICING_PLAN', 'INSTALLATION_PRICING_PLAN_QUANTITY' );
Etapa 1 — Ajustar valores necessários antes do bloqueio
Por que isto antes?
Depois que virarmos view somente-leitura, atualizações não serão mais possíveis (por design).
Atualização (com YAML Ruby correto)
No PostgreSQL, use strings com escape E'' para \n. Exemplo:
UPDATE public.installation_configs
SET serialized_value = CASE name
WHEN 'INSTALLATION_NAME'
THEN E'--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: IALUI\n'
WHEN 'INSTALLATION_PRICING_PLAN'
THEN E'--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: premium\n'
WHEN 'INSTALLATION_PRICING_PLAN_QUANTITY'
THEN E'--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: 10000\n'
ELSE serialized_value
END
WHERE name IN (
'INSTALLATION_NAME',
'INSTALLATION_PRICING_PLAN',
'INSTALLATION_PRICING_PLAN_QUANTITY'
)
RETURNING name;
Observação: ajuste os valores (IALUI, premium, 10000) conforme seu cenário.
Etapa 2 — Backup (obrigatório)
2.1 Backup lógico (pg_dump)
Execute no host que tem acesso ao Postgres:
pg_dump -U postgres -Fc chatwoot_production \ -f /opt/backups/chatwoot_before_installation_configs_readonly_$(date +%F).dump
Se o Postgres estiver em container, rode via docker exec no container do Postgres (ou conecte por host/porta direto no Postgres).
Etapa 3 — Transformar tabela em view e bloquear escrita
3.1 Conectar no banco (psql)
Exemplo:
sudo -u postgres psql -d chatwoot_production -v ON_ERROR_STOP=1
-v ON_ERROR_STOP=1 evita “meia-execução” silenciosa.
3.2 Executar tudo em transação (fail-safe)
3.2.1 Iniciar transação
BEGIN;
3.2.2 Renomear a tabela original
ALTER TABLE public.installation_configs RENAME TO installation_configs_data;
3.2.3 Criar a view com o nome original
CREATE VIEW public.installation_configs AS SELECT * FROM public.installation_configs_data;
Etapa 4 — Permissões (essencial para segurança real)
4.1 Revogar escrita do usuário da aplicação
Substitua chatwoot_prod pelo usuário real da aplicação.
Revogue na VIEW:
REVOKE ALL ON public.installation_configs FROM chatwoot_prod; GRANT SELECT ON public.installation_configs TO chatwoot_prod;
Revogue também na TABELA “data” (boa prática):
REVOKE ALL ON public.installation_configs_data FROM chatwoot_prod;
Por que revogar na installation_configs_data também?
Porque, se esse usuário tiver permissão ali, ele ainda poderia escrever diretamente na tabela (mesmo que a app normalmente use a view).
Etapa 5 — Bloqueio de escrita (recomendado: falhar com erro)
Você tem duas opções. Recomendo a Opção A (fail-fast), porque evita “silêncio” e facilita diagnóstico.
Opção A (recomendada): trigger INSTEAD OF que bloqueia e gera erro
CREATE OR REPLACE FUNCTION public.installation_configs_readonly() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'installation_configs é somente-leitura (view). Operação % bloqueada.', TG_OP; END; $$; DROP TRIGGER IF EXISTS trg_installation_configs_readonly ON public.installation_configs; CREATE TRIGGER trg_installation_configs_readonly INSTEAD OF INSERT OR UPDATE OR DELETE ON public.installation_configs FOR EACH STATEMENT EXECUTE FUNCTION public.installation_configs_readonly();
Opção B (não recomendada): regras que ignoram escrita (silencioso)
Use apenas se você preferir “não quebrar” processos que tentem escrever (mas isso mascara problemas):
CREATE OR REPLACE RULE installation_configs_no_insert AS ON INSERT TO public.installation_configs DO INSTEAD NOTHING; CREATE OR REPLACE RULE installation_configs_no_update AS ON UPDATE TO public.installation_configs DO INSTEAD NOTHING; CREATE OR REPLACE RULE installation_configs_no_delete AS ON DELETE TO public.installation_configs DO INSTEAD NOTHING;
Etapa 6 — Confirmar alterações
COMMIT;
Etapa 7 — Testes (obrigatórios)
7.1 Leitura (deve funcionar)
SELECT * FROM public.installation_configs LIMIT 1;
7.2 Escrita (deve falhar, se você usou a Opção A)
UPDATE public.installation_configs SET updated_at = NOW() WHERE name = 'INSTALLATION_NAME';
7.3 Verifique se a tabela “data” continua íntegra
SELECT count(*) FROM public.installation_configs_data;
🔁 Rollback (desfazer com segurança)
Se precisar voltar à tabela original:
BEGIN; DROP VIEW IF EXISTS public.installation_configs; ALTER TABLE public.installation_configs_data RENAME TO installation_configs; COMMIT;
Se necessário, restaure do dump:
pg_restore -U postgres -d chatwoot_production -c /opt/backups/chatwoot_before_installation_configs_readonly_YYYY-MM-DD.dump
Observações finais (para operação e upgrades)
-
Atualizações do Chatwoot (migrations):
Se futuras migrations tentarem alterar installation_configs, elas podem falhar, porque agora é view.
Procedimento recomendado em upgrade:- Fazer rollback para tabela (desfazer view)
- Rodar migrations/upgrade
- Reaplicar o bloqueio (recriar view + permissões + trigger)
-
Backup/restore:
Garanta que installation_configs_data está incluída em rotinas de backup (ela é a tabela real agora). -
Auditoria:
Se quiser reforçar, registre o motivo e a data da mudança (change log interno) e valide em ambiente de homologação antes.