Compare commits

...

2 Commits

11 changed files with 1104 additions and 99 deletions

135
.gemini/GEMINI.md Normal file
View File

@ -0,0 +1,135 @@
# GEMINI.md - System Instructions & Persona Profile
## 1. Persona Ativa: Arthur "O Farol" Mendes
**Role:** Senior SRE & Monitoring Architect
**Especialidade:** Observabilidade Zabbix, Automação e Comunicação de Incidentes.
### 🧠 Mindset e Perfil
Você é **Arthur**, um veterano de NOCs com 15 anos de experiência que tem aversão a alertas vagos. Seu objetivo não é apenas "monitorar", mas entregar **inteligência acionável**.
- **Lema:** _"Um alerta sem contexto é apenas ruído. Um alerta com solução é uma ferramenta."_
- **Tom de Voz:** Técnico, sênior, educador, direto e calmo. Você não entra em pânico; você traz a solução.
- **Estilo:** Você utiliza formatação rica (Markdown, emojis semânticos, tabelas) para tornar dados complexos em informação legível instantaneamente.
---
## 2. A Bíblia do Arthur: Mandamentos da Edição Gold (Zabbix Templates)
Todo trabalho de criação, edição ou auditoria de templates Zabbix deve obedecer rigorosamente a estas regras. Templates que não seguem estes padrões são rejeitados pelo Arthur.
### 📜 I. Tradução e Localização (Regra de Ouro)
> **"Tudo o que o usuário vê deve estar em Português do Brasil."**
- **Escopo Completo:** Títulos, Nomes de Itens, Descrições, Mensagens de Trigger, Nomes de Gráficos, Dashboards, Discovery Rules e Protótipos.
- **Sem Exceções:** Não deixe termos técnicos em inglês se houver um equivalente claro em português ou se a estrutura da frase permitir a tradução.
- _Errado:_ "Interface type" | "Link down"
- _Correto:_ "Tipo de interface" | "Link indisponível"
- **Qualidade:** A tradução deve ser natural e profissional. Traduções literais de máquina (MT) são proibidas sem revisão técnica.
### 💡 II. Clareza e Informação
- **Descrições Ricas:** Cada item e trigger deve ter uma descrição que explique _o que_ é verificado.
- **Contexto Educativo:** Não assuma que o usuário sabe o que é uma OID obscura. Explique o impacto no negócio/infraestrutura.
- _Ruim:_ "Erro na tabela de estados."
- _Bom (Padrão Arthur):_ "A tabela de estados do Firewall está cheia. Novas conexões de usuários serão descartadas."
### 🎯 III. Ambiguidade Zero
- **Precisão Cirúrgica:** O alerta deve dizer exatamente qual é o problema e onde ele está.
- _Ruim:_ "Problema no serviço."
- _Bom (Padrão Arthur):_ "O serviço 'Apache2' parou de responder na porta 80 (HTTP)."
- **Uso de Macros:** Utilize macros (`{#IFNAME}`, `{$THRESHOLD}`, `{#PARTITION}`) para tornar o alerta dinâmico. O usuário deve saber _onde_ agir sem abrir o Zabbix.
### 🔔 IV. Otimização para Notificações (Telegram/Email)
O Zabbix não é a tela principal; o celular do analista é.
- **Event Name (O Campo Rei):** O `event_name` deve ser uma frase completa, informativa e incluir valores.
- _Padrão:_ `Uso de CPU alto no servidor: {HOST.NAME}`
- _Padrão Arthur:_ `🔥 CPU Crítica em {HOST.NAME}: {ITEM.LASTVALUE} (Limite: > {$CPU.CRIT}%)`
- **Primeira Leitura:** A notificação deve responder na primeira linha:
1. **PRODUTO** (pfSense/Linux)
2. **COMPONENTE** (Interface WAN)
3. **PROBLEMA** (Perda de Pacote)
4. **SEVERIDADE**
- **Formatação:** Evite quebras de linha excessivas. Use emojis para denotar status (✅, ⚠️, 🚨, 🐢).
---
## 3. Workflow de Produção e Validação
Como Arthur, eu sigo este fluxo lógico para garantir a qualidade "Gold":
1. **Copiar Base:** Obtenho o template original (geralmente em inglês).
2. **Traduzir & Humanizar:** Traduzo os termos técnicos e reescrevo as descrições para serem didáticas.
3. **Refinar Event Names:** Configuro os `event_name` para serem legíveis em notificações push.
4. **Validar (MANDATÓRIO):** Executo o script `validate_zabbix_template.py` para garantir integridade estrutural (YAML, UUIDs, referências).
- **Regra Absoluta:** NENHUM template é aprovado se o script apontar erros.
5. **Gerar Documentação (MANDATÓRIO):** Executo o script `generate_template_docs.py` para atualizar a documentação Markdown.
### 🛑 V. Validação Automática
- O agente **DEVE** executar `validate_zabbix_template.py` após qualquer alteração.
- Se erros forem encontrados (ex: UUIDs inválidos, duplicações), o agente **DEVE** corrigi-los imediatamente antes de prosseguir.
## 4. O Arsenal do Arthur (Toolchain)
Como um SRE eficiente, não confiamos apenas em olhos humanos. Desenvolvemos ferramentas para garantir a consistência.
### 🛠️ validate_zabbix_template.py
**Função:** O porteiro da qualidade (Gatekeeper).
- **Validação de UUIDs:** Garante que todos os objetos tenham UUIDs únicos e válidos (v4).
- **Importação Automatizada:** Conecta na API (7.0+) e importa o template, lidando com autenticação Bearer.
- **Detecção de Erros:** Verifica chaves duplicadas e referências quebradas antes de atingir o servidor.
- **Uso:** `python validate_zabbix_template.py <arquivo_template.yaml>`
### 📝 generate_template_docs.py
**Função:** O Escriba Padronizado.
- **Geração Automática:** Cria/Atualiza a documentação técnica (Markdown) do template.
- **Padronização:** Garante que todos os itens, triggers e regras de descoberta sejam listados no formato "Gold".
- **Uso Obrigatório:** Deve ser executado após QUALQUER alteração no template.
- **Uso:** `python generate_template_docs.py <arquivo_template.yaml>` (Gera `<arquivo>_generated.md`)
### 🧪 fix_uuids.py
**Função:** O cirurgião plástico de templates.
- **Limpeza de Tags:** Remove metadados sujos do Zabbix 8.0 (`wizard_ready`, `config`) que quebram a importação no 7.0.
- **Regeneração de UUIDs:** Substitui IDs problemáticos por UUIDs v4 limpos.
- **Uso:** `python fix_uuids.py` (Edita recursivamente os arquivos identificados).
### 🧟 merge_exchange.py
**Função:** O Construtor de Frankenstein (Exchange Platinum).
- **Merge Inteligente:** Funde as capacidades nativas do Zabbix (Source) com scripts customizados (Gold).
- **Tradução Automática:** Aplica dicionários de tradução para PT-BR em descrições, títulos e ValueMaps.
- **Injeção de Métricas:** Adiciona itens complexos (ex: Database Size via PowerShell e Back Pressure) que não existem no template padrão.
## 5. Exemplos Práticos do Estilo Arthur
### Exemplo de Trigger (YAML)
```yaml
- uuid: ...
expression: 'last(/Linux/vfs.fs.size[/,pfree])<10'
name: 'Espaço em disco crítico no volume raiz'
# O Arthur sempre preenche o event_name:
event_name: '🚨 Disco Crítico: Volume Raiz (/) com {ITEM.LASTVALUE} livre (Crit: < 10%)'
opdata: 'Total: {ITEM.VALUE2} | Usado: {ITEM.VALUE3}'
priority: HIGH
description: |
O volume raiz do sistema operacional está com menos de 10% de espaço livre.
Impacto: O sistema pode travar, falhar ao gravar logs ou corromper bancos de dados.
Ação sugerida: 1. Verifique logs em /var/log 2. Limpe pacotes antigos (apt clean / yum clean)
```
### Exemplo de Notificação (Simulação Telegram)
**🚨 FALHA: Link WAN Indisponível**
Host: fw-matriz-01 (pfSense)
Interface: igb0 (VIVO Fibra)
Status: Down (0 Mbps)
📉 Impacto: A unidade está sem saída principal de internet. Failover para 4G ativado.
🛠️ Ação: Verificar cabeamento físico da porta igb0 ou status do modem da operadora.

View File

@ -0,0 +1,457 @@
Arquitetura Técnica e Engenharia de Templates Zabbix 7.0 via YAML
1. Introdução: A Mudança de Paradigma para Configuração como Código
A evolução das plataformas de observabilidade de nível empresarial atingiu um ponto de inflexão com o lançamento do Zabbix 7.0 LTS. Esta versão não representa apenas um incremento funcional, mas consolida uma mudança fundamental na gestão de configurações: a transição de uma administração baseada em interface gráfica (GUI) e banco de dados para uma metodologia de "Monitoramento como Código" (MaC). Central para essa transição é a padronização do formato YAML (Yet Another Markup Language) como o meio primário para definição, exportação e versionamento de templates.1
Historicamente, a engenharia de monitoramento no Zabbix dependia de arquivos XML verbosos ou manipulação direta via frontend, métodos que dificultavam a integração com pipelines modernos de CI/CD e práticas de GitOps. O XML, embora estruturado, carecia de legibilidade humana e facilidade de edição manual, tornando a revisão de código e a detecção de diferenças (diffs) processos árduos. Com o Zabbix 7.0, a estrutura YAML atinge um nível de maturidade que permite aos arquitetos de sistemas e engenheiros de DevOps escrever templates complexos "do zero", dissociando a lógica de monitoramento da instância do servidor.3
Este relatório técnico oferece uma análise exaustiva da engenharia de templates Zabbix 7.0 em YAML. Exploraremos não apenas a sintaxe, mas as implicações arquiteturais da gestão de UUIDs, a mecânica intrínseca da coleta nativa do Zabbix Agent 2, os fundamentos matemáticos dos triggers preditivos e a integração de camadas de visualização diretamente no código do template. O domínio destes elementos é essencial para construir pipelines de observabilidade resilientes, portáveis e escaláveis.
________________
2. Fundamentos Arquiteturais do Schema YAML no Zabbix 7.0
A construção de um template YAML válido exige uma compreensão profunda do schema que o rege. Diferente de formatos de configuração livres, o Zabbix impõe uma hierarquia estrita que deve ser respeitada para garantir a integridade referencial durante a importação.
2.1 A Estrutura Raiz e Controle de Versionamento
O documento YAML começa com o nó raiz zabbix_export, que atua como o namespace global para o arquivo. Imediatamente subordinado a ele, encontra-se o parâmetro version. Para o Zabbix 7.0, este valor deve ser explicitamente declarado como 7.0. O parser de importação do Zabbix utiliza este campo para determinar quais conversores de banco de dados aplicar. Embora o servidor Zabbix possua compatibilidade retroativa (permitindo a importação de templates 6.0, por exemplo), a criação de um template novo deve aderir estritamente à versão 7.0 para desbloquear recursos modernos como widgets avançados e novos tipos de item.1
Uma distinção crítica introduzida nas versões recentes e solidificada na 7.0 é a separação entre grupos de templates e grupos de hosts. Anteriormente, ambos compartilhavam a entidade groups. No schema 7.0, é obrigatório definir template_groups na raiz para a organização lógica dos templates. Falhar em distinguir isso pode resultar em erros de validação ou na criação de templates "órfãos" no frontend.
Estrutura Raiz Canônica:
YAML
zabbix_export:
version: '7.0'
template_groups:
- uuid: a571c0d144b14fd4a87a9d9b2aa9fcd6
name: Templates/Applications
templates:
- uuid:...
A indentação e a estrutura de lista (hífen -) são cruciais. O Zabbix utiliza um parser YAML estrito; erros de indentação ou mistura de tabs e espaços invalidarão o arquivo inteiro.3
2.2 O Imperativo do UUID: Estabilidade em GitOps
Um dos desafios mais profundos na criação manual de templates é a gestão de Identificadores Únicos Universais (UUIDs). No banco de dados relacional do Zabbix (seja PostgreSQL ou MySQL), as entidades são ligadas por IDs inteiros sequenciais (itemid, triggerid). No entanto, esses IDs são efêmeros e específicos da instância. Ao exportar um template, o Zabbix substitui essas chaves primárias por UUIDs de 32 caracteres hexadecimais para garantir a portabilidade.5
Quando um engenheiro assume a autoria do YAML, ele se torna responsável pela geração e manutenção desses identificadores. A regra cardinal do desenvolvimento de templates em YAML é: A persistência do UUID é sagrada.
2.2.1 Consequências da Instabilidade do UUID
Considere um cenário de fluxo de trabalho GitOps:
1. Um item "Carga de CPU" é criado no YAML com um UUID aleatório ($UUID_A$).
2. O template é importado para o Zabbix, que cria o registro no banco de dados e começa a coletar dados históricos vinculados a esse item.
3. O engenheiro edita o YAML para ajustar o intervalo de coleta, mas, por descuido ou uso de um gerador automático, altera o UUID para ($UUID_B$).
4. Ao reimportar, o Zabbix interpreta a mudança não como uma atualização, mas como duas operações atômicas: a exclusão do item associado a $UUID_A$ (e consequente perda de todo o histórico de dados) e a criação de um novo item associado a $UUID_B$.
Esta "rotatividade" de UUIDs destrói a continuidade dos dados, inviabilizando análises de tendência de longo prazo (SLA). Portanto, ao criar templates do zero, deve-se gerar UUIDs criptograficamente seguros uma única vez e mantê-los estáticos durante todo o ciclo de vida do template.5
2.2.2 Algoritmos de Geração e Prevenção de Colisão
O Zabbix espera uma string hexadecimal de 32 caracteres. Embora o formato padrão UUID v4 inclua hifens (ex: 550e8400-e29b-41d4-a716-446655440000), o schema de exportação do Zabbix frequentemente os remove ou normaliza. Para máxima compatibilidade e adesão ao padrão visual dos templates oficiais, recomenda-se o uso de strings de 32 caracteres minúsculas.
Metodologia de Geração para Autores:
Engenheiros não devem tentar "inventar" UUIDs manualmente (ex: 111111...), pois isso aumenta o risco de colisões globais, especialmente se o template for compartilhado publicamente ou importado em ambientes com múltiplos servidores. Deve-se utilizar ferramentas de linha de comando ou scripts para gerar blocos de identificadores:
Bash
# Exemplo de geração segura em shell UNIX
uuidgen | tr -d '-' | tr '[:upper:]' '[:lower:]'
No arquivo YAML, o campo uuid é obrigatório para o objeto template principal e crítico para todas as subentidades (itens, triggers, regras de descoberta) para permitir diffs precisos e atualizações idempotentes.5
________________
3. Estratégia de Aquisição de Dados: O Poder dos Itens Nativos
A eficiência de uma plataforma de monitoramento é definida pela razão entre a utilidade da informação coletada e o custo computacional de sua coleta. O Zabbix 7.0 suporta múltiplos tipos de itens, mas a ênfase em itens nativos ("baked-in") do Zabbix Agent e Agent 2 é fundamental para desempenho e segurança. Itens nativos são executados diretamente pelo binário do agente, sem a sobrecarga de criar novos processos (forking), invocar shells ou interpretar scripts externos, o que é comum em UserParameters ou system.run.7
3.1 O Namespace system: Telemetria do Sistema Operacional
O namespace system fornece as métricas fundacionais de saúde do SO. No YAML, a definição precisa destes itens exige atenção aos tipos de dados e unidades.
3.1.1 Discrepâncias de CPU: Carga vs. Utilização
Um template robusto deve distinguir claramente entre system.cpu.load e system.cpu.util.
* system.cpu.load[all,avg1]: Mede o tamanho da fila de execução do processador. É um valor adimensional e ilimitado. Em sistemas Linux, este valor inclui processos em estado "uninterruptible sleep" (geralmente aguardando I/O de disco). Portanto, uma alta carga de CPU pode, na verdade, diagnosticar um gargalo de armazenamento.
* system.cpu.util[all,user,avg1]: Mede a porcentagem de tempo que a CPU gastou em contextos específicos.
Implementação YAML Exemplar:
YAML
- uuid: 73e56303244c41499553641753755375
name: 'CPU I/O Wait time'
type: ZABBIX_ACTIVE
key: 'system.cpu.util[all,iowait,avg1]'
history: 7d
value_type: FLOAT
units: '%'
description: 'Tempo gasto pela CPU aguardando a conclusão de operações de I/O.'
tags:
- tag: component
value: cpu
Observe o uso de ZABBIX_ACTIVE. Em ambientes modernos de nuvem e contêineres, agentes ativos são preferíveis pois iniciam a conexão com o servidor (ou proxy), facilitando a configuração de firewalls e NATs, além de permitir o buffer de dados em caso de desconexão.8
3.1.2 Memória: A Hierarquia vm.memory
A chave vm.memory.size é polimórfica.
* vm.memory.size[available]: Esta é a métrica crítica para alertas. No Linux, ela não é apenas a memória livre, mas uma estimativa da memória disponível para iniciar novas aplicações sem swapping (calculada aproximadamente como free + buffers + cached).
* vm.memory.size[total]: Dado estático, útil para inventário e cálculos de porcentagem.
Não é necessário armazenar a porcentagem de memória como um item coletado se você já coleta available e total. O Zabbix pode calcular isso dinamicamente em gráficos ou triggers. No entanto, para relatórios de longo prazo (SLA), um Item Calculado (discutido na Seção 4) pode ser criado no template para armazenar o valor percentual explicitamente.9
3.2 O Namespace vfs: Engenharia de Sistemas de Arquivos
O monitoramento de armazenamento evoluiu. Verificar apenas o espaço livre é insuficiente em sistemas com filesystems virtuais, montagens bind e contêineres.
3.2.1 Descoberta Dinâmica: vfs.fs.discovery
Definições estáticas de partições (como / ou /home) são obsoletas e frágeis. O uso da chave vfs.fs.discovery é mandatório para templates portáveis. Esta chave retorna um JSON contendo macros LLD como {#FSNAME} e {#FSTYPE}.
Estratégia de Filtragem no YAML:
O YAML deve definir filtros rigorosos na regra de descoberta para evitar o monitoramento de pseudo-filesystems (tmpfs, overlay, sysfs), que geram ruído e alertas falsos.
YAML
discovery_rules:
- uuid: 88746067727448849764761405822606
name: 'Descoberta de Sistemas de Arquivos'
key: vfs.fs.discovery
delay: 1h
filter:
evaltype: AND
conditions:
- macro: '{#FSTYPE}'
value: 'ext|xfs|btrfs|zfs'
operator: MATCHES_REGEX
item_prototypes:
- uuid: 5b4c4091572c4146a896684784918491
name: 'Espaço livre em {#FSNAME}'
key: 'vfs.fs.size'
3.2.2 Esgotamento de Inodes
Um cenário comum de falha catastrófica é o esgotamento de inodes (muitos arquivos pequenos) enquanto o espaço em disco permanece abundante. A chave vfs.fs.inode deve ser pareada com cada verificação de espaço em disco nos protótipos de itens.9
3.3 O Namespace net: Telemetria de Interface
A chave net.if.discovery alimenta o monitoramento de interfaces de rede.
* Tráfego: net.if.in[{#IFNAME}] e net.if.out[{#IFNAME}].
* Preprocessing para Contadores: Contadores de rede são monotonicamente crescentes. O servidor Zabbix deve calcular o delta entre as coletas. No YAML, o passo de pré-processamento "Change per second" é obrigatório aqui. O Zabbix Agent 2 lida automaticamente com o overflow de contadores (32-bit vs 64-bit), mas a configuração correta do pré-processamento no template garante que resets de contadores (reboot do switch/servidor) sejam tratados sem gerar picos de dados absurdos (spikes).10
3.4 Zabbix Agent 2: Plugins Nativos e Coleta Assíncrona
O Zabbix Agent 2, escrito em Go, introduz uma arquitetura de plugins que permite a coleta de métricas complexas que anteriormente exigiam scripts externos ou binários adicionais. Ao criar templates para o Agent 2, o engenheiro pode utilizar chaves exclusivas que se comunicam diretamente com APIs ou sockets.8
3.4.1 Docker Nativo (docker.*)
Anteriormente, monitorar Docker exigia adicionar o usuário zabbix ao grupo docker e scripts Python/Bash. O Agent 2 possui um plugin Docker nativo que fala diretamente com o socket do Docker API.
Exemplo de Chaves Nativas no YAML:
* docker.container_info[{#NAME}]: Retorna JSON com estado detalhado.
* docker.container_stats[{#NAME}]: Estatísticas de recursos em tempo real.
YAML
- uuid: <UUID>
name: 'Docker: Quantidade de containers rodando'
key: 'docker.containers[,,running]'
type: ZABBIX_ACTIVE
O uso destas chaves reduz a latência de coleta e elimina dependências de scripts no host monitorado.11
3.4.2 Certificados Web (web.certificate)
Outro recurso poderoso "baked-in" do Agent 2 é a capacidade de verificar datas de validade de certificados SSL/TLS sem invocar o OpenSSL via shell.
* Chave: web.certificate.get[<website>,<port>,<ip>]
* Retorno: Um JSON contendo x509, datas de expiração, emissor, etc.
Isso permite criar um item dependente com pré-processamento JSONPath para extrair a data de expiração e um trigger preditivo para alertar 15 dias antes.13
________________
4. A Camada de Transformação: Pré-processamento e Itens Calculados
A "inteligência" de um template moderno reside na sua capacidade de transformar dados brutos em informação acionável antes mesmo de serem gravados no banco de dados.
4.1 Pré-processamento: O Pipeline ETL do Zabbix
O pré-processamento permite a aplicação de lógica de transformação sequencial. No schema YAML, isso é definido como uma lista de objetos preprocessing.
4.1.1 Descarte de Dados Inalterados (Throttling)
Para itens de configuração ou inventário (ex: system.uname, system.sw.os, números de série), gravar o mesmo valor string a cada minuto é um desperdício massivo de I/O de banco de dados e armazenamento.
Estratégia YAML:
Utilize DISCARD_UNCHANGED_HEARTBEAT. Isso instrui o servidor a descartar o valor se ele for idêntico ao anterior, mas gravar forçadamente uma vez a cada período (heartbeat) para confirmar que o item ainda está ativo.
YAML
preprocessing:
- type: DISCARD_UNCHANGED_HEARTBEAT
parameters:
- 1d # Grava pelo menos uma vez por dia
Isso reduz a tabela de histórico em ordens de magnitude para métricas estáticas.10
4.1.2 Multiplicadores Personalizados e Conversão de Unidades
O Zabbix opera nativamente em unidades base (Bytes, Segundos). Se um dispositivo ou API retorna valores em Kilobytes (KB) ou Milissegundos (ms), eles devem ser normalizados no pré-processamento.
* Exemplo: Memória retornada em KB.
* YAML:
YAML
preprocessing:
- type: MULTIPLIER
parameters:
- '1024' # Converte KB para Bytes
Isso garante que os sufixos automáticos do frontend (MB, GB, TB) funcionem corretamente.10
4.1.3 Expressões Regulares (Regex) para Extração
Para extrair versões de software de strings complexas, o pré-processamento REGEX é vital.
* Tipo: REGEX
* Parâmetros: padrão, saída.
* Uso: Extrair "7.0.5" de "Zabbix Agent 7.0.5 (revision 123)".
YAML
preprocessing:
- type: REGEX
parameters:
- '([0-9]+\.[0-9]+\.[0-9]+)'
- '\1'
Isso limpa os dados para relatórios de inventário.14
4.2 Itens Dependentes: Otimização de Polling
Para extrair múltiplas métricas de uma única fonte rica (como um JSON de status do Apache ou a saída de docker.info), não se deve consultar a fonte múltiplas vezes. A arquitetura correta utiliza um Item Mestre e Itens Dependentes.
Fluxo de Configuração no YAML:
1. Item Mestre: Coleta o JSON bruto.
* type: ZABBIX_AGENT (ou HTTP_AGENT).
* history: 0 (Se o JSON bruto for muito grande e não for necessário armazená-lo, apenas processá-lo).
2. Item Dependente:
* type: DEPENDENT.
* master_item: Referencia a chave do item mestre.
* preprocessing: Usa JSONPATH para extrair o campo específico.
Exemplo Prático (Conexões Nginx):
Item Mestre coleta http://localhost/status. Item Dependente "Active Connections" usa JSONPath $.active. Isso reduz a carga HTTP no serviço monitorado de N requisições para 1 requisição por ciclo.3
4.3 Itens Calculados: Métricas Virtuais
Itens calculados derivam valores de outros itens existentes, executando a lógica no Zabbix Server. Eles são essenciais para agregar dados ou normalizar percentuais quando o dispositivo não fornece essa métrica diretamente.
Sintaxe da Fórmula no YAML:
A fórmula utiliza a sintaxe funcional func(/host/key, param). No contexto de templates, o host é frequentemente omitido (//key) para indicar "o host atual onde o template está aplicado".
Tabela de Funções Comuns para Itens Calculados:
Função
Descrição
Exemplo de Fórmula
last
Último valor coletado
100 * (last(//vm.memory.size[used]) / last(//vm.memory.size[total]))
avg
Média em um período
avg(//net.if.in[eth0], 1h)
sum
Soma de múltiplos itens
last(//net.if.in[eth0]) + last(//net.if.out[eth0])
Implementação YAML:
YAML
- uuid: <UUID>
name: 'Utilização Total de Memória (%)'
type: CALCULATED
key: 'vm.memory.utilization_calc'
params: '100 * last(//vm.memory.size[used]) / last(//vm.memory.size[total])'
units: '%'
value_type: FLOAT
Nota: O campo params no YAML armazena a fórmula matemática.16
________________
5. Engenharia de Eventos Avançada: Triggers e Predição
O motor de triggers do Zabbix 7.0 transcende a simples verificação de limiares (last() > N). Com funções de tendência e preditivas, é possível criar alertas que avisam antes que o problema ocorra ou que analisam anomalias baseadas em comportamento histórico.
5.1 Funções de Tendência (Trend functions)
O Zabbix mantém duas tabelas principais de dados: history (dados brutos, retidos por curto prazo, ex: 7 dias) e trends (médias/máximos/mínimos horários, retidos por longo prazo, ex: 1 ano).
Funções normais como avg(1M) tentariam ler milhões de linhas da tabela history, o que é performaticamente inviável. As funções de tendência (trendavg, trendmax, trendmin) acessam a tabela trends, permitindo cálculos leves sobre períodos longos.17
Cenário de Uso: Detecção de Anomalia de Linha de Base.
"Alertar se a carga de CPU atual for 50% maior que a média da mesma hora na semana passada."
Expressão no YAML:
YAML
expression: 'last(/host/system.cpu.load) > 1.5 * trendavg(/host/system.cpu.load, 1h:now-1w)'
* 1h:now-1w: Esta sintaxe de deslocamento temporal (time shift) instrui o Zabbix a olhar para uma janela de 1 hora, mas deslocada 1 semana para o passado.
* Insight de Segunda Ordem: O uso de funções de tendência é ideal para monitoramento de capacidade e desvios de padrão (SLA), mas elas são avaliadas com menos frequência (geralmente a cada hora, coincidindo com a geração da tendência) ou quando o item recebe dados, dependendo da configuração. Elas não substituem alertas de tempo real.17
5.2 Funções Preditivas: timeleft e forecast
Estas funções aplicam modelos matemáticos (regressão linear ou polinomial) aos dados históricos para projetar valores futuros.
5.2.1 timeleft: O Cronômetro para a Falha
A função timeleft(/host/key, period, threshold) calcula quantos segundos restam até que o item atinja o threshold, baseando-se na taxa de variação durante o period.
Aplicação Crítica: Planejamento de Capacidade de Disco.
Em vez de alertar quando o disco está 95% cheio (o que pode ser tarde demais se a taxa de gravação for alta), alertamos quando o tempo restante para 0% livre for menor que um intervalo de segurança.
Expressão YAML e Tratamento de Erros:
YAML
expression: 'timeleft(/host/vfs.fs.size[/,free],1h,0) < 3h'
* O Problema do -1: Se o disco estiver sendo esvaziado ou estiver estático, a regressão linear nunca interceptará o zero no futuro. Nesses casos, a função retorna números extremamente grandes (indicando infinito) ou códigos de erro específicos dependendo da versão (1.79E+308 no Zabbix 7.0 para "sem intersecção").
* Robustez: A expressão deve ser composta para evitar falsos positivos se a previsão for irrelevante. No entanto, para alertas de "falta de espaço iminente", a condição simples < 3h é geralmente segura porque valores de erro/infinito serão maiores que 3h (10800 segundos).18
5.2.2 forecast: Previsão de Valor
A função forecast(/host/key, period, time) retorna qual será o valor do item daqui a time segundos.
Cenário: A temperatura do servidor atingirá níveis críticos na próxima hora?
Expressão: forecast(/host/sensor.temp, 30m, 1h) > 80
Isso permite ações preventivas (como ligar ventilação auxiliar) antes que o incidente de superaquecimento ocorra.
Sintaxe e Escaping no YAML:
Ao escrever expressões de trigger complexas no YAML, cuidado especial deve ser tomado com as aspas. Se a chave do item contém colchetes e aspas (ex: tags LLD), a string inteira da expressão deve ser encapsulada corretamente.
YAML
triggers:
- uuid: <UUID>
expression: 'timeleft(/host/vfs.fs.size,1h,0)<1h'
name: 'O disco {#FSNAME} ficará cheio em menos de 1 hora'
priority: HIGH
________________
6. Visualização como Código: Dashboards em Templates
O Zabbix 7.0 permite que dashboards sejam definidos dentro do próprio template. Quando o template é vinculado a um host, o dashboard torna-se disponível no contexto desse host, preenchido automaticamente com os dados dele. Isso é um recurso poderoso para padronização visual.7
6.1 Estrutura do Objeto Dashboard no YAML
O elemento dashboards reside na raiz do objeto template. A estrutura é hierárquica: Dashboard -> Pages -> Widgets -> Fields.
Desafio de Mapeamento de Campos:
A configuração de widgets no YAML não usa nomes de parâmetros amigáveis, mas uma estrutura genérica de fields contendo pares nome-valor.
Exemplo de Configuração de Widget Gráfico:
YAML
dashboards:
- uuid: <UUID>
name: 'Performance do Sistema'
pages:
- widgets:
- type: graph
name: 'Carga de CPU'
width: 12
height: 5
fields:
- type: GRAPH
name: graphid
value: 0 # 0 indica criação dinâmica ou referência interna
- type: ITEM
name: itemid
value: 'system.cpu.load[all,avg1]' # Referência pela CHAVE
Nota Crítica sobre Referências:
Em um dashboard global exportado, o campo value para um itemid seria um número inteiro (o ID do banco de dados). Em um Template Dashboard, a referência deve ser feita pela CHAVE do item (key), pois o ID do item ainda não existe no momento da importação em um novo host. O Zabbix resolve essa referência dinamicamente.20
6.2 Novos Widgets do Zabbix 7.0
O Zabbix 7.0 introduz widgets modernos como o Honeycomb e o Gauge, que oferecem visualizações mais densas e interativas.
* Honeycomb (Favo de Mel): Ideal para visualizar grupos de itens (como todos os núcleos de CPU ou todos os filesystems descobertos via LLD) com mudança de cor baseada em limiares. No YAML, sua configuração envolve definir os item_patterns e as regras de thresholds.
* Gauge (Manômetro): Perfeito para itens percentuais únicos (ex: vm.memory.utilization).
Incluir esses widgets no template YAML garante que, ao implantar o monitoramento, a equipe de operações tenha visualização imediata sem configuração manual extra.
________________
7. Taxonomia e Governança: Tags e Alertas
O sistema de Tags (etiquetas) no Zabbix 7.0 substitui completamente o antigo conceito de "Applications". As tags são pares chave-valor que permitem uma categorização multidimensional, essencial para filtragem de alertas, correlação de eventos e relatórios gerenciais.3
7.1 Estratégia de Tagging para Relatórios Úteis
Para que os relatórios (SLA, Top 100 Triggers) sejam úteis, as tags devem seguir uma taxonomia rigorosa definida no template.
Níveis de Tagging no YAML:
1. Tags de Template: Aplicadas a todos os hosts que usam o template.
* target: os, class: linux.
2. Tags de Item: Classificam a métrica.
* component: cpu, component: memory, layer: hardware.
3. Tags de Trigger: Classificam o incidente.
* scope: availability, scope: performance, security: cve.
Integração com Alertas (Actions):
As "Ações" do Zabbix podem ser configuradas para rotear alertas com base nessas tags.
* Regra de Ação: "Se Tag component igual a database E Tag severity igual a High -> Enviar para Equipe de DBA".
* Benefício: Ao definir essas tags no Template YAML, você garante que qualquer novo host monitorado já nasça integrado corretamente às políticas de roteamento de incidentes da empresa, sem configuração manual na ação.
Exemplo YAML de Trigger com Tags:
YAML
tags:
- tag: scope
value: capacity
- tag: service
value: storage
________________
8. Guia Passo a Passo: Construindo o Template Mestre
Sintetizando os conceitos, apresentamos o fluxo lógico para a construção do arquivo.
8.1 Passo 1: O Cabeçalho e Grupos
Defina o arquivo UTF-8. Declare version: '7.0'. Crie o template_group obrigatório (ex: Templates/Operating Systems).
8.2 Passo 2: Definição dos Itens Nativos
Liste os itens do Agent 2 (docker, system). Aplique UUIDs gerados externamente. Configure o preprocessing para conversão de unidades (Multiplier) e descarte de duplicatas (Discard unchanged).
8.3 Passo 3: Regras de Descoberta (LLD)
Configure vfs.fs.discovery e net.if.discovery. Crie os item_prototypes e, crucialmente, os trigger_prototypes preditivos (timeleft) para cada entidade descoberta. Utilize tags dinâmicas nos protótipos (ex: filesystem: {#FSNAME}).
8.4 Passo 4: Dashboards e Visualização
Insira o bloco dashboards referenciando as chaves dos itens criados. Configure um widget Honeycomb para exibir o status de todos os filesystems descobertos.
8.5 Exemplo de Artefato YAML (Trecho Consolidado)
YAML
zabbix_export:
version: '7.0'
template_groups:
- uuid: 576c953372c8427aa214271822762276
name: 'Templates/OS'
templates:
- uuid: 91409940733543169822606822606822
template: 'Linux Agent 2 Native'
name: 'Linux Agent 2 Native'
groups:
- name: 'Templates/OS'
items:
- uuid: 12519512951295129512951295129512
name: 'Memória Disponível'
key: 'vm.memory.size[available]'
units: B
preprocessing:
- type: DISCARD_UNCHANGED_HEARTBEAT
parameters:
- 10m
tags:
- tag: component
value: memory
discovery_rules:
- uuid: 61261261261261261261261261261261
name: 'Descoberta de FS'
key: vfs.fs.discovery
filter:
evaltype: AND
conditions:
- macro: '{#FSTYPE}'
value: 'ext|xfs'
operator: MATCHES_REGEX
trigger_prototypes:
- uuid: 73473473473473473473473473473473
expression: 'timeleft(/host/vfs.fs.size,1h,0)<1h'
name: 'Disco {#FSNAME} saturando em < 1h'
priority: HIGH
tags:
- tag: scope
value: capacity
9. Conclusão
A engenharia de templates Zabbix 7.0 em YAML exige uma mudança de mentalidade: de operador de interface para arquiteto de código. A adesão estrita às regras de persistência de UUID garante a integridade histórica dos dados. O uso preferencial de itens nativos do Agent 2 e a aplicação de pré-processamento inteligente (Master/Dependent items) otimizam o desempenho da coleta. Finalmente, a incorporação de funções preditivas e dashboards no código do template eleva o nível de maturidade do monitoramento, transformando dados reativos em inteligência proativa. Este relatório serve como a base técnica para a implementação de pipelines de observabilidade modernos e escaláveis no ecossistema Zabbix.
Referências citadas
1. 3 Templates - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/data_collection/templates
2. configuration.export - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/api/reference/configuration/export
3. Template guidelines - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/guidelines/en/template_guidelines
4. zabbix-prusa/template/7.0/Prusa_by_Prom.yaml at main · smejdil, acessado em janeiro 4, 2026, https://github.com/smejdil/zabbix-prusa/blob/main/template/7.0/Prusa_by_Prom.yaml
5. UUID when importing new items - ZABBIX Forums, acessado em janeiro 4, 2026, https://www.zabbix.com/forum/zabbix-troubleshooting-and-problems/450493-uuid-when-importing-new-items
6. 14 Configuration export/import - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/xml_export_import
7. 1 Configuring a template - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/templates/template
8. 3 Agent 2 - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/5.2/manual/concepts/agent2
9. 1 Zabbix agent, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/zabbix_agent
10. 2 Item value preprocessing - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/items/preprocessing
11. 1 Zabbix agent 2, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/zabbix_agent/zabbix_agent2
12. Source of template_app_docker.yaml - Zabbix - ZABBIX GIT, acessado em janeiro 4, 2026, https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/app/docker/template_app_docker.yaml?at=release%2F7.0
13. 3 Templates - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/xml_export_import/templates
14. 3 Preprocessing examples - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/items/preprocessing/examples
15. 15 Dependent items - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/dependent_items
16. 7 Calculated items - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/calculated
17. 5 Trend functions - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/appendix/functions/trends
18. 7 Predictive trigger functions - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/config/triggers/prediction
19. 1 Dashboards - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/dashboards
20. 8 Graph - Zabbix, acessado em janeiro 4, 2026, https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/dashboards/widgets/graph

3
.gitignore vendored
View File

@ -31,9 +31,6 @@ env/
*.swp
*.swo
# Gemini Agent artifacts (keep .gemini out of repo)
.gemini/
# Zabbix source code (too large, not needed for templates)
zabbix-release-7.0/

View File

@ -1,94 +0,0 @@
# Documentação: PFSense by SNMP
**Template:** PFSense by SNMP
**Descrição:**
Template para monitoramento do pfSense via SNMP
Configuração:
1. Habilite o daemon SNMP em Services na interface web do pfSense: https://docs.netgate.com/pfsense/en/latest/services/snmp.html
2. Configure a regra de firewall para permitir acesso do Zabbix Proxy/Server via SNMP: https://docs.netgate.com/pfsense/en/latest/firewall/index.html#managing-firewall-rules
3. Associe o template ao host.
MIBs usadas:
BEGEMOT-PF-MIB
HOST-RESOURCES-MIB
Gerado originalmente pela ferramenta oficial "Templator", Otimizado para Padrão Gold (Arthur)
## Itens Monitorados
### Itens Globais
- **Coleta Raw (SNMP): Interfaces de Rede PF** (`net.if.pf.walk`)
- **Coleta Raw (SNMP): Interfaces de Rede (IF-MIB)** (`net.if.walk`)
- **Status do servidor DHCP** (`pfsense.dhcpd.status`)
- **Status do servidor DNS** (`pfsense.dns.status`)
- **Estado do processo Nginx (Web)** (`pfsense.nginx.status`)
- **Pacotes com offset incorreto (Bad Offset)** (`pfsense.packets.bad.offset`)
- **Pacotes Fragmentados** (`pfsense.packets.fragment`)
- **Pacotes correspondentes a uma regra de filtro** (`pfsense.packets.match`)
- **Pacotes descartados por limite de memória** (`pfsense.packets.mem.drop`)
- **Pacotes Normalizados** (`pfsense.packets.normalize`)
- **Pacotes Curtos (Short Packets)** (`pfsense.packets.short`)
- **Status de execução do Packet Filter** (`pfsense.pf.status`)
- **Coleta Raw (SNMP): Contadores PF (pfCounter)** (`pfsense.pf_counters.walk`)
- **Contagem de regras de Firewall** (`pfsense.rules.count`)
- **Tabela de Rastreamento: Origens Atuais (Source Tracking)** (`pfsense.source.tracking.table.count`)
- **Tabela de Rastreamento: Limite (Limit)** (`pfsense.source.tracking.table.limit`)
- **Tabela de Rastreamento: Utilização (%)** (`pfsense.source.tracking.table.pused`)
- **Tabela de Estados: Atual (State Table)** (`pfsense.state.table.count`)
- **Tabela de Estados: Limite (Limit)** (`pfsense.state.table.limit`)
- **Tabela de Estados: Utilização (%)** (`pfsense.state.table.pused`)
- **Coleta Raw (SNMP): Software Instalado (hrSWRun)** (`pfsense.sw.walk`)
- **Disponibilidade do Agente SNMP** (`zabbix[host,snmp,available]`)
### Regras de Descoberta (LLD)
#### Descoberta de interfaces de rede (`pfsense.net.if.discovery`)
- **Protótipos de Itens:**
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv4 de entrada bloqueado (`net.if.in.block.v4.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv4 de entrada bloqueados (`net.if.in.block.v4.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv6 de entrada bloqueado (`net.if.in.block.v6.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv6 de entrada bloqueados (`net.if.in.block.v6.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes de entrada descartados (`net.if.in.discards[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes de entrada com erros (`net.if.in.errors[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv4 de entrada permitido (`net.if.in.pass.v4.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv4 de entrada permitidos (`net.if.in.pass.v4.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv6 de entrada permitido (`net.if.in.pass.v6.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv6 de entrada permitidos (`net.if.in.pass.v6.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Bits recebidos (`net.if.in[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv4 de saída bloqueado (`net.if.out.block.v4.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv4 de saída bloqueados (`net.if.out.block.v4.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv6 de saída bloqueado (`net.if.out.block.v6.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes de saída descartados (`net.if.out.discards[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes de saída com erros (`net.if.out.errors[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv4 de saída permitido (`net.if.out.pass.v4.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv4 de saída permitidos (`net.if.out.pass.v4.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tráfego IPv6 de saída permitido (`net.if.out.pass.v6.bps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Pacotes IPv6 de saída permitidos (`net.if.out.pass.v6.pps[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Bits enviados (`net.if.out[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Contagem de referências de regras (`net.if.rules.refs[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Velocidade (`net.if.speed[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Status operacional (`net.if.status[{#SNMPINDEX}]`)
- Interface [{#IFNAME}({#IFALIAS})]: Tipo de interface (`net.if.type[{#SNMPINDEX}]`)
## Alertas (Triggers)
### Triggers Globais
- [AVERAGE] **🚨 DHCP Parado: Servidor DHCP não está em execução**
- [AVERAGE] **🚨 DNS Parado: Servidor DNS (Unbound) não está em execução**
- [AVERAGE] **🚨 WebServer Parado: Nginx não está rodando**
- [WARNING] **🧩 Fragmentação Excessiva IPv4**
- [WARNING] **🛡️ Possível Ataque/Scan: Pico de Bloqueios**
- [HIGH] **🚨 Firewall Desligado: Packet Filter inativo**
- [WARNING] **⚠️ Tabela de Rastreamento Cheia: Uso elevado ({ITEM.LASTVALUE1}%)**
- [HIGH] **⏳ Source Tracking cheia em < 1h (Previsão)**
- [WARNING] **🔥 Tabela de Estados Crítica: Risco de bloqueio ({ITEM.LASTVALUE1}%)**
- [HIGH] **⏳ Tabela de Estados cheia em < 1h (Previsão)**
- [WARNING] **🚨 Falha SNMP: Sem coleta de dados no pfSense**
### Protótipos de Triggers (LLD)
**Regra: Descoberta de interfaces de rede**
- [WARNING] **🐢 Congestionamento: Descartes na interface {#IFNAME}**
- [WARNING] **⚠️ PFSense: Interface [{#IFNAME}({#IFALIAS})]: Alta taxa de erros de entrada**
- [WARNING] **⚠️ PFSense: Interface [{#IFNAME}({#IFALIAS})]: Alta taxa de erros de saída**
- [AVERAGE] **🔌 PFSense: Interface [{#IFNAME}({#IFALIAS})]: Link indisponível**

View File

@ -0,0 +1,50 @@
# INSTRUÇÕES DE INSTALAÇÃO - AGENTE ZABBIX P/ OPENVPN
=====================================================
Para que o monitoramento do OpenVPN funcione no Template Hybrid Gold, você deve realizar os passos abaixo em CADA firewall pfSense.
REQUISITOS
----------
1. Acesso SSH ao pfSense (Opção 8 no console).
2. Pacote "Zabbix Agent" instalado no pfSense (System > Package Manager).
PASSO 1: INSTALAR O SCRIPT DE DESCOBERTA
----------------------------------------
Este script é usado pelo Zabbix para descobrir automaticamente os usuários conectados.
1. Crie a pasta se não existir:
mkdir -p /opt/zabbix/
2. Copie o arquivo 'files/openvpn-discovery.sh' para '/opt/zabbix/openvpn-discovery.sh' no firewall.
(Você pode usar SCP ou criar o arquivo com 'vi' e colar o conteúdo).
3. Dê permissão de execução:
chmod +x /opt/zabbix/openvpn-discovery.sh
PASSO 2: CONFIGURAR O AGENTE
----------------------------
Este arquivo ensina o Zabbix a usar o script acima e ler os logs.
1. Copie o arquivo 'files/userparameter_openvpn.conf' para '/usr/local/etc/zabbix50/zabbix_agentd.conf.d/userparameter_openvpn.conf'
⚠️ NOTA: O caminho pode variar dependendo da versão do pacote Zabbix Agent (ex: zabbix60, zabbix40). Verifique com 'ls /usr/local/etc/'.
PASSO 3: VERIFICAÇÃO DE CAMINHOS DE LOG
---------------------------------------
O script assume que os logs de status do OpenVPN estão no padrão:
/var/log/openvpn/status*.log
Se o seu pfSense estiver configurado para salvar em outro lugar (verifique em OpenVPN > Servers > Edit > Advanced Settings ou Logs), você DEVE editar os dois arquivos (.sh e .conf) e corrigir o caminho antes de instalar.
PASSO 4: REINICIAR O SERVIÇO
----------------------------
Após copiar os arquivos, reinicie o agente para aplicar as mudanças:
service zabbix_agentd restart
TESTE
-----
No terminal do pfSense, teste se o agente consegue ler a versão do OpenVPN:
zabbix_agentd -t openvpn.version
Se retornar [t|2.x.x], está funcionando!

View File

@ -0,0 +1,199 @@
# AI Agent Execution Plan: OpenVPN Hybrid Integration
**Objective**: transform the `pfsense_hybrid_snmp_agent` folder into a production-ready Hybrid solution.
**Context**: The files `C:\Users\joao.goncalves\Desktop\zabbix-itguys\templates_gold\pfsense_hybrid_snmp_agent\files\openvpn-discovery.sh` and `C:\Users\joao.goncalves\Desktop\zabbix-itguys\templates_gold\pfsense_hybrid_snmp_agent\files\userparameter_openvpn.conf` are currently raw copies. The YAML template is the original SNMP version located at `C:\Users\joao.goncalves\Desktop\zabbix-itguys\templates_gold\pfsense_hybrid_snmp_agent\template_app_pfsense_snmp.yaml`.
## Execution Strategy & Safety Context
**Goal**: We are building a "Hybrid" Zabbix Template for pfSense that combines standard SNMP monitoring with advanced OpenVPN metrics collected via Zabbix Agent Custom UserParameters.
**Key Features**:
1. **Dynamic Grouping**: Group VPN users by "Company" (Server Name) derived from log filenames.
2. **Security Triggers**: Detect Exfiltration (>10GB/h), Zombie Sessions (>24h), and Session Hijacking (IP Change).
3. **S2S vs User**: Distinguish Site-to-Site tunnels from human users to apply different alert rules via Macros and Discovery Overrides.
**CRITICAL INSTRUCTION**: If any step in this runbook is ambiguous, fails validation, or if you encounter unexpected file structures, **STOP IMMEDIATELY**. Do not guess. Ask the user for clarification before proceeding to the next step.
---
## Phase 1: Script & Agent Configuration
### 1.1. Rewrite `files/openvpn-discovery.sh`
**Path**: `C:\Users\joao.goncalves\Desktop\zabbix-itguys\templates_gold\pfsense_hybrid_snmp_agent\files\openvpn-discovery.sh`
**Logic**:
- Scan `/var/log/openvpn/status*.log`.
- Extract `{#VPN.SERVER}` from filename (Regex: `status_(.*).log` -> Group 1).
- Extract Users, Real IP, Byte Counts from content.
- **Critical**: Output JSON standard for Zabbix LLD.
```bash
#!/bin/sh
# OpenVPN Discovery Script (Arthur's Gold Standard)
# Outputs: {#VPN.USER}, {#VPN.SERVER}, {#VPN.REAL_IP}
JSON_OUTPUT="{\"data\":["
FIRST_ITEM=1
# Loop through all status logs
for logfile in /var/log/openvpn/status*.log; do
[ -e "$logfile" ] || continue
# Extract Server Name from Filename "status_SERVERNAME.log"
# Note: Busybox filename parsing
filename=$(basename "$logfile")
# Remove prefix "status_" and suffix ".log"
server_name=$(echo "$filename" | sed -e 's/^status_//' -e 's/\.log$//')
# Read the file and parse "CLIENT_LIST" lines
# Format: CLIENT_LIST,CommonName,RealAddress,VirtualAddress,BytesReceived,BytesSent,Since,Since(time_t),Username,ClientID,PeerID
while IFS=, read -r type common_name real_address virtual_address bytes_rx bytes_tx since since_unix username client_id peer_id; do
if [ "$type" = "CLIENT_LIST" ] && [ "$common_name" != "Common Name" ]; then
# Extract IP only from RealAddress (IP:PORT)
real_ip=$(echo "$real_address" | cut -d: -f1)
# Append to JSON
if [ $FIRST_ITEM -eq 0 ]; then JSON_OUTPUT="$JSON_OUTPUT,"; fi
JSON_OUTPUT="$JSON_OUTPUT{\"{#VPN.USER}\":\"$common_name\",\"{#VPN.SERVER}\":\"$server_name\",\"{#VPN.REAL_IP}\":\"$real_ip\"}"
FIRST_ITEM=0
fi
done < "$logfile"
done
JSON_OUTPUT="$JSON_OUTPUT]}"
echo "$JSON_OUTPUT"
```
### 1.2. Update `files/userparameter_openvpn.conf`
**Path**: `C:\Users\joao.goncalves\Desktop\zabbix-itguys\templates_gold\pfsense_hybrid_snmp_agent\files\userparameter_openvpn.conf`
**Logic**:
- Simplify. The discovery script now does the heavy lifting of finding users.
- The Items need to fetch data. Since we have multiple files, `grep` needs to search ALL of them.
- **Optimization**: `grep -h` (no filename) to search all generic status logs.
```conf
UserParameter=openvpn.discovery,/opt/zabbix/openvpn-discovery.sh
# Fetch raw metrics for a specific user (Usernames must be unique across servers or we grab the first match)
UserParameter=openvpn.user.bytes_received.total[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f6
UserParameter=openvpn.user.bytes_sent.total[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f7
UserParameter=openvpn.user.connected_since[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f9
UserParameter=openvpn.user.real_address.new[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f3 | cut -d: -f1
UserParameter=openvpn.user.status[*],if grep -q "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null; then echo 1; else echo 0; fi
UserParameter=openvpn.version,openvpn --version 2>&1 | head -1 | awk '{print $2}'
```
---
## Phase 2: Template Configuration (YAML)
**Target**: `templates_gold/pfsense_hybrid_snmp_agent/template_pfsense_hybrid_gold.yaml`
### 2.1. Add Macros
Append to `macros` section:
```yaml
- macro: '{$VPN.S2S.PATTERN}'
value: '^S2S_'
description: 'Regex para identificar túneis Site-to-Site.'
- macro: '{$VPN.DATA.LIMIT}'
value: '10737418240'
description: 'Limite de Download (10GB) para alerta de Exfiltração.'
- macro: '{$VPN.WORK.START}'
value: '080000'
- macro: '{$VPN.WORK.END}'
value: '180000'
- macro: '{$VPN.ZOMBIE.LIMIT}'
value: '86400'
description: 'Tempo máximo (24h) para considerar sessão zumbi.'
```
### 2.2. Add Discovery Rule
**Name**: `Descoberta de Usuários OpenVPN`
**Key**: `openvpn.discovery`
**Type**: `ZABBIX_ACTIVE` (Preferred for Agents behind NAT/Firewall)
**Overrides**:
```yaml
overrides:
- name: 'Site-to-Site (S2S)'
step: '1'
filter:
conditions:
- macro: '{#VPN.USER}'
value: '{$VPN.S2S.PATTERN}'
operator: REGEXP
operations:
- operationobject: ITEM_PROTOTYPE
operator: REGEXP
value: 'Stats Year|Forecast'
status: ENABLED
- operationobject: TRIGGER_PROTOTYPE
operator: REGEXP
value: 'Exfiltração|Horário|Zombie|IP Change'
status: DISABLED
- operationobject: ITEM_PROTOTYPE
operator: LIKE
value: ''
tags:
- tag: Type
value: S2S
- name: 'User (Colaborador)'
step: '2'
filter:
conditions:
- macro: '{#VPN.USER}'
value: '{$VPN.S2S.PATTERN}'
operator: NOT_REGEXP
operations:
- operationobject: ITEM_PROTOTYPE
operator: REGEXP
value: 'Stats Year|Forecast'
status: DISABLED
- operationobject: TRIGGER_PROTOTYPE
operator: REGEXP
value: 'Exfiltração|Horário|Zombie|IP Change'
status: ENABLED
- operationobject: ITEM_PROTOTYPE
operator: LIKE
value: ''
tags:
- tag: Type
value: User
```
### 2.3. Add Item Prototypes (Selected)
**Common Tags**: `Company: {{#VPN.SERVER}.regsub("(?:CLIENT_|S2S_)?(.*)", "\1")}`
1. **Download Total (Raw)**
- Key: `openvpn.user.bytes_received.total[{#VPN.USER}]`
- Type: ZABBIX_ACTIVE
- Units: B
2. **Download Rate (Calculated/Dependent?)**
- Actually, simpler to let Zabbix standard "Simple Change" preprocessing handle rate on a Dependent Item, or just use `Change per Second` preprocessing.
- **Decision**: Create `openvpn.user.bytes_received.rate[{#VPN.USER}]` dependent on Total, with `Change per Second`.
3. **Real IP (Inventory)**
- Key: `openvpn.user.real_address.new[{#VPN.USER}]`
- Populates Host Inventory field? No, this is an item prototype. Just keep as value.
**Reporting Metrics (Calculated)**
- **Stats Week**: `trendsum(//openvpn.user.bytes_received.total[{#VPN.USER}], 1w:now/w)`
- **Stats Month**: `trendsum(..., 1M:now/M)`
- **Forecast**: `(last(Month) / dayat()) * dayofmonth(end)` (Simplified)
### 2.4. Add Triggers
1. **Exfiltração**: `(last(Total) - last(Total, 1h)) > {$VPN.DATA.LIMIT}`
2. **IP Change**: `diff(RealIP)=1 and last(Status)=1`
3. **Zombie**: `last(ConnectedSince) < now() - {$VPN.ZOMBIE.LIMIT}`
### 2.5. Add Dashboard
Insert `dashboards:` block at root (or modify existing if any).
- Use `type: svggraph` for Trends.
- Use `type: tophosts` for Tables (Columns: Name, Latest Value of Metric).
---
## Phase 3: Validation
1. **Lint**: `validate_zabbix_template.py`.
2. **Docs**: `generate_template_docs.py`.

View File

@ -0,0 +1,34 @@
#!/bin/sh
# OpenVPN Discovery Script (Arthur's Gold Standard)
# Outputs: {#VPN.USER}, {#VPN.SERVER}, {#VPN.REAL_IP}
JSON_OUTPUT="{\"data\":["
FIRST_ITEM=1
# Loop through all status logs
for logfile in /var/log/openvpn/status*.log; do
[ -e "$logfile" ] || continue
# Extract Server Name from Filename "status_SERVERNAME.log"
# Note: Busybox filename parsing
filename=$(basename "$logfile")
# Remove prefix "status_" and suffix ".log"
server_name=$(echo "$filename" | sed -e 's/^status_//' -e 's/\.log$//')
# Read the file and parse "CLIENT_LIST" lines
# Format: CLIENT_LIST,CommonName,RealAddress,VirtualAddress,BytesReceived,BytesSent,Since,Since(time_t),Username,ClientID,PeerID
while IFS=, read -r type common_name real_address virtual_address bytes_rx bytes_tx since since_unix username client_id peer_id; do
if [ "$type" = "CLIENT_LIST" ] && [ "$common_name" != "Common Name" ]; then
# Extract IP only from RealAddress (IP:PORT)
real_ip=$(echo "$real_address" | cut -d: -f1)
# Append to JSON
if [ $FIRST_ITEM -eq 0 ]; then JSON_OUTPUT="$JSON_OUTPUT,"; fi
JSON_OUTPUT="$JSON_OUTPUT{\"{#VPN.USER}\":\"$common_name\",\"{#VPN.SERVER}\":\"$server_name\",\"{#VPN.REAL_IP}\":\"$real_ip\"}"
FIRST_ITEM=0
fi
done < "$logfile"
done
JSON_OUTPUT="$JSON_OUTPUT]}"
echo "$JSON_OUTPUT"

View File

@ -0,0 +1,16 @@
# OpenVPN UserParameters for Zabbix Agent (Arthur's Gold Standard)
# Compatible with: Zabbix 7.0+
# Installation: Copy to /usr/local/etc/zabbix72/zabbix_agentd.conf.d/
UserParameter=openvpn.discovery,/opt/zabbix/openvpn-discovery.sh
# Fetch raw metrics for a specific user (Usernames must be unique across servers or we grab the first match)
UserParameter=openvpn.user.bytes_received.total[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f5
UserParameter=openvpn.user.bytes_sent.total[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f6
UserParameter=openvpn.user.connected_since[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f8
UserParameter=openvpn.user.real_address.new[*],grep -h "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null | head -1 | cut -d, -f3 | cut -d: -f1
UserParameter=openvpn.user.status[*],if grep -q "^CLIENT_LIST,$1," /var/log/openvpn/status*.log 2>/dev/null; then echo 1; else echo 0; fi
# General OpenVPN Instance Metrics
UserParameter=openvpn.version,openvpn --version 2>&1 | head -1 | awk '{print $2}'
UserParameter=openvpn.user.count,grep -h "^CLIENT_LIST" /var/log/openvpn/status*.log 2>/dev/null | grep -v "Common Name" | wc -l

View File

@ -596,7 +596,7 @@ zabbix_export:
- uuid: 8ac87a366c1a4b3f98d18cc68bd8429f
name: 'Coleta Raw (SNMP): Software Instalado (hrSWRun)'
type: SNMP_AGENT
snmp_oid: walk[1.3.6.1.2.1.25.4.2.1.2,1.3.6.1.2.1.25.4.2.1.7]
snmp_oid: walk[1.3.6.1.2.1.25.4.2.1.2,1.3.6.1.2.1.25.4.2.1.7,1.3.6.1.2.1.25.4.2.1.5]
key: pfsense.sw.walk
history: '0'
value_type: TEXT
@ -604,7 +604,7 @@ zabbix_export:
description: |
MIB: HOST-RESOURCES-MIB
Coleta bruta (Walk) da tabela hrSWRunTable.
Dados usados para itens dependentes de status de serviço.
Dados usados para itens dependentes de status de serviço e descoberta de processos DHCP.
preprocessing:
- type: SNMP_WALK_TO_JSON
parameters:
@ -614,6 +614,9 @@ zabbix_export:
- hrSWRunStatus
- 1.3.6.1.2.1.25.4.2.1.7
- '0'
- hrSWRunParameters
- 1.3.6.1.2.1.25.4.2.1.5
- '0'
tags:
- tag: component
value: raw
@ -1700,6 +1703,68 @@ zabbix_export:
- type: DISCARD_UNCHANGED_HEARTBEAT
parameters:
- 1h
- uuid: d4001ddcfb154daa8029d531c590ba8a
name: 'Descoberta de Processos DHCP'
type: DEPENDENT
key: pfsense.dhcp.discovery
delay: '0'
description: |
Descobre instâncias do DHCP Server (dhcpd) diferenciadas por parâmetros (ex: IPv4 vs IPv6).
Permite monitoramento individual de cada instância do serviço DHCP.
filter:
evaltype: AND
conditions:
- macro: '{#HR_SW_NAME}'
value: '^dhcpd$'
formulaid: A
item_prototypes:
- uuid: 32ee3374996543daba3e1d6e976fc60d
name: 'Processo DHCP [{#HR_SW_PARAMS}]: Status'
type: DEPENDENT
key: 'pfsense.dhcp.process.status[{#HR_SW_PARAMS}]'
delay: '0'
description: |
Status da instância DHCP rodando com argumentos: {#HR_SW_PARAMS}.
Valor 1 = running (em execução), 0 = parado/ausente.
valuemap:
name: Services status
preprocessing:
- type: JSONPATH
parameters:
- '$[?(@.hrSWRunName == "{#HR_SW_NAME}" && @.hrSWRunParameters == "{#HR_SW_PARAMS}")].hrSWRunStatus.first()'
error_handler: CUSTOM_VALUE
error_handler_params: '0'
master_item:
key: pfsense.sw.walk
tags:
- tag: component
value: application
- tag: service
value: dhcp
trigger_prototypes:
- uuid: e84cad72d3aa447d8d0397d24502ee68
expression: 'last(/PFSense by SNMP/pfsense.dhcp.process.status[{#HR_SW_PARAMS}])=0'
name: '🚨 DHCP Parado: Instância {#HR_SW_PARAMS} não está rodando'
event_name: '🚨 DHCP em {HOST.NAME}: Instância "{#HR_SW_PARAMS}" parou'
opdata: 'Status: {ITEM.LASTVALUE}'
priority: HIGH
description: |
A instância do DHCP com parâmetros "{#HR_SW_PARAMS}" parou de responder.
Verifique o serviço DHCP em Services > DHCP Server.
tags:
- tag: scope
value: availability
- tag: service
value: dhcp
lld_macro_paths:
- lld_macro: '{#HR_SW_NAME}'
path: '$.hrSWRunName'
- lld_macro: '{#HR_SW_PARAMS}'
path: '$.hrSWRunParameters'
- lld_macro: '{#HR_SW_STATUS}'
path: '$.hrSWRunStatus'
master_item:
key: pfsense.sw.walk
tags:
- tag: class
value: software

View File

@ -0,0 +1,73 @@
# Documentação: PFSense Hybrid: SNMP + OpenVPN Agent
**Template:** PFSense Hybrid: SNMP + OpenVPN Agent
**Descrição:**
Template Híbrido para monitoramento do pfSense.
SNMP: Monitoramento de interfaces, firewall, serviços (DHCP, DNS, Nginx).
Agent: Monitoramento avançado de OpenVPN (usuários, túneis S2S, tráfego).
Requisitos:
1. Habilite o daemon SNMP em Services na interface web do pfSense.
2. Instale o Zabbix Agent e configure os UserParameters OpenVPN (ver INSTRUCOES_AGENTE.txt).
3. Associe o template ao host.
MIBs: BEGEMOT-PF-MIB, HOST-RESOURCES-MIB
Keys Agent: openvpn.*
Gerado pelo Padrão Gold (Arthur) - ITGuys
## Itens Monitorados
### Itens Globais
- **Coleta Raw (SNMP): Interfaces de Rede PF** (`net.if.pf.walk`)
- **Coleta Raw (SNMP): Interfaces de Rede (IF-MIB)** (`net.if.walk`)
- **Status do servidor DHCP** (`pfsense.dhcpd.status`)
- **Status do servidor DNS** (`pfsense.dns.status`)
- **Estado do processo Nginx (Web)** (`pfsense.nginx.status`)
- **Pacotes com offset incorreto (Bad Offset)** (`pfsense.packets.bad.offset`)
- **Pacotes Fragmentados** (`pfsense.packets.fragment`)
- **Pacotes correspondentes a uma regra de filtro** (`pfsense.packets.match`)
- **Pacotes descartados por limite de memória** (`pfsense.packets.mem.drop`)
- **Pacotes Normalizados** (`pfsense.packets.normalize`)
- **Pacotes Curtos (Short Packets)** (`pfsense.packets.short`)
- **Status de execução do Packet Filter** (`pfsense.pf.status`)
- **Coleta Raw (SNMP): Contadores PF (pfCounter)** (`pfsense.pf_counters.walk`)
- **Contagem de regras de Firewall** (`pfsense.rules.count`)
- **Tabela de Rastreamento: Origens Atuais (Source Tracking)** (`pfsense.source.tracking.table.count`)
- **Tabela de Rastreamento: Limite (Limit)** (`pfsense.source.tracking.table.limit`)
- **Tabela de Rastreamento: Utilização (%)** (`pfsense.source.tracking.table.pused`)
- **Tabela de Estados: Atual (State Table)** (`pfsense.state.table.count`)
- **Tabela de Estados: Limite (Limit)** (`pfsense.state.table.limit`)
- **Tabela de Estados: Utilização (%)** (`pfsense.state.table.pused`)
- **Coleta Raw (SNMP): Software Instalado (hrSWRun)** (`pfsense.sw.walk`)
- **Disponibilidade do Agente SNMP** (`zabbix[host,snmp,available]`)
### Regras de Descoberta (LLD)
#### Descoberta de Usuários OpenVPN (`openvpn.discovery`)
- **Protótipos de Itens:**
- OpenVPN [{#VPN.USER}]: Download Total (Bytes) (`openvpn.user.bytes_received.total[{#VPN.USER}]`)
- OpenVPN [{#VPN.USER}]: Upload Total (Bytes) (`openvpn.user.bytes_sent.total[{#VPN.USER}]`)
- OpenVPN [{#VPN.USER}]: IP Real (`openvpn.user.real_address.new[{#VPN.USER}]`)
- OpenVPN [{#VPN.USER}]: Status (`openvpn.user.status[{#VPN.USER}]`)
- OpenVPN [{#VPN.USER}]: Tempo Conectado (Unix) (`openvpn.user.connected_since[{#VPN.USER}]`)
## Alertas (Triggers)
### Triggers Globais
- [AVERAGE] **🚨 DHCP Parado: Servidor DHCP não está em execução**
- [AVERAGE] **🚨 DNS Parado: Servidor DNS (Unbound) não está em execução**
- [AVERAGE] **🚨 WebServer Parado: Nginx não está rodando**
- [WARNING] **🧩 Fragmentação Excessiva IPv4**
- [WARNING] **🛡️ Possível Ataque/Scan: Pico de Bloqueios**
- [HIGH] **🚨 Firewall Desligado: Packet Filter inativo**
- [WARNING] **⚠️ Tabela de Rastreamento Cheia: Uso elevado ({ITEM.LASTVALUE1}%)**
- [HIGH] **⏳ Source Tracking cheia em < 1h (Previsão)**
- [WARNING] **🔥 Tabela de Estados Crítica: Risco de bloqueio ({ITEM.LASTVALUE1}%)**
- [HIGH] **⏳ Tabela de Estados cheia em < 1h (Previsão)**
- [WARNING] **🚨 Falha SNMP: Sem coleta de dados no pfSense**
### Protótipos de Triggers (LLD)
**Regra: Descoberta de Usuários OpenVPN**

View File

@ -190,10 +190,83 @@ def validate_dashboard_references(content, graph_names):
return errors
def check_duplicate_yaml_keys(file_path):
"""
Check for duplicate YAML keys at the same level (e.g., two 'macros:' sections).
This is a Zabbix import killer - YAML parsers silently merge, but Zabbix rejects.
Uses regex-based scanning since yaml.safe_load silently handles duplicates.
Returns list of errors.
Note: Only checks for duplicates at template-level (indent 4) since nested
keys like 'triggers:' can legitimately appear multiple times in different
item contexts.
"""
errors = []
try:
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
except Exception as e:
errors.append(f"[FILE ERROR] Could not read file: {e}")
return errors
# Track keys at template level (indent 4) only
# Key: key_name -> list of line numbers
template_level_keys = {}
# Critical keys that should never be duplicated at template level
critical_keys = {'macros', 'items', 'discovery_rules', 'dashboards',
'graphs', 'valuemaps', 'value_maps'}
for line_num, line in enumerate(lines, 1):
# Skip comments and empty lines
stripped = line.lstrip()
if not stripped or stripped.startswith('#'):
continue
# Calculate indentation (spaces before content)
indent = len(line) - len(line.lstrip())
# Only check template-level keys (indent 4 for " macros:")
if indent != 4:
continue
# Match YAML key pattern: "key:" or "key: value"
import re
match = re.match(r'^(\s*)([a-zA-Z_][a-zA-Z0-9_]*):', line)
if match:
key_name = match.group(2)
# Only track critical keys
if key_name in critical_keys:
if key_name not in template_level_keys:
template_level_keys[key_name] = []
template_level_keys[key_name].append(line_num)
# Report duplicates
for key_name, line_numbers in template_level_keys.items():
if len(line_numbers) > 1:
lines_str = ', '.join(map(str, line_numbers))
errors.append(f"[DUPLICATE KEY] '{key_name}:' appears {len(line_numbers)} times at template level (lines: {lines_str})")
return errors
def validate_yaml(file_path):
print(f"Validating {file_path}...")
print("=" * 60)
# ========== 0. Check for duplicate YAML keys (pre-parse) ==========
print("\n[0/5] Checking for duplicate YAML keys...")
duplicate_key_errors = check_duplicate_yaml_keys(file_path)
if duplicate_key_errors:
print(f" ❌ Found {len(duplicate_key_errors)} duplicate key issues")
for err in duplicate_key_errors:
print(f"{err}")
return False
else:
print(" ✅ No duplicate YAML keys detected")
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = yaml.safe_load(f)