# Webhook PHP v3 (Zabbix → TELIC)

Container que recebe os alertas do Zabbix por webhook, guarda cada um em uma fila no disco
e repassa ao servidor de notificação da TELIC. Também envia ao Zabbix o tamanho da fila, para
alertar quando as mensagens ficam presas.

Imagem: `registry.telic.tec.br/telictec/webhook-php:v3`

## Sumário

- [Arquitetura](#arquitetura)
- [Estrutura do projeto](#estrutura-do-projeto)
- [Variáveis de ambiente](#variáveis-de-ambiente)
- [Build e deploy](#build-e-deploy)
- [Configuração no Zabbix](#configuração-no-zabbix)
- [Funcionamento da fila](#funcionamento-da-fila)
- [Monitoramento da fila no Zabbix](#monitoramento-da-fila-no-zabbix)
- [Logs e troubleshooting](#logs-e-troubleshooting)

## Arquitetura

```
Zabbix (mídia webhook)
   │  POST JSON  →  http://webhook/webhook.php?token=<TOKEN>
   ▼
webhook.php ──grava──▶ /var/www/html/fila/AAAAMMDD_HHMMSS_<id>.json
                                   │
                    queue.php (loop a cada 1s) lê e envia
                                   ▼
             POST ${URLWEBHOOK}message   (Authorization: Bearer <TOKEN>)
                     Servidor de notificação TELIC
                                   │ sucesso → apaga o arquivo
                                   │ falha   → mantém e tenta de novo

monitor_fila.sh (a cada FILA_INTERVALO s) ──zabbix_sender──▶ item trapper (qtd. de arquivos na fila)
```

O container roda três processos sob o **supervisord** ([supervisord.conf](supervisord.conf)):

| Programa | Comando | Função |
|---|---|---|
| `apache2` | `apache2ctl -DFOREGROUND` | Serve o `webhook.php` na porta 80 (exposta como `8081`) |
| `queue_program_name` | `php /var/www/html/queue.php` | Consome a fila e envia para a TELIC |
| `monitor_fila` | `/usr/local/bin/monitor_fila.sh` | Envia o tamanho da fila ao Zabbix |

## Estrutura do projeto

| Arquivo | Descrição |
|---|---|
| [Dockerfile](Dockerfile) | Imagem `php:8.4.27-apache-trixie` com `supervisor` e `zabbix-sender` |
| [docker-compose.yaml](docker-compose.yaml) | Sobe apenas o serviço `webhook` |
| [docker-compose.yml](docker-compose.yml) | Stack completa: MariaDB, Zabbix Server/Frontend/Agent/Proxy, Grafana e webhook |
| [000-default.conf](000-default.conf) | VirtualHost do Apache (`DocumentRoot /var/www/html`) |
| [supervisord.conf](supervisord.conf) | Processos gerenciados pelo supervisord |
| [monitor_fila.sh](monitor_fila.sh) | Serviço de monitoramento da fila (substitui o cron do `fila.sh`) |
| [fila.sh](fila.sh) | Script antigo: envia o conteúdo de um arquivo ao Zabbix (legado) |
| [zabbix.sh](zabbix.sh) | Script de instalação do host (Docker, rede `app_net`, build e `docker-compose up`) |
| [www/html/webhook.php](www/html/webhook.php) | Endpoint que recebe o POST do Zabbix e grava na fila |
| [www/html/queue.php](www/html/queue.php) | Worker CLI que processa a fila |
| [www/html/config.php](www/html/config.php) | Lê `URLWEBHOOK` do ambiente |
| [www/html/lib/](www/html/lib/) | `TCall` (requisição cURL para a TELIC) e `TLog` (log em stdout) |
| [www/html/script-webhook-zabbix.js](www/html/script-webhook-zabbix.js) | Script da mídia webhook no Zabbix (formato Discord) |

## Variáveis de ambiente

Definidas no `.env` ao lado do compose.

| Variável | Obrigatória | Padrão | Descrição |
|---|---|---|---|
| `URLWEBHOOK` | Sim | — | URL base do servidor de notificação. **Deve terminar com `/`**, pois o envio é feito para `${URLWEBHOOK}message` |
| `TZ` | Não | `America/Sao_Paulo` | Fuso horário |
| `ZABBIX_SERVER` | Para o monitoramento | — | Host/IP do Zabbix Server ou Proxy que recebe o trapper |
| `ZABBIX_HOST` | Para o monitoramento | — | Nome do host cadastrado no Zabbix |
| `ZABBIX_KEY` | Não | `file.content` | Chave do item trapper |
| `ZABBIX_PORT` | Não | `10051` | Porta do trapper |
| `FILA_INTERVALO` | Não | `300` | Intervalo entre envios, em segundos |
| `FILA_DIR` | Não | `/var/www/html/fila` | Pasta monitorada |

Exemplo de `.env`:

```env
URLWEBHOOK=https://notificacao.exemplo.com.br/api/
ZABBIX_SERVER=zabbix-proxy
ZABBIX_HOST=Zabbix-Alaife
ZABBIX_KEY=file.content
FILA_INTERVALO=300
```

> O token do cliente **não** vem do ambiente: ele é enviado pelo Zabbix na query string
> (`?token=...`) de cada requisição. A chave `Token` (`WEBHOOK`) em `config.php` não é usada.

## Build e deploy

Pré-requisito: a rede externa `app_net` precisa existir.

```bash
docker network create --ipv6 --subnet 2001:3984:3989::/64 --gateway 2001:3984:3989::1 app_net
```

Build e publicação da imagem:

```bash
docker build -t registry.telic.tec.br/telictec/webhook-php:v3 .
docker push registry.telic.tec.br/telictec/webhook-php:v3
```

Subir só o webhook:

```bash
docker compose -f docker-compose.yaml up -d
```

Ou a stack completa (Zabbix + Grafana + webhook):

```bash
docker compose -f docker-compose.yml up -d
```

O serviço fica disponível na porta **8081** do host. Limites de recurso do container: 256 MB de memória.

> Os scripts `.sh` têm o CRLF removido no build, então podem ser editados no Windows sem quebrar.

## Configuração no Zabbix

1. Em **Alertas → Tipos de mídia**, crie uma mídia do tipo **Webhook** usando o
   [script-webhook-zabbix.js](www/html/script-webhook-zabbix.js).
2. No parâmetro `discord_endpoint`, aponte para o webhook com o token do cliente:

   ```
   http://webhook/webhook.php?token=<TOKEN_DO_CLIENTE>&_=
   ```

   O script acrescenta `?wait=True` ao final da URL. O `&_=` no final faz esse sufixo cair
   num parâmetro descartável, sem corromper o `token`.
3. Preencha os demais parâmetros padrão da mídia (`zabbix_url`, `event_source`, `event_value`,
   `event_nseverity`, `alert_subject`, `alert_message` etc.).

### IPs permitidos

O `webhook.php` só aceita requisições `POST` vindas destes endereços (lista `$allowed_ips`):

- `::1` e `127.0.0.1`
- `172.16.31.1`
- `2001:3984:3989::/64` (sub-rede IPv6 da `app_net`)

Outros IPs recebem `401`. Para liberar uma nova origem, edite `$allowed_ips` em
[webhook.php](www/html/webhook.php) e gere a imagem novamente.

### Respostas do endpoint

| Situação | HTTP | Corpo |
|---|---|---|
| Mensagem enfileirada | 200 | `{"ok":true}` |
| IP não autorizado | 401 | `{"ok":false,"message":"Recurso negado para o IP ..."}` |
| Método diferente de POST | 401 | `{"ok":false,"message":"Apenas requisições POST devem ser realizadas"}` |
| Corpo não é JSON válido | 401 | `{"ok":false,"message":"Falha ao obter JSON..."}` |
| Falha ao gravar o arquivo | 401 | `{"ok":false,"message":"Falha ao criar arquivo..."}` |

## Funcionamento da fila

- Cada alerta vira um arquivo `fila/AAAAMMDD_HHMMSS_<hex>.json` contendo
  `{"params": <query string>, "data": <corpo JSON>}`.
- O `queue.php` percorre a pasta a cada 1 s e, para cada arquivo:
  - lê e decodifica o JSON. Se houver erro de sintaxe, tenta recuperar o primeiro objeto
    `{...}` completo e descarta o restante;
  - ignora o arquivo se não houver `params.token`;
  - envia `data` para `${URLWEBHOOK}message` com `Authorization: Bearer <token>`;
  - se o servidor responder `{"ok": true}`, **apaga** o arquivo;
  - se falhar, **interrompe a rodada** e tenta de novo no próximo ciclo (o arquivo permanece).
- Arquivos vazios, com JSON irrecuperável ou sem token são ignorados, mas **não são apagados**:
  continuam na pasta e contam no tamanho da fila.

## Monitoramento da fila no Zabbix

### Como era (cron no host)

O envio era feito por um cron no host, lendo um arquivo gerado externamente:

```
*/5 * * * * root /bin/sh /root/ZABBIX-TELIC/fila.sh "2804:54c8:cafe:85::14" "Zabbix-Alaife" "/var/lib/docker/volumes/zabbix-telic_externalscripts/_data/output.txt" >> /var/log/meu_script.log 2>&1
```

### Como ficou (serviço dentro do container)

O serviço `monitor_fila` (via supervisord) conta, a cada `FILA_INTERVALO` segundos, os arquivos
em `/var/www/html/fila` e envia o valor por `zabbix_sender` para o item trapper.
**O cron acima pode ser removido.**

- Valor enviado: quantidade de arquivos na fila (`0` = sem fila).
- Sem `ZABBIX_SERVER`/`ZABBIX_HOST` o serviço fica ocioso (desativado), sem reiniciar em loop.
- Na stack completa o envio vai para o `zabbix-proxy` (mesma rede `app_net`), que repassa ao
  Zabbix principal.

No Zabbix, o item `file.content` deve ser do tipo **Zabbix trapper**. Sugestões de trigger:

```
last(/Zabbix-Alaife/file.content)>0
min(/Zabbix-Alaife/file.content,15m)>0   # só fila persistente; exige item numérico
```

## Logs e troubleshooting

| Log | Comando |
|---|---|
| Worker da fila | `docker exec webhook tail -f /var/log/queue_php.out.log` |
| Erros do worker | `docker exec webhook tail -f /var/log/queue_php.err.log` |
| Monitor da fila | `docker exec webhook tail -f /var/log/monitor_fila.out.log` |
| Apache | `docker exec webhook tail -f /var/log/apache2/error.log` |

Os logs ficam no volume `webhook_log`.

Comandos úteis:

```bash
# Status dos processos do supervisord
docker exec webhook supervisorctl status

# Quantidade de mensagens presas na fila
docker exec webhook sh -c 'ls /var/www/html/fila | wc -l'

# Testar o endpoint de dentro do container
docker exec webhook curl -s -X POST 'http://127.0.0.1/webhook.php?token=TESTE' \
  -H 'Content-Type: application/json' -d '{"teste":true}'
```

Problemas comuns:

- **Fila crescendo**: veja `queue_php.out.log`. Erros `[ERRO CURL]` indicam falha de rede até a
  TELIC; `[ERRO Webhook]` indica que o servidor recusou (token inválido, por exemplo). Confira
  também se `URLWEBHOOK` termina com `/`.
- **Zabbix recebe 401 "Recurso negado"**: o IP de origem não está em `$allowed_ips`.
- **Monitor não envia**: veja `monitor_fila.out.log`. Confirme que o item é do tipo trapper e
  que o nome em `ZABBIX_HOST` é exatamente o do host no Zabbix.
