Sistema bloqueado
-Ative a licenca deste equipamento para liberar o monitoramento.
-Hardware ID
-{{ hwid }}
-
-
- diff --git a/.env.example b/.env.example index 70ae8d1..8bfd9f7 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,8 @@ VFM_APP_SECRET= VFM_LICENSE_MASTER_KEY= VFM_ENV= VFM_LOG_LEVEL=INFO +VFM_SESSION_COOKIE_SECURE= +VFM_TRUST_PROXY= +VFM_MAX_CONTENT_LENGTH=1048576 +HOST=0.0.0.0 PORT=8080 diff --git a/.gitignore b/.gitignore index f5bc35a..f9f99e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ __pycache__/ *.pyc .pytest_cache/ +.venv/ +.codex config_nodes.json mapa_dispositivos.json license.key diff --git a/README.md b/README.md index 449a56c..a512fb6 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,197 @@ -# V-Fire Monitor - -Aplicacao Flask para monitoramento de centrais Notifier via Modbus/TCP, com painel web, descoberta de pontos, integracao com Zabbix e licenciamento por hardware. - +# V-Fire Monitor + +Aplicacao Flask para monitoramento de centrais Notifier via Modbus/TCP, com painel web, descoberta de pontos, integracao com Zabbix e licenciamento por hardware. + ## O que mudou nesta refatoracao - Persistencia JSON com escrita atomica e migracao de configuracao legada. - Login com hash de senha em vez de senha em texto puro no arquivo de configuracao. +- Alteracao de credenciais pelo proprio painel, sem editar arquivo manualmente. - Chave secreta do Flask persistida localmente, sem valor fixo no codigo. +- Cookies de sessao e headers HTTP endurecidos para operacao web basica em producao. +- Protecao CSRF para login e APIs mutaveis. - Validacao de payloads da API e respostas de erro consistentes. +- Validacao de IPs de nodes e validacao do serial antes de gravar a licenca. - Logging basico para diagnostico, no lugar de falhas silenciosas. - Polling Modbus e discovery por blocos, reduzindo chamadas individuais. -- Frontend com tratamento de erro e textos corrigidos. +- Healthcheck publico e endpoint autenticado com status operacional do monitor. +- Shutdown mais limpo da thread de monitoramento. +- Frontend com assets locais, sem dependencia de CDN externa. - Gerador de licenca com argumentos de linha de comando e validacao de data. - -## Estrutura - + +## Estrutura + - `monitor.py`: entrypoint simples da aplicacao Flask. +- `simulator_nfs320.py`: simulador Modbus/TCP local de uma central NFS-320 para testes. - `vfire_monitor/__init__.py`: app factory e bootstrap da aplicacao. -- `vfire_monitor/core.py`: regras de negocio, persistencia, licenca e engine de monitoramento. -- `vfire_monitor/routes.py`: rotas web e APIs. +- `vfire_monitor/core.py`: regras de negocio, persistencia, licenca e engine de monitoramento. +- `vfire_monitor/routes.py`: rotas web e APIs. - `generator.py`: gerador de serial de licenca. +- `static/`: assets locais de interface carregados pela aplicacao. - `templates/`: telas do login e dashboard. -- `tests/`: suite inicial de testes automatizados. -- `config_nodes.json`: configuracao persistida da aplicacao. -- `mapa_dispositivos.json`: mapa de dispositivos descobertos. -- `license.key`: serial instalado localmente. -- `app_secret.key`: segredo de sessao gerado automaticamente na primeira execucao. - +- `tests/`: suite inicial de testes automatizados. +- `config_nodes.json`: configuracao persistida da aplicacao. +- `mapa_dispositivos.json`: mapa de dispositivos descobertos. +- `license.key`: serial instalado localmente. +- `app_secret.key`: segredo de sessao gerado automaticamente na primeira execucao. + ## Requisitos - Python 3.10+ - Conectividade com as centrais via Modbus/TCP - Acesso ao servidor Zabbix, quando a integracao estiver habilitada - -Instalacao: - -```bash -pip install -r requirements.txt -``` - -## Variaveis de ambiente - -Veja `.env.example`. - -As principais: - -- `VFM_DEFAULT_PASSWORD`: senha inicial do usuario `admin` na primeira carga do sistema. -- `VFM_APP_SECRET`: opcional, substitui o segredo salvo em `app_secret.key`. -- `VFM_LICENSE_MASTER_KEY`: chave mestre do licenciamento. Em producao, use esta variavel e remova a dependencia da chave legada. -- `VFM_ENV`: use `production` para obrigar `VFM_LICENSE_MASTER_KEY` no startup. +- Para instalacao automatizada da stack completa, Debian 13 (trixie) + +Instalacao: + +```bash +pip install -r requirements.txt +``` + +## Variaveis de ambiente + +Veja `.env.example`. + +As principais: + +- `VFM_DEFAULT_PASSWORD`: senha inicial do usuario `admin` na primeira carga do sistema. +- `VFM_APP_SECRET`: opcional, substitui o segredo salvo em `app_secret.key`. +- `VFM_LICENSE_MASTER_KEY`: chave mestre do licenciamento. Em producao, use esta variavel e remova a dependencia da chave legada. +- `VFM_ENV`: use `production` para obrigar `VFM_LICENSE_MASTER_KEY` no startup. - `VFM_LOG_LEVEL`: nivel de log, por exemplo `INFO` ou `DEBUG`. +- `VFM_SESSION_COOKIE_SECURE`: force cookie `Secure`, recomendado atras de HTTPS. +- `VFM_TRUST_PROXY`: habilita `ProxyFix` quando houver reverse proxy na frente. +- `VFM_MAX_CONTENT_LENGTH`: limite maximo do corpo HTTP em bytes. +- `HOST`: host HTTP do processo Flask. - `PORT`: porta HTTP da aplicacao. +Cada central aceita: + +- `nome` +- `ip` +- `unit` +- `port`: opcional na integracao Modbus, default `502` + ## Execucao - -```bash -python monitor.py -``` - + +```bash +python monitor.py +``` + O sistema sobe em `http://0.0.0.0:8080` por padrao. -## Geracao de licenca - -Modo interativo: +Healthcheck: ```bash -python generator.py +curl http://127.0.0.1:8080/healthz ``` -Modo por argumentos: +Status operacional autenticado: ```bash +curl http://127.0.0.1:8080/api/system/status +``` + +## Instalador Debian 13 + +O repositorio inclui um instalador para Debian 13 que provisiona: + +- `V-Fire Monitor` como servico `systemd` +- `PostgreSQL` +- `Zabbix Server + frontend Nginx` +- `Grafana` +- importacao automatica do template `Notifier NFS320` +- criacao automatica do host monitorado no Zabbix +- datasource do Grafana apontando para o Zabbix +- dashboard inicial de planta baixa e icones SVG locais + +Arquivos: + +- [installer/install_debian13.sh](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/installer/install_debian13.sh) +- [installer/vfire-stack.env.example](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/installer/vfire-stack.env.example) +- [serve.py](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/serve.py): entrypoint de producao do app com Waitress +- [docs/INSTALACAO_DEBIAN13.md](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/docs/INSTALACAO_DEBIAN13.md) +- [docs/LICENCIAMENTO.md](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/docs/LICENCIAMENTO.md) +- [docs/GRAFANA_PLANTA_BAIXA.md](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/docs/GRAFANA_PLANTA_BAIXA.md) +- [floorplan/floorplan-map.example.json](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/floorplan/floorplan-map.example.json) +- [tools/build_floorplan_bundle.py](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/tools/build_floorplan_bundle.py) + +Fluxo recomendado: + +1. Copie `installer/vfire-stack.env.example` para `installer/vfire-stack.env`. +2. Ajuste senhas, hostname e `VFM_LICENSE_MASTER_KEY`. +3. Opcionalmente valide em dry-run: + +```bash +./installer/install_debian13.sh --dry-run ./installer/vfire-stack.env +``` + +4. Execute como `root` no Debian 13: + +```bash +chmod +x installer/install_debian13.sh +./installer/install_debian13.sh ./installer/vfire-stack.env +``` + +Portas padrao do instalador: + +- `V-Fire Monitor`: `8088` +- `Zabbix frontend`: `8080` +- `Grafana`: `3000` + +Ao final, o instalador grava um resumo em `/root/vfire-stack-summary.txt`. + +## Geracao de licenca + +Modo interativo: + +```bash +python generator.py +``` + +Modo por argumentos: + +```bash python generator.py --hwid "UUID-DO-CLIENTE" --cliente "Cliente" --expira 2026-12-31 ``` -## Observacoes operacionais +## Simulador NFS-320 +Para testar sem uma central fisica: + +```bash +python simulator_nfs320.py --host 127.0.0.1 --port 1502 +``` + +Depois, no painel do V-Fire Monitor, cadastre uma central com: + +- `Nome`: `Sim NFS320` +- `IP`: `127.0.0.1` +- `Unit`: `3` +- `Porta`: `1502` + +O simulador publica detectores, modulos e um painel repetidor com estados alternando entre normal, incidente, ack e removido. + +## Observacoes operacionais + - A senha do painel fica armazenada como hash em `config_nodes.json`. +- O serial de licenca e validado antes de ser salvo em `license.key`. +- Em Linux, `app_secret.key` e `license.key` passam a ser gravados com permissao privada (`0600`). +- O painel exige token CSRF em login e chamadas mutaveis da API. - Se existir configuracao antiga com `web_password`, ela e migrada automaticamente para `web_password_hash`. - Em ambiente de desenvolvimento, o sistema ainda aceita a chave de licenca legada embutida para manter compatibilidade. - Em ambiente de producao (`VFM_ENV=production`), `VFM_LICENSE_MASTER_KEY` passa a ser obrigatoria e o sistema falha no startup sem ela. -- Bootstrap continua sendo carregado via CDN. Se o ambiente nao tiver acesso externo, copie os assets localmente e ajuste os templates. - -## Testes - +- A interface web usa assets locais em `static/`, sem dependencia de internet para carregar CSS e JS. + +## Testes + Executar: ```bash -pytest +python3 -m pytest ``` - -## Proximos passos recomendados - -- Adicionar testes cobrindo polling Modbus e integracao com Zabbix com doubles dedicados. -- Separar configuracao e logging em modulos proprios se a aplicacao continuar crescendo. -- Trocar Bootstrap via CDN por assets locais se o ambiente alvo nao tiver acesso externo. + +## Proximos passos recomendados + +- Adicionar testes cobrindo polling Modbus e integracao com Zabbix com doubles dedicados. +- Separar configuracao e logging em modulos proprios se a aplicacao continuar crescendo. diff --git a/dashboards/grafana/vfire-floorplan-starter.json b/dashboards/grafana/vfire-floorplan-starter.json new file mode 100644 index 0000000..de28de8 --- /dev/null +++ b/dashboards/grafana/vfire-floorplan-starter.json @@ -0,0 +1,65 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "content": "
Este dashboard foi provisionado para servir de base.
Use um painel Canvas no Grafana para montar a planta baixa.
URLs sugeridas de icones:
http://SEU_HOST:8088/static/grafana-icons/detector-smoke.svghttp://SEU_HOST:8088/static/grafana-icons/detector-heat.svghttp://SEU_HOST:8088/static/grafana-icons/module-input.svghttp://SEU_HOST:8088/static/grafana-icons/siren.svghttp://SEU_HOST:8088/static/grafana-icons/panel-nfs320.svgAssocie cada icone a itens do Zabbix via datasource Zabbix.
", + "mode": "html" + }, + "pluginVersion": "11.0.0", + "title": "Como usar", + "type": "text" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "style": "dark", + "tags": [ + "vfire", + "floorplan", + "starter" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V-Fire Floorplan Starter", + "uid": "vfire-floorplan", + "version": 1, + "weekStart": "" +} diff --git a/docs/GRAFANA_PLANTA_BAIXA.md b/docs/GRAFANA_PLANTA_BAIXA.md new file mode 100644 index 0000000..45b463f --- /dev/null +++ b/docs/GRAFANA_PLANTA_BAIXA.md @@ -0,0 +1,123 @@ +# Grafana e Planta Baixa + +Este guia cobre o uso do Grafana com o `V-Fire Monitor` para dashboards baseados em planta baixa. + +## Arquivos de apoio + +- [floorplan/floorplan-map.example.json](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/floorplan/floorplan-map.example.json) +- [tools/build_floorplan_bundle.py](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/tools/build_floorplan_bundle.py) + +## O que o instalador ja deixa pronto + +Na instalacao Debian 13: + +- plugin Zabbix do Grafana instalado +- datasource `Zabbix` provisionado +- dashboard inicial `V-Fire Floorplan Starter` +- icones SVG servidos pelo proprio `V-Fire Monitor` + +## URLs dos icones + +Substitua `SEU_HOST` pelo host do V-Fire Monitor. + +- `http://SEU_HOST:8088/static/grafana-icons/detector-smoke.svg` +- `http://SEU_HOST:8088/static/grafana-icons/detector-heat.svg` +- `http://SEU_HOST:8088/static/grafana-icons/module-input.svg` +- `http://SEU_HOST:8088/static/grafana-icons/siren.svg` +- `http://SEU_HOST:8088/static/grafana-icons/panel-nfs320.svg` + +## Dashboard inicial + +O instalador provisiona a pasta `V-Fire` no Grafana e um dashboard chamado: + +- `V-Fire Floorplan Starter` + +Esse dashboard serve como ponto de partida e lembra as URLs dos icones. + +## Formato versionavel da planta + +Para nao depender apenas de arrastar elementos manualmente no Grafana, o repositorio agora inclui um formato JSON de mapa de planta. + +Campos principais: + +- `title` +- `grafana_dashboard_uid` +- `background_image_url` +- `vfire_base_url` +- `zabbix_host` +- `items` + +Cada item descreve: + +- `id` +- `label` +- `item_key` +- `icon` +- `x` +- `y` +- `width` +- `height` +- `rotation` + +## Gerar bundle normalizado + +Exemplo: + +```bash +python3 tools/build_floorplan_bundle.py floorplan/floorplan-map.example.json +``` + +Isso gera um arquivo `.bundle.json` com: + +- URLs completas dos icones +- referencia do host do Zabbix +- referencias dos `item_key` +- coordenadas prontas para uso em futuras automacoes + +## Montando a planta baixa + +Abordagem recomendada: + +1. Abra o dashboard `V-Fire Floorplan Starter` +2. Crie ou edite um painel do tipo `Canvas` +3. Defina a imagem de fundo com a planta baixa do local +4. Adicione elementos visuais ou imagens representando cada ponto +5. Use os SVGs do `V-Fire Monitor` como base visual +6. Associe cada elemento ao item correspondente no datasource `Zabbix` + +## Como mapear itens + +Os itens que o app envia ao Zabbix seguem chaves como: + +- `notifier.status[NomeDoPonto]` +- `notifier.comm[NomeDoPonto]` +- `node.status[NomeDaCentral]` + +Os nomes dos pontos sao descobertos automaticamente a partir do monitoramento. + +## Estrategia recomendada de planta baixa + +- use a planta como imagem base +- posicione um icone por detector, modulo ou sirene +- use cor dinamica para refletir estado +- crie uma legenda simples com: + - normal + - incidente + - ack + - removido + - offline + +## Observacao pratica + +O instalador nao conhece a planta baixa real do cliente. + +Por isso, ele consegue deixar: + +- Grafana integrado +- datasource pronto +- dashboard inicial provisionado +- icones prontos +- formato versionavel da planta baixa +- bundle normalizado para futuras importacoes + +Mas o posicionamento exato dos elementos na planta baixa ainda precisa ser feito conforme o layout real da instalacao. diff --git a/docs/INSTALACAO_DEBIAN13.md b/docs/INSTALACAO_DEBIAN13.md new file mode 100644 index 0000000..8d11b3a --- /dev/null +++ b/docs/INSTALACAO_DEBIAN13.md @@ -0,0 +1,186 @@ +# Instalacao no Debian 13 + +Este guia cobre a instalacao da stack completa em uma maquina dedicada com: + +- `V-Fire Monitor` +- `PostgreSQL` +- `Zabbix Server + frontend` +- `Grafana` + +## Visao geral + +O instalador provisiona: + +- app em `/opt/vfire-monitor` +- dados persistentes em `/var/lib/vfire-monitor` +- logs do app em `/var/log/vfire-monitor/app.log` +- servico `systemd` do V-Fire Monitor +- template do Zabbix importado automaticamente +- host monitorado do Zabbix criado automaticamente +- datasource do Grafana apontando para o Zabbix +- dashboard inicial do Grafana provisionado + +Portas padrao: + +- `V-Fire Monitor`: `8088` +- `Zabbix frontend`: `8080` +- `Grafana`: `3000` + +## Requisitos + +- Debian 13 (`trixie`) +- acesso `root` +- internet para baixar pacotes +- portas livres para os servicos +- chave `VFM_LICENSE_MASTER_KEY` se a instalacao for de producao + +## Arquivos do instalador + +- [installer/install_debian13.sh](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/installer/install_debian13.sh) +- [installer/vfire-stack.env.example](/mnt/c/Users/Syllas/Documents/V-Fire-Monitor/installer/vfire-stack.env.example) + +## Passo 1: preparar configuracao + +Copie o arquivo de exemplo: + +```bash +cp installer/vfire-stack.env.example installer/vfire-stack.env +``` + +Edite `installer/vfire-stack.env`. + +Campos minimos que voce deve revisar: + +- `PUBLIC_HOSTNAME` +- `VFM_DEFAULT_PASSWORD` +- `VFM_LICENSE_MASTER_KEY` +- `ZABBIX_DB_PASSWORD` +- `ZABBIX_ADMIN_PASSWORD` +- `GRAFANA_ADMIN_PASSWORD` + +Se voce quiser testar sem chave mestre, pode usar: + +```env +ALLOW_LEGACY_LICENSE_KEY=1 +``` + +Isso e aceitavel para laboratorio. Para producao, use `VFM_LICENSE_MASTER_KEY`. + +## Passo 2: validar em dry-run + +Antes de instalar de verdade: + +```bash +chmod +x installer/install_debian13.sh +./installer/install_debian13.sh --dry-run ./installer/vfire-stack.env +``` + +Esse modo mostra as principais acoes sem executar a instalacao real. + +## Passo 3: executar a instalacao + +Como `root`: + +```bash +./installer/install_debian13.sh ./installer/vfire-stack.env +``` + +## Passo 4: validar os servicos + +Verifique: + +```bash +systemctl status vfire-monitor +systemctl status zabbix-server +systemctl status grafana-server +systemctl status nginx +``` + +Healthchecks HTTP: + +```bash +curl http://127.0.0.1:8088/healthz +curl -I http://127.0.0.1:8080/ +curl -I http://127.0.0.1:3000/login +``` + +## Passo 5: acessar os paineis + +Substitua `SEU_HOST` pelo hostname ou IP do servidor. + +V-Fire Monitor: + +```text +http://SEU_HOST:8088 +``` + +Zabbix: + +```text +http://SEU_HOST:8080 +``` + +Grafana: + +```text +http://SEU_HOST:3000 +``` + +## Credenciais + +O instalador grava um resumo em: + +```text +/root/vfire-stack-summary.txt +``` + +Esse arquivo inclui: + +- URL do V-Fire Monitor +- senha inicial do painel +- senha do Admin do Zabbix +- senha do admin do Grafana +- credenciais do banco do Zabbix +- nome do template do Zabbix +- nome do host do Zabbix + +## Estrutura final + +Arquivos principais apos instalar: + +- codigo: `/opt/vfire-monitor` +- dados: `/var/lib/vfire-monitor` +- ambiente: `/etc/default/vfire-monitor` +- servico: `/etc/systemd/system/vfire-monitor.service` + +## Operacoes comuns + +Reiniciar o app: + +```bash +systemctl restart vfire-monitor +``` + +Ver log do app: + +```bash +tail -f /var/log/vfire-monitor/app.log +``` + +Reiniciar Zabbix: + +```bash +systemctl restart zabbix-server nginx +``` + +Reiniciar Grafana: + +```bash +systemctl restart grafana-server +``` + +## Observacoes + +- o instalador foi desenhado para Debian 13 +- o app roda com Waitress, nao com o servidor de desenvolvimento do Flask +- o frontend do app usa assets locais, sem depender de CDN diff --git a/docs/LICENCIAMENTO.md b/docs/LICENCIAMENTO.md new file mode 100644 index 0000000..ccba9d3 --- /dev/null +++ b/docs/LICENCIAMENTO.md @@ -0,0 +1,113 @@ +# Licenciamento + +Este guia cobre a geracao e a instalacao de licenca do `V-Fire Monitor`. + +## Como o licenciamento funciona + +A licenca vincula: + +- `HWID` da maquina +- nome do cliente +- data de expiracao + +O serial e salvo em `license.key`. + +## Modos de chave + +Existem dois cenarios: + +1. `Chave mestre via VFM_LICENSE_MASTER_KEY` +2. `Chave legada embutida` + +Para producao, use `VFM_LICENSE_MASTER_KEY`. + +## Identificar o HWID + +Quando o app esta sem licenca, a tela inicial mostra o `Hardware ID`. + +Voce tambem pode coletar no proprio sistema onde o app esta rodando: + +```bash +python3 - <<'PY' +from vfire_monitor.core import get_hardware_id +print(get_hardware_id()) +PY +``` + +## Gerar o serial + +Uso por argumentos: + +```bash +python3 generator.py --hwid "HWID-DA-MAQUINA" --cliente "Nome do Cliente" --expira 2027-01-01 +``` + +Se `VFM_LICENSE_MASTER_KEY` estiver definida no ambiente, o gerador usa essa chave. + +Se nao estiver definida, ele usa a chave legada embutida. + +## Instalar a licenca pelo painel + +1. Abra o painel do `V-Fire Monitor` +2. Na tela de bloqueio, copie o `HWID` +3. Gere o serial +4. Cole o serial no campo de ativacao +5. Clique em `Ativar licenca` + +O backend valida: + +- serial +- HWID +- data de expiracao + +Se tudo estiver correto, o monitoramento e liberado. + +## Instalar a licenca manualmente no servidor + +Se voce ja tem o serial: + +```bash +cat > /var/lib/vfire-monitor/license.key <<'EOF' +SEU_SERIAL_AQUI +EOF + +chown vfire-monitor:vfire-monitor /var/lib/vfire-monitor/license.key +chmod 600 /var/lib/vfire-monitor/license.key +systemctl restart vfire-monitor +``` + +## Validar a licenca instalada + +Exemplo local: + +```bash +python3 - <<'PY' +from pathlib import Path +from vfire_monitor.core import LEGACY_LICENSE_KEY, LicenseService + +service = LicenseService(Path("license.key"), LEGACY_LICENSE_KEY) +status = service.verify() +print(status.valid, status.message, status.days_remaining, status.hardware_id) +PY +``` + +Se a instalacao de producao usar `VFM_LICENSE_MASTER_KEY`, a validacao deve usar a mesma chave mestre. + +## Quando a licenca falha + +Os erros mais comuns sao: + +- `Licenca ausente` +- `Serial invalido` +- `HWID incompativel` +- `Licenca expirada` + +## Boas praticas + +- use chave mestre por variavel de ambiente em producao +- nao distribua a chave legada em ambiente final +- mantenha backup de: + - `license.key` + - `app_secret.key` + - `config_nodes.json` + - `mapa_dispositivos.json` diff --git a/floorplan/floorplan-map.example.json b/floorplan/floorplan-map.example.json new file mode 100644 index 0000000..2913a44 --- /dev/null +++ b/floorplan/floorplan-map.example.json @@ -0,0 +1,39 @@ +{ + "title": "Planta Baixa - Pavimento 1", + "grafana_dashboard_uid": "vfire-floorplan-pav1", + "background_image_url": "https://example.local/planta/pavimento-1.png", + "vfire_base_url": "http://127.0.0.1:8088", + "zabbix_host": "NFS-320", + "items": [ + { + "id": "det-recepcao", + "label": "Detector Recepcao", + "item_key": "notifier.status[Detector Recepcao]", + "icon": "detector-smoke", + "x": 180, + "y": 120, + "width": 36, + "height": 36 + }, + { + "id": "mod-bomba", + "label": "Modulo Bomba Incendio", + "item_key": "notifier.status[Modulo Bomba Incendio]", + "icon": "module-input", + "x": 420, + "y": 260, + "width": 40, + "height": 40 + }, + { + "id": "sirene-p1", + "label": "Sirene Pavimento 1", + "item_key": "notifier.status[Modulo Sirene Pavimento 1]", + "icon": "siren", + "x": 680, + "y": 110, + "width": 42, + "height": 42 + } + ] +} diff --git a/generator.py b/generator.py index a5b63eb..55e1f98 100644 --- a/generator.py +++ b/generator.py @@ -1,58 +1,58 @@ -import argparse -import json -import os -from datetime import datetime - -from cryptography.fernet import Fernet - - -LEGACY_LICENSE_KEY = b"vS-C5Z_R1ST-Gf_K8_L9_Xo2-M1A3B5C7D9E1F2G3H4=" - - -def load_master_key() -> bytes: - env_key = os.getenv("VFM_LICENSE_MASTER_KEY") - if env_key: - return env_key.encode("utf-8") - return LEGACY_LICENSE_KEY - - -def parse_args(): - parser = argparse.ArgumentParser(description="Gerador de licenca do V-Fire Monitor") - parser.add_argument("--hwid", help="Hardware ID do cliente") - parser.add_argument("--cliente", help="Nome do cliente") - parser.add_argument("--expira", help="Data de expiracao no formato AAAA-MM-DD") - return parser.parse_args() - - -def ask_if_missing(value: str, prompt: str) -> str: - return value if value else input(prompt).strip() - - -def validate_expiration(date_text: str) -> str: - datetime.strptime(date_text, "%Y-%m-%d") - return date_text - - -def gerar_serial(hardware_id: str, cliente: str, data_expiracao: str) -> str: - fernet = Fernet(load_master_key()) - payload = {"h": hardware_id.strip(), "c": cliente.strip(), "e": validate_expiration(data_expiracao)} - return fernet.encrypt(json.dumps(payload, ensure_ascii=False).encode("utf-8")).decode("utf-8") - - -def main(): - args = parse_args() - - hardware_id = ask_if_missing(args.hwid, "Hardware ID do cliente: ") - cliente = ask_if_missing(args.cliente, "Nome do cliente: ") - expiracao = ask_if_missing(args.expira, "Validade (AAAA-MM-DD): ") - - if not hardware_id: - raise SystemExit("Hardware ID obrigatorio.") - if not cliente: - raise SystemExit("Nome do cliente obrigatorio.") - - print(gerar_serial(hardware_id, cliente, expiracao)) - - -if __name__ == "__main__": - main() +import argparse +import json +import os +from datetime import datetime + +from cryptography.fernet import Fernet + + +LEGACY_LICENSE_KEY = b"vS-C5Z_R1ST-Gf_K8_L9_Xo2-M1A3B5C7D9E1F2G3H4=" + + +def load_master_key() -> bytes: + env_key = os.getenv("VFM_LICENSE_MASTER_KEY") + if env_key: + return env_key.encode("utf-8") + return LEGACY_LICENSE_KEY + + +def parse_args(): + parser = argparse.ArgumentParser(description="Gerador de licenca do V-Fire Monitor") + parser.add_argument("--hwid", help="Hardware ID do cliente") + parser.add_argument("--cliente", help="Nome do cliente") + parser.add_argument("--expira", help="Data de expiracao no formato AAAA-MM-DD") + return parser.parse_args() + + +def ask_if_missing(value: str, prompt: str) -> str: + return value if value else input(prompt).strip() + + +def validate_expiration(date_text: str) -> str: + datetime.strptime(date_text, "%Y-%m-%d") + return date_text + + +def gerar_serial(hardware_id: str, cliente: str, data_expiracao: str) -> str: + fernet = Fernet(load_master_key()) + payload = {"h": hardware_id.strip(), "c": cliente.strip(), "e": validate_expiration(data_expiracao)} + return fernet.encrypt(json.dumps(payload, ensure_ascii=False).encode("utf-8")).decode("utf-8") + + +def main(): + args = parse_args() + + hardware_id = ask_if_missing(args.hwid, "Hardware ID do cliente: ") + cliente = ask_if_missing(args.cliente, "Nome do cliente: ") + expiracao = ask_if_missing(args.expira, "Validade (AAAA-MM-DD): ") + + if not hardware_id: + raise SystemExit("Hardware ID obrigatorio.") + if not cliente: + raise SystemExit("Nome do cliente obrigatorio.") + + print(gerar_serial(hardware_id, cliente, expiracao)) + + +if __name__ == "__main__": + main() diff --git a/installer/install_debian13.sh b/installer/install_debian13.sh new file mode 100644 index 0000000..563d038 --- /dev/null +++ b/installer/install_debian13.sh @@ -0,0 +1,640 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +CONFIG_FILE="${1:-${SCRIPT_DIR}/vfire-stack.env}" +DRY_RUN=0 + +APP_USER="vfire-monitor" +APP_GROUP="vfire-monitor" +APP_HOME="/opt/vfire-monitor" +APP_VENV="${APP_HOME}/.venv" +APP_STATE_DIR="/var/lib/vfire-monitor" +APP_LOG_DIR="/var/log/vfire-monitor" +APP_SERVICE="vfire-monitor.service" +APP_ENV_FILE="/etc/default/vfire-monitor" +APP_SYSTEMD_FILE="/etc/systemd/system/${APP_SERVICE}" + +ZABBIX_APT_KEYRING="/usr/share/keyrings/zabbix.gpg" +ZABBIX_APT_LIST="/etc/apt/sources.list.d/zabbix.list" +GRAFANA_APT_KEYRING="/etc/apt/keyrings/grafana.asc" +GRAFANA_APT_LIST="/etc/apt/sources.list.d/grafana.list" +GRAFANA_DASHBOARD_DIR="/var/lib/grafana/dashboards/vfire" +SUMMARY_FILE="/root/vfire-stack-summary.txt" + +log() { + printf '[vfire-installer] %s\n' "$*" +} + +die() { + printf '[vfire-installer] ERROR: %s\n' "$*" >&2 + exit 1 +} + +run_cmd() { + if [[ "${DRY_RUN}" == "1" ]]; then + printf '[dry-run] %s\n' "$*" + return 0 + fi + "$@" +} + +parse_args() { + local positional=() + while (($#)); do + case "$1" in + --dry-run) + DRY_RUN=1 + shift + ;; + *) + positional+=("$1") + shift + ;; + esac + done + + if ((${#positional[@]} > 0)); then + CONFIG_FILE="${positional[0]}" + fi +} + +require_root() { + [[ "${EUID}" -eq 0 ]] || die "Execute como root." +} + +require_supported_os() { + source /etc/os-release + [[ "${ID}" == "debian" ]] || die "Este instalador suporta apenas Debian." + [[ "${VERSION_CODENAME:-}" == "trixie" ]] || die "Este instalador foi preparado para Debian 13 (trixie)." +} + +random_secret() { + tr -dc 'A-Za-z0-9' /dev/null || hostname)}" + VFM_APP_PORT="${VFM_APP_PORT:-8088}" + ZABBIX_WEB_PORT="${ZABBIX_WEB_PORT:-8080}" + GRAFANA_PORT="${GRAFANA_PORT:-3000}" + VFM_DEFAULT_PASSWORD="${VFM_DEFAULT_PASSWORD:-admin}" + VFM_LOG_LEVEL="${VFM_LOG_LEVEL:-INFO}" + ZABBIX_VERSION="${ZABBIX_VERSION:-7.4}" + ZABBIX_DB_NAME="${ZABBIX_DB_NAME:-zabbix}" + ZABBIX_DB_USER="${ZABBIX_DB_USER:-zabbix}" + ZABBIX_ADMIN_USERNAME="${ZABBIX_ADMIN_USERNAME:-Admin}" + ZABBIX_ADMIN_PASSWORD="${ZABBIX_ADMIN_PASSWORD:-zabbix}" + ZABBIX_TEMPLATE_FILE="${ZABBIX_TEMPLATE_FILE:-${APP_HOME}/zbx_export_templates.yaml}" + ZABBIX_TEMPLATE_NAME="${ZABBIX_TEMPLATE_NAME:-Notifier NFS320}" + ZABBIX_HOST_GROUP="${ZABBIX_HOST_GROUP:-Notifier}" + ZABBIX_MONITORED_HOST="${ZABBIX_MONITORED_HOST:-NFS-320}" + GRAFANA_ADMIN_USER="${GRAFANA_ADMIN_USER:-admin}" + GRAFANA_INSTALL_ZABBIX_PLUGIN="${GRAFANA_INSTALL_ZABBIX_PLUGIN:-1}" + ALLOW_LEGACY_LICENSE_KEY="${ALLOW_LEGACY_LICENSE_KEY:-0}" + + ZABBIX_DB_PASSWORD="${ZABBIX_DB_PASSWORD:-$(random_secret)}" + GRAFANA_ADMIN_PASSWORD="${GRAFANA_ADMIN_PASSWORD:-$(random_secret)}" + + [[ "${ZABBIX_ADMIN_USERNAME}" == "Admin" ]] || die "O instalador atualmente suporta apenas o usuario administrativo padrao do Zabbix: Admin." + + if [[ -z "${VFM_LICENSE_MASTER_KEY:-}" && "${ALLOW_LEGACY_LICENSE_KEY}" != "1" ]]; then + die "Defina VFM_LICENSE_MASTER_KEY no arquivo de configuracao ou ajuste ALLOW_LEGACY_LICENSE_KEY=1." + fi +} + +assert_command_dependencies() { + local cmd + for cmd in curl jq gpg wget rsync python3 psql runuser systemctl ss; do + command -v "${cmd}" >/dev/null 2>&1 || die "Comando obrigatorio ausente: ${cmd}" + done +} + +assert_ports_available() { + local port + [[ "${DRY_RUN}" == "1" ]] && return 0 + for port in "${VFM_APP_PORT}" "${ZABBIX_WEB_PORT}" "${GRAFANA_PORT}"; do + if ss -ltn "( sport = :${port} )" | tail -n +2 | grep -q .; then + die "Porta ${port} ja esta em uso." + fi + done +} + +apt_install() { + if [[ "${DRY_RUN}" == "1" ]]; then + printf '[dry-run] DEBIAN_FRONTEND=noninteractive apt-get install -y %s\n' "$*" + return 0 + fi + DEBIAN_FRONTEND=noninteractive apt-get install -y "$@" +} + +write_zabbix_repo() { + run_cmd install -d -m 0755 /usr/share/keyrings + if [[ "${DRY_RUN}" == "1" ]]; then + printf '[dry-run] curl -fsSL https://repo.zabbix.com/zabbix-official-repo.key | gpg --dearmor -o %s\n' "${ZABBIX_APT_KEYRING}" + else + curl -fsSL "https://repo.zabbix.com/zabbix-official-repo.key" | gpg --dearmor -o "${ZABBIX_APT_KEYRING}" + fi + run_cmd chmod 0644 "${ZABBIX_APT_KEYRING}" + cat >"${ZABBIX_APT_LIST}" <Ative a licenca deste equipamento para liberar o monitoramento.
-Hardware ID
-{{ hwid }}
-
-
- Ative a licenca deste equipamento para liberar o monitoramento.
+Hardware ID
+{{ hwid }}
+
+
+